shaderflow 0.4.0.dev1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shaderflow might be problematic. Click here for more details.
- ShaderFlow/Common/Imgui.py +0 -0
- ShaderFlow/Common/Notes.py +134 -0
- ShaderFlow/Common/__init__.py +0 -0
- ShaderFlow/Message.py +147 -0
- ShaderFlow/Module.py +162 -0
- ShaderFlow/Modules/Audio.py +431 -0
- ShaderFlow/Modules/Bouncing.py +94 -0
- ShaderFlow/Modules/Camera.py +451 -0
- ShaderFlow/Modules/Depth.py +0 -0
- ShaderFlow/Modules/Dynamics.py +288 -0
- ShaderFlow/Modules/Frametimer.py +79 -0
- ShaderFlow/Modules/Keyboard.py +43 -0
- ShaderFlow/Modules/Noise.py +80 -0
- ShaderFlow/Modules/OpticalFlow.py +1 -0
- ShaderFlow/Modules/Piano.py +362 -0
- ShaderFlow/Modules/Spectrogram.py +315 -0
- ShaderFlow/Modules/Video.py +234 -0
- ShaderFlow/Modules/Waveform.py +100 -0
- ShaderFlow/Modules/__init__.py +0 -0
- ShaderFlow/Resources/Images/ShaderFlow.png +0 -0
- ShaderFlow/Resources/Images/ShaderFlow.svg +1 -0
- ShaderFlow/Resources/Scenes/Examples/Demo.py +284 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Bars.frag +20 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Bouncing.frag +50 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Life/Simulation.glsl +49 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Life/Visuals.glsl +31 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/MotionBlur.frag +20 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Multipass.frag +43 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/RayMarch.frag +59 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/ShaderToy.frag +65 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Video.frag +5 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Visualizer.frag +75 -0
- ShaderFlow/Resources/Scenes/Examples/GLSL/Waveform.frag +18 -0
- ShaderFlow/Resources/Shaders/Fragment/Default.glsl +65 -0
- ShaderFlow/Resources/Shaders/Fragment/Final.glsl +7 -0
- ShaderFlow/Resources/Shaders/Fragment/Loading.frag +40 -0
- ShaderFlow/Resources/Shaders/Fragment/Missing.glsl +23 -0
- ShaderFlow/Resources/Shaders/Include/Camera.glsl +166 -0
- ShaderFlow/Resources/Shaders/Include/Complex.glsl +0 -0
- ShaderFlow/Resources/Shaders/Include/ShaderFlow.glsl +444 -0
- ShaderFlow/Resources/Shaders/Vertex/Default.glsl +21 -0
- ShaderFlow/Scene.py +1124 -0
- ShaderFlow/Shader.py +391 -0
- ShaderFlow/Texture.py +528 -0
- ShaderFlow/Variable.py +128 -0
- ShaderFlow/__init__.py +11 -0
- ShaderFlow/__main__.py +22 -0
- shaderflow-0.4.0.dev1.dist-info/METADATA +79 -0
- shaderflow-0.4.0.dev1.dist-info/RECORD +51 -0
- shaderflow-0.4.0.dev1.dist-info/WHEEL +5 -0
- shaderflow-0.4.0.dev1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import io
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from collections import deque
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Callable, Dict, Tuple
|
|
8
|
+
|
|
9
|
+
import cv2
|
|
10
|
+
import numpy
|
|
11
|
+
import PIL
|
|
12
|
+
from attr import Factory, define
|
|
13
|
+
|
|
14
|
+
from Broken import BrokenAttrs, BrokenRelay, BrokenThread, SameTracker, log
|
|
15
|
+
from Broken.Externals.FFmpeg import BrokenFFmpeg
|
|
16
|
+
from Broken.Types import Hertz, Seconds
|
|
17
|
+
from ShaderFlow.Module import ShaderModule
|
|
18
|
+
from ShaderFlow.Texture import ShaderTexture
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@define(slots=False)
|
|
22
|
+
class BrokenSmartVideoFrames(BrokenAttrs):
|
|
23
|
+
path: Path = None
|
|
24
|
+
buffer: Seconds = 60
|
|
25
|
+
threads: int = 6
|
|
26
|
+
quality: int = 95
|
|
27
|
+
time: Seconds = 0
|
|
28
|
+
lossless: bool = True
|
|
29
|
+
_width: int = None
|
|
30
|
+
_height: int = None
|
|
31
|
+
_fps: Hertz = None
|
|
32
|
+
_turbo: Any = None
|
|
33
|
+
_raw: deque = Factory(deque)
|
|
34
|
+
_frames: Dict = Factory(dict)
|
|
35
|
+
|
|
36
|
+
# Dynamically set
|
|
37
|
+
encode: Callable = None
|
|
38
|
+
decode: Callable = None
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def max_raw(self) -> int:
|
|
42
|
+
return self.threads*4
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def width(self) -> int:
|
|
46
|
+
return self._width
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def height(self) -> int:
|
|
50
|
+
return self._height
|
|
51
|
+
|
|
52
|
+
# # Initialization
|
|
53
|
+
|
|
54
|
+
LOSSLESS_MAX_BUFFER_LENGTH = 4
|
|
55
|
+
|
|
56
|
+
def __post__(self):
|
|
57
|
+
self._fps = BrokenFFmpeg.get_video_framerate(self.path)
|
|
58
|
+
self._width, self._height = BrokenFFmpeg.get_video_resolution(self.path)
|
|
59
|
+
|
|
60
|
+
if not all((self._fps, self._width, self._height)):
|
|
61
|
+
raise ValueError("Could not get video metadata")
|
|
62
|
+
|
|
63
|
+
# TurboJPEG will raise if shared lib is not found
|
|
64
|
+
with contextlib.suppress(RuntimeError, ModuleNotFoundError):
|
|
65
|
+
import turbojpeg
|
|
66
|
+
self._turbo = turbojpeg.TurboJPEG()
|
|
67
|
+
|
|
68
|
+
if self.lossless:
|
|
69
|
+
log.warning("Using lossless frames. Limiting buffer length for Out of Memory safety")
|
|
70
|
+
self.buffer = min(self.buffer, BrokenSmartVideoFrames.LOSSLESS_MAX_BUFFER_LENGTH)
|
|
71
|
+
self.encode = lambda frame: frame
|
|
72
|
+
self.decode = lambda frame: frame
|
|
73
|
+
|
|
74
|
+
elif (self._turbo is not None):
|
|
75
|
+
log.success("Using TurboJPEG for compression. Best speeds available")
|
|
76
|
+
self.encode = lambda frame: self._turbo.encode(frame, quality=self.quality)
|
|
77
|
+
self.decode = lambda frame: self._turbo.decode(frame)
|
|
78
|
+
|
|
79
|
+
elif ("cv2" in sys.modules):
|
|
80
|
+
log.success("Using OpenCV for compression. Slower than TurboJPEG but enough")
|
|
81
|
+
self.encode = lambda frame: cv2.imencode(".jpeg", frame)[1]
|
|
82
|
+
self.decode = lambda frame: cv2.imdecode(frame, cv2.IMREAD_COLOR)
|
|
83
|
+
|
|
84
|
+
else:
|
|
85
|
+
log.warning("Using PIL for compression. Performance killer GIL fallback")
|
|
86
|
+
self.decode = lambda frame: PIL.Image.open(io.BytesIO(frame))
|
|
87
|
+
self.encode = lambda frame: PIL.Image.fromarray(frame).save(
|
|
88
|
+
io.BytesIO(), format="jpeg", quality=self.quality
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Create worker threads. The good, the bad and the ugly
|
|
92
|
+
BrokenThread(target=self.extractor, daemon=True)
|
|
93
|
+
BrokenThread(target=self.deleter, daemon=True)
|
|
94
|
+
for _ in range(self.threads):
|
|
95
|
+
BrokenThread(target=self.worker, daemon=True)
|
|
96
|
+
|
|
97
|
+
# # Utilities
|
|
98
|
+
|
|
99
|
+
def time2index(self, time: Seconds) -> int:
|
|
100
|
+
return int(time*self._fps)
|
|
101
|
+
|
|
102
|
+
def index2time(self, index: int) -> Seconds:
|
|
103
|
+
return (index/self._fps)
|
|
104
|
+
|
|
105
|
+
# # Check if we can decode and encode with the libraries
|
|
106
|
+
|
|
107
|
+
def get_frame(self, time: Seconds) -> Tuple[int, numpy.ndarray]:
|
|
108
|
+
want = self.time2index(time)
|
|
109
|
+
self.time = time
|
|
110
|
+
import time
|
|
111
|
+
|
|
112
|
+
# Wait until the frame exists
|
|
113
|
+
while (jpeg := self._frames.get(want)) is None:
|
|
114
|
+
time.sleep(0.01)
|
|
115
|
+
|
|
116
|
+
return (want, lambda: self.decode(jpeg))
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def buffer_frames(self) -> int:
|
|
120
|
+
return int(self.buffer*self._fps)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def time_index(self) -> int:
|
|
124
|
+
return self.time2index(self.time)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def _future_index(self) -> int:
|
|
128
|
+
return self.time_index + self.buffer_frames
|
|
129
|
+
|
|
130
|
+
def _future_window(self, index: int) -> bool:
|
|
131
|
+
return index < self._future_index
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def _past_index(self) -> int:
|
|
135
|
+
return self.time_index - self.buffer_frames
|
|
136
|
+
|
|
137
|
+
def _past_window(self, index: int) -> bool:
|
|
138
|
+
return self._past_index < index
|
|
139
|
+
|
|
140
|
+
def _time_window(self, index: int) -> bool:
|
|
141
|
+
return self._past_window(index) and self._future_window(index)
|
|
142
|
+
|
|
143
|
+
def _should_rewind(self, index: int) -> bool:
|
|
144
|
+
"""Point must be older than the past cutoff to trigger a rewind"""
|
|
145
|
+
return (self.time_index + self.buffer_frames) < index
|
|
146
|
+
|
|
147
|
+
# # Workers
|
|
148
|
+
|
|
149
|
+
_oldest: int = 0
|
|
150
|
+
_newest: int = 0
|
|
151
|
+
|
|
152
|
+
def extractor(self):
|
|
153
|
+
def forward():
|
|
154
|
+
for index, frame in enumerate(BrokenFFmpeg.iter_video_frames(self.path)):
|
|
155
|
+
|
|
156
|
+
# Skip already processed frames
|
|
157
|
+
if self._frames.get(index) is not None:
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
# Skip frames outside of the past time window
|
|
161
|
+
if not self._past_window(index):
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
while not self._future_window(index):
|
|
165
|
+
if self._should_rewind(index):
|
|
166
|
+
return
|
|
167
|
+
time.sleep(0.01)
|
|
168
|
+
|
|
169
|
+
# Limit how much raw frames there can be
|
|
170
|
+
while len(self._raw) > self.max_raw:
|
|
171
|
+
time.sleep(0.01)
|
|
172
|
+
|
|
173
|
+
self._raw.append((index, frame))
|
|
174
|
+
self._newest = max(self._newest, index)
|
|
175
|
+
|
|
176
|
+
while True:
|
|
177
|
+
forward()
|
|
178
|
+
|
|
179
|
+
def worker(self):
|
|
180
|
+
"""Blindly get new frames from the deque, compress and store them"""
|
|
181
|
+
while True:
|
|
182
|
+
try:
|
|
183
|
+
index, frame = self._raw.popleft()
|
|
184
|
+
frame = numpy.array(numpy.flip(frame, axis=0))
|
|
185
|
+
self._frames[index] = self.encode(frame)
|
|
186
|
+
except IndexError:
|
|
187
|
+
time.sleep(0.01)
|
|
188
|
+
|
|
189
|
+
def deleter(self):
|
|
190
|
+
"""Delete old frames that are not in the time window"""
|
|
191
|
+
while True:
|
|
192
|
+
for index in range(self._oldest, self._past_index):
|
|
193
|
+
self._frames[index] = None
|
|
194
|
+
self._oldest = index
|
|
195
|
+
for index in range(self._newest, self._future_index, -1):
|
|
196
|
+
self._frames[index] = None
|
|
197
|
+
self._newest = index
|
|
198
|
+
time.sleep(0.5)
|
|
199
|
+
|
|
200
|
+
# -------------------------------------------------------------------------------------------------|
|
|
201
|
+
|
|
202
|
+
@define
|
|
203
|
+
class ShaderVideo(BrokenSmartVideoFrames, ShaderModule):
|
|
204
|
+
name: str = "iVideo"
|
|
205
|
+
texture: ShaderTexture = None
|
|
206
|
+
|
|
207
|
+
temporal: int = 10
|
|
208
|
+
"""How many """
|
|
209
|
+
|
|
210
|
+
on_frame: BrokenRelay = Factory(BrokenRelay)
|
|
211
|
+
"""Whenever a new video frame is decoded, this attribute is called. Preferably subscribe to
|
|
212
|
+
it with `video.on_frame.subscribe(callable)` or `video.on_frame @ (A, B, C)`, see BokenRelay"""
|
|
213
|
+
|
|
214
|
+
def __post__(self):
|
|
215
|
+
self.texture = ShaderTexture(
|
|
216
|
+
scene=self.scene,
|
|
217
|
+
name=self.name,
|
|
218
|
+
width=self.width,
|
|
219
|
+
height=self.height,
|
|
220
|
+
temporal=self.temporal,
|
|
221
|
+
components=3,
|
|
222
|
+
dtype="f1"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
__same__: SameTracker = Factory(SameTracker)
|
|
226
|
+
|
|
227
|
+
def update(self):
|
|
228
|
+
index, decode = self.get_frame(self.scene.time)
|
|
229
|
+
|
|
230
|
+
if not self.__same__(index):
|
|
231
|
+
image = decode()
|
|
232
|
+
self.texture.roll()
|
|
233
|
+
self.texture.write(image)
|
|
234
|
+
self.on_frame(image)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
import numpy
|
|
5
|
+
from attr import Factory, define
|
|
6
|
+
|
|
7
|
+
from Broken import BrokenEnum, SameTracker, nearest
|
|
8
|
+
from Broken.Types import Hertz, Samples, Seconds
|
|
9
|
+
from ShaderFlow.Module import ShaderModule
|
|
10
|
+
from ShaderFlow.Modules.Audio import BrokenAudio
|
|
11
|
+
from ShaderFlow.Texture import ShaderTexture, TextureType
|
|
12
|
+
from ShaderFlow.Variable import ShaderVariable
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WaveformReducer(BrokenEnum):
|
|
16
|
+
def Average(x: numpy.ndarray) -> numpy.ndarray:
|
|
17
|
+
return numpy.sqrt(numpy.mean(numpy.abs(x), axis=2))
|
|
18
|
+
|
|
19
|
+
def RMS(x: numpy.ndarray) -> numpy.ndarray:
|
|
20
|
+
return numpy.sqrt(numpy.sqrt(numpy.mean(x**2, axis=2))*(2**0.5))
|
|
21
|
+
|
|
22
|
+
def STD(x: numpy.ndarray) -> numpy.ndarray:
|
|
23
|
+
return numpy.sqrt(numpy.std(x, axis=2))
|
|
24
|
+
|
|
25
|
+
@define
|
|
26
|
+
class ShaderWaveform(ShaderModule):
|
|
27
|
+
|
|
28
|
+
name: str = "iWaveform"
|
|
29
|
+
"""Prefix name and Texture name of the Shader Variables"""
|
|
30
|
+
|
|
31
|
+
audio: BrokenAudio = None
|
|
32
|
+
"""Audio class to read the data from"""
|
|
33
|
+
|
|
34
|
+
length: Seconds = 3
|
|
35
|
+
"""Horizontal length of the Waveform content"""
|
|
36
|
+
|
|
37
|
+
samplerate: Hertz = 180
|
|
38
|
+
"""Number of bars per second"""
|
|
39
|
+
|
|
40
|
+
reducer: WaveformReducer = WaveformReducer.Average
|
|
41
|
+
"""How to convert a (channels, length, samples) chunks into (channels, length)"""
|
|
42
|
+
|
|
43
|
+
smooth: bool = True
|
|
44
|
+
"""Enables Linear interpolation on the Texture, not much useful for Bars mode"""
|
|
45
|
+
|
|
46
|
+
texture: ShaderTexture = None
|
|
47
|
+
"""Internal managed Texture"""
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def length_samples(self) -> Samples:
|
|
51
|
+
return int(max(1, self.length*self.scene.fps))
|
|
52
|
+
|
|
53
|
+
def __post__(self):
|
|
54
|
+
self.texture = ShaderTexture(
|
|
55
|
+
scene=self.scene,
|
|
56
|
+
name=self.name,
|
|
57
|
+
height=1,
|
|
58
|
+
mipmaps=False,
|
|
59
|
+
dtype=TextureType.f4,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def chunk_size(self) -> Samples:
|
|
64
|
+
return max(1, int(self.length*self.audio.samplerate/self._points))
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def _points(self) -> int:
|
|
68
|
+
return self.length*self.samplerate
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def _offset(self) -> int:
|
|
72
|
+
return self.audio.tell % self.chunk_size
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def _cutoff(self) -> Samples:
|
|
76
|
+
return nearest(
|
|
77
|
+
number=self.audio.buffer_size,
|
|
78
|
+
multiple=self.chunk_size,
|
|
79
|
+
operator=math.floor,
|
|
80
|
+
type=int,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
_same: SameTracker = Factory(SameTracker)
|
|
84
|
+
|
|
85
|
+
def update(self):
|
|
86
|
+
if self._same(self.audio.tell):
|
|
87
|
+
return
|
|
88
|
+
self.texture.filter = ("linear" if self.smooth else "nearest")
|
|
89
|
+
self.texture.components = self.audio.channels
|
|
90
|
+
self.texture.width = self._points
|
|
91
|
+
start = -int(self.chunk_size*self._points + self._offset + 1)
|
|
92
|
+
end = -int(self._offset + 1)
|
|
93
|
+
chunks = self.audio.data[:, start:end]
|
|
94
|
+
chunks = chunks.reshape(self.audio.channels, -1, self.chunk_size)
|
|
95
|
+
chunks = self.reducer(chunks)
|
|
96
|
+
chunks = numpy.ascontiguousarray(chunks.T)
|
|
97
|
+
self.texture.write(chunks)
|
|
98
|
+
|
|
99
|
+
def pipeline(self) -> Iterable[ShaderVariable]:
|
|
100
|
+
yield ShaderVariable("uniform", "int", f"{self.name}Length", self.length_samples)
|
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg width="32" height="32" version="1" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.91242 0 0 .91242 1.4013026 1.6537216)"><rect style="opacity:.2" width="28" height="29" x="2" y="2" rx="4.1999998" ry="4.1999998"/><rect style="fill:#0286e3;fill-opacity:1" width="28" height="29" x="2" y="1" rx="4.1999998" ry="4.1999998"/><path style="opacity:.1;fill:#fff" d="M6.1992188 1C3.8724189 1 2 2.8724189 2 5.1992188v1C2 3.8724189 3.8724189 2 6.1992188 2H25.800781C28.127581 2 30 3.8724189 30 6.1992188v-1C30 2.8724189 28.127581 1 25.800781 1Z"/><text xml:space="preserve" style="font-size:26.9514px;font-family:'Roboto Slab';-inkscape-font-specification:'Roboto Slab';opacity:.2;fill:#000;fill-opacity:1;stroke:none;stroke-width:42.808;stroke-linejoin:round;stroke-dasharray:none" x="7.6854787" y="26.417761" transform="translate(.21518099 -.33798856)"><tspan x="7.6854787" y="26.417761" style="font-weight:400;fill:#000;fill-opacity:1;stroke-width:42.808;stroke-dasharray:none">S</tspan></text><text xml:space="preserve" style="font-size:26.9514px;font-family:'Roboto Slab';-inkscape-font-specification:'Roboto Slab';fill:#fff;fill-opacity:1;stroke:none;stroke-width:42.8078;stroke-linejoin:round;stroke-dasharray:none" x="7.6843672" y="25.418978" transform="translate(.21518099 -.33798856)"><tspan x="7.6843672" y="25.418978" style="font-style:normal;font-weight:400;fill:#fff;fill-opacity:1;stroke-width:42.8078">S</tspan></text></g></svg>
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import numpy
|
|
5
|
+
from ShaderFlow import SHADERFLOW
|
|
6
|
+
from ShaderFlow.Common.Notes import BrokenPianoNote
|
|
7
|
+
from ShaderFlow.Modules.Audio import ShaderAudio
|
|
8
|
+
from ShaderFlow.Modules.Bouncing import ShaderBouncing
|
|
9
|
+
from ShaderFlow.Modules.Dynamics import ShaderDynamics
|
|
10
|
+
from ShaderFlow.Modules.Noise import ShaderNoise
|
|
11
|
+
from ShaderFlow.Modules.Spectrogram import ShaderSpectrogram
|
|
12
|
+
from ShaderFlow.Modules.Video import ShaderVideo
|
|
13
|
+
from ShaderFlow.Modules.Waveform import ShaderWaveform
|
|
14
|
+
from ShaderFlow.Scene import ShaderScene
|
|
15
|
+
from ShaderFlow.Shader import ShaderObject
|
|
16
|
+
from ShaderFlow.Texture import ShaderTexture, TextureFilter
|
|
17
|
+
from ShaderFlow.Variable import ShaderVariable
|
|
18
|
+
|
|
19
|
+
from Broken import BrokenPath
|
|
20
|
+
|
|
21
|
+
BACKGROUND = "https://w.wallhaven.cc/full/e7/wallhaven-e778vr.jpg"
|
|
22
|
+
|
|
23
|
+
# -------------------------------------------------------------------------------------------------|
|
|
24
|
+
|
|
25
|
+
class Default(ShaderScene):
|
|
26
|
+
"""The most basic ShaderScene, the default shader"""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
# -------------------------------------------------------------------------------------------------|
|
|
30
|
+
|
|
31
|
+
class ShaderToy(ShaderScene):
|
|
32
|
+
"""ShaderToy Default Shader"""
|
|
33
|
+
|
|
34
|
+
def build(self):
|
|
35
|
+
ShaderScene.build(self)
|
|
36
|
+
self.shader.fragment = (self.directory/"GLSL"/"ShaderToy.frag")
|
|
37
|
+
|
|
38
|
+
# -------------------------------------------------------------------------------------------------|
|
|
39
|
+
|
|
40
|
+
class Nested(ShaderScene):
|
|
41
|
+
"""Basic scene with two shaders acting together, main shader referencing the child"""
|
|
42
|
+
__name__ = "Nested Shaders"
|
|
43
|
+
|
|
44
|
+
def build(self):
|
|
45
|
+
ShaderScene.build(self)
|
|
46
|
+
self.child = ShaderObject(scene=self, name="child")
|
|
47
|
+
|
|
48
|
+
# - Left screen is black, right screen is red
|
|
49
|
+
# - Adds content of child shader to final image
|
|
50
|
+
self.shader.fragment = ("""
|
|
51
|
+
void main() {
|
|
52
|
+
fragColor.rgb = vec3(stuv.x, 0, 0);
|
|
53
|
+
fragColor.rgb += texture(child, astuv).rgb;
|
|
54
|
+
fragColor.a = 1;
|
|
55
|
+
}
|
|
56
|
+
""")
|
|
57
|
+
|
|
58
|
+
# Left screen is green, right screen is black
|
|
59
|
+
self.child.fragment = ("""
|
|
60
|
+
void main() {
|
|
61
|
+
fragColor.rgb = vec3(0, 1 - stuv.x, 0);
|
|
62
|
+
fragColor.a = 1;
|
|
63
|
+
}
|
|
64
|
+
""")
|
|
65
|
+
|
|
66
|
+
# -------------------------------------------------------------------------------------------------|
|
|
67
|
+
|
|
68
|
+
class Multipass(ShaderScene):
|
|
69
|
+
"""Many Layers ('Buffers') done on a single shader"""
|
|
70
|
+
__name__ = "Multipass"
|
|
71
|
+
|
|
72
|
+
def build(self):
|
|
73
|
+
ShaderScene.build(self)
|
|
74
|
+
ShaderTexture(scene=self, name="background").from_image(BACKGROUND)
|
|
75
|
+
self.shader.texture.layers = 2
|
|
76
|
+
self.shader.fragment = (self.directory/"GLSL"/"Multipass.frag")
|
|
77
|
+
|
|
78
|
+
# -------------------------------------------------------------------------------------------------|
|
|
79
|
+
|
|
80
|
+
class MotionBlur(ShaderScene):
|
|
81
|
+
"""Poor's man Motion Blur. If you dislike the effect, definitely don't run this"""
|
|
82
|
+
__name__ = "MotionBlur"
|
|
83
|
+
|
|
84
|
+
def build(self):
|
|
85
|
+
ShaderScene.build(self)
|
|
86
|
+
ShaderTexture(scene=self, name="background").from_image(BACKGROUND)
|
|
87
|
+
self.shader.texture.temporal = 10
|
|
88
|
+
self.shader.texture.layers = 2
|
|
89
|
+
self.shader.fragment = (self.directory/"GLSL"/"MotionBlur.frag")
|
|
90
|
+
|
|
91
|
+
# -------------------------------------------------------------------------------------------------|
|
|
92
|
+
|
|
93
|
+
class Dynamics(ShaderScene):
|
|
94
|
+
"""Second order system"""
|
|
95
|
+
__name__ = "Dynamics"
|
|
96
|
+
|
|
97
|
+
def build(self):
|
|
98
|
+
ShaderScene.build(self)
|
|
99
|
+
ShaderTexture(scene=self, name="background").from_image(BACKGROUND)
|
|
100
|
+
self.dynamics = ShaderDynamics(scene=self, name="iShaderDynamics", frequency=4)
|
|
101
|
+
self.shader.fragment = ("""
|
|
102
|
+
void main() {
|
|
103
|
+
vec2 uv = zoom(stuv, 0.85 + 0.1*iShaderDynamics, vec2(0.5));
|
|
104
|
+
fragColor = stexture(background, uv);
|
|
105
|
+
}
|
|
106
|
+
""")
|
|
107
|
+
|
|
108
|
+
def update(self):
|
|
109
|
+
self.dynamics.target = 0.5 * (1 + numpy.sign(numpy.sin(2*math.pi*self.time * 0.5)))
|
|
110
|
+
|
|
111
|
+
# -------------------------------------------------------------------------------------------------|
|
|
112
|
+
|
|
113
|
+
class Noise(ShaderScene):
|
|
114
|
+
"""Basics of Simplex noise"""
|
|
115
|
+
__name__ = "Procedural Noise"
|
|
116
|
+
|
|
117
|
+
def build(self):
|
|
118
|
+
ShaderScene.build(self)
|
|
119
|
+
ShaderTexture(scene=self, name="background").from_image(BACKGROUND)
|
|
120
|
+
self.shake_noise = ShaderNoise(scene=self, name="Shake", dimensions=2)
|
|
121
|
+
self.zoom_noise = ShaderNoise(scene=self, name="Zoom")
|
|
122
|
+
self.shader.fragment = ("""
|
|
123
|
+
void main() {
|
|
124
|
+
vec2 uv = zoom(stuv, 0.95 + 0.02*iZoom, vec2(0.5));
|
|
125
|
+
uv += 0.02 * iShake;
|
|
126
|
+
fragColor = stexture(background, uv);
|
|
127
|
+
}
|
|
128
|
+
""")
|
|
129
|
+
|
|
130
|
+
# -------------------------------------------------------------------------------------------------|
|
|
131
|
+
|
|
132
|
+
class Bouncing(ShaderScene):
|
|
133
|
+
"""Bouncing Logo animation"""
|
|
134
|
+
__name__ = "Bouncing Logo"
|
|
135
|
+
|
|
136
|
+
def build(self):
|
|
137
|
+
ShaderScene.build(self)
|
|
138
|
+
LOGO = SHADERFLOW.RESOURCES.ICON
|
|
139
|
+
self.dvd = ShaderTexture(scene=self, name="logo").from_image(LOGO)
|
|
140
|
+
self.shader.fragment = (self.directory/"GLSL"/"Bouncing.frag")
|
|
141
|
+
self.bounce = ShaderBouncing(scene=self)
|
|
142
|
+
self.bounce.advanced_ratios(LOGO)
|
|
143
|
+
|
|
144
|
+
def update(self):
|
|
145
|
+
self.bounce.aspect_ratio = self.aspect_ratio
|
|
146
|
+
|
|
147
|
+
def pipeline(self):
|
|
148
|
+
yield from ShaderScene.pipeline(self)
|
|
149
|
+
yield ShaderVariable("uniform", "float", "iLogoSize", 0.3)
|
|
150
|
+
|
|
151
|
+
# -------------------------------------------------------------------------------------------------|
|
|
152
|
+
|
|
153
|
+
class Video(ShaderScene):
|
|
154
|
+
"""Video as a Texture demo"""
|
|
155
|
+
__name__ = "Video"
|
|
156
|
+
|
|
157
|
+
def build(self):
|
|
158
|
+
ShaderScene.build(self)
|
|
159
|
+
BUNNY = "https://download.blender.org/demo/movies/BBB/bbb_sunflower_1080p_60fps_normal.mp4.zip"
|
|
160
|
+
self.video = next(BrokenPath.get_external(BUNNY).rglob("*.mp4"))
|
|
161
|
+
ShaderVideo(scene=self, path=self.video)
|
|
162
|
+
self.shader.fragment = (self.directory/"GLSL"/"Video.frag")
|
|
163
|
+
|
|
164
|
+
# -------------------------------------------------------------------------------------------------|
|
|
165
|
+
|
|
166
|
+
class Audio(ShaderScene):
|
|
167
|
+
"""Basic audio processing"""
|
|
168
|
+
__name__ = "Audio"
|
|
169
|
+
|
|
170
|
+
def build(self):
|
|
171
|
+
ShaderScene.build(self)
|
|
172
|
+
self.audio = ShaderAudio(scene=self, name="iAudio")
|
|
173
|
+
self.audio.open_recorder()
|
|
174
|
+
self.shader.fragment = ("""
|
|
175
|
+
void main() {
|
|
176
|
+
fragColor = vec4(vec3(iAudioVolume), 1.0);
|
|
177
|
+
}
|
|
178
|
+
""")
|
|
179
|
+
|
|
180
|
+
# -------------------------------------------------------------------------------------------------|
|
|
181
|
+
|
|
182
|
+
class Waveform(ShaderScene):
|
|
183
|
+
"""Audio Waveform Oscilloscope demo"""
|
|
184
|
+
__name__ = "Waveform"
|
|
185
|
+
|
|
186
|
+
def build(self):
|
|
187
|
+
ShaderScene.build(self)
|
|
188
|
+
self.audio = ShaderAudio(scene=self, name="iAudio", file="/path/to/audio.ogg")
|
|
189
|
+
self.waveform = ShaderWaveform(scene=self, audio=self.audio)
|
|
190
|
+
self.shader.fragment = (self.directory/"GLSL"/"Waveform.frag")
|
|
191
|
+
|
|
192
|
+
# -------------------------------------------------------------------------------------------------|
|
|
193
|
+
|
|
194
|
+
class Bars(ShaderScene):
|
|
195
|
+
"""Basic music bars"""
|
|
196
|
+
__name__ = "Music Bars"
|
|
197
|
+
|
|
198
|
+
def build(self):
|
|
199
|
+
ShaderScene.build(self)
|
|
200
|
+
self.audio = ShaderAudio(scene=self, name="iAudio", file="/path/to/audio.ogg")
|
|
201
|
+
self.spectrogram = ShaderSpectrogram(scene=self, audio=self.audio, length=0)
|
|
202
|
+
self.spectrogram.from_notes(
|
|
203
|
+
start=BrokenPianoNote.from_frequency(20),
|
|
204
|
+
end=BrokenPianoNote.from_frequency(18000),
|
|
205
|
+
piano=True
|
|
206
|
+
)
|
|
207
|
+
self.shader.fragment = (self.directory/"GLSL"/"Bars.frag")
|
|
208
|
+
|
|
209
|
+
# -------------------------------------------------------------------------------------------------|
|
|
210
|
+
|
|
211
|
+
class Visualizer(ShaderScene):
|
|
212
|
+
"""Radial Bars Music Visualizer Scene"""
|
|
213
|
+
__name__ = "Visualizer"
|
|
214
|
+
|
|
215
|
+
def build(self):
|
|
216
|
+
ShaderScene.build(self)
|
|
217
|
+
self.audio = ShaderAudio(scene=self, name="iAudio", file="/path/to/audio.ogg")
|
|
218
|
+
self.waveform = ShaderWaveform(scene=self, audio=self.audio)
|
|
219
|
+
self.spectrogram = ShaderSpectrogram(scene=self, length=0, audio=self.audio, smooth=False)
|
|
220
|
+
self.spectrogram.from_notes(
|
|
221
|
+
start=BrokenPianoNote.from_frequency(20),
|
|
222
|
+
end=BrokenPianoNote.from_frequency(14000),
|
|
223
|
+
piano=True
|
|
224
|
+
)
|
|
225
|
+
ShaderTexture(scene=self, name="background").from_image("https://w.wallhaven.cc/full/ex/wallhaven-ex6kmr.jpg")
|
|
226
|
+
ShaderTexture(scene=self, name="logo").from_image(SHADERFLOW.RESOURCES.ICON)
|
|
227
|
+
self.shader.fragment = (self.directory/"GLSL"/"Visualizer.frag")
|
|
228
|
+
|
|
229
|
+
# -------------------------------------------------------------------------------------------------|
|
|
230
|
+
|
|
231
|
+
class RayMarch(ShaderScene):
|
|
232
|
+
"""Ray Marching demo"""
|
|
233
|
+
__name__ = "Ray Marching"
|
|
234
|
+
|
|
235
|
+
def build(self):
|
|
236
|
+
ShaderScene.build(self)
|
|
237
|
+
self.shader.fragment = (self.directory/"GLSL"/"RayMarch.frag")
|
|
238
|
+
|
|
239
|
+
# -------------------------------------------------------------------------------------------------|
|
|
240
|
+
|
|
241
|
+
class Batch(ShaderScene):
|
|
242
|
+
"""Batch exporting demo. Run with `shaderflow batch -b 1-3 --base /path/to/folder"""
|
|
243
|
+
__name__ = "Batch"
|
|
244
|
+
|
|
245
|
+
def export_name(self, path: Path) -> Path:
|
|
246
|
+
return path.with_stem({
|
|
247
|
+
1: "SubScene A",
|
|
248
|
+
2: "SubScene B",
|
|
249
|
+
3: "SubScene C",
|
|
250
|
+
}[self.export_index])
|
|
251
|
+
|
|
252
|
+
def build(self):
|
|
253
|
+
ShaderScene.build(self)
|
|
254
|
+
|
|
255
|
+
# -------------------------------------------------------------------------------------------------|
|
|
256
|
+
|
|
257
|
+
class Life(ShaderScene):
|
|
258
|
+
"""Conway's Game of Life in GLSL"""
|
|
259
|
+
__name__ = "Game of Life"
|
|
260
|
+
|
|
261
|
+
life_period: int = 6
|
|
262
|
+
"""Number of frames between each life update"""
|
|
263
|
+
|
|
264
|
+
def setup(self):
|
|
265
|
+
width, height = 192, 108
|
|
266
|
+
random = numpy.random.randint(0, 2, (width, height), dtype=bool)
|
|
267
|
+
self.simulation.texture.size = (width, height)
|
|
268
|
+
self.simulation.texture.write(random.astype(numpy.float32), temporal=1)
|
|
269
|
+
|
|
270
|
+
def build(self):
|
|
271
|
+
ShaderScene.build(self)
|
|
272
|
+
self.simulation = ShaderObject(scene=self, name="iLife")
|
|
273
|
+
self.simulation.texture.temporal = 10
|
|
274
|
+
self.simulation.texture.filter = TextureFilter.Nearest
|
|
275
|
+
self.simulation.texture.components = 1
|
|
276
|
+
self.simulation.texture.track = False
|
|
277
|
+
self.simulation.fragment = (self.directory/"GLSL"/"Life/Simulation.glsl")
|
|
278
|
+
self.shader.fragment = (self.directory/"GLSL"/"Life/Visuals.glsl")
|
|
279
|
+
|
|
280
|
+
def pipeline(self):
|
|
281
|
+
yield from ShaderScene.pipeline(self)
|
|
282
|
+
yield ShaderVariable("uniform", "int", "iLifePeriod", self.life_period)
|
|
283
|
+
|
|
284
|
+
# -------------------------------------------------------------------------------------------------|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
// (c) MIT, Tremeschin
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
void main() {
|
|
6
|
+
vec2 intensity = sqrt(texture(iSpectrogram, astuv.yx).xy)/70;
|
|
7
|
+
|
|
8
|
+
if (astuv.y < intensity.x) {
|
|
9
|
+
fragColor.rgb += vec3(1.0, 0.0, 0.0);
|
|
10
|
+
}
|
|
11
|
+
if (astuv.y < intensity.y) {
|
|
12
|
+
fragColor.rgb += vec3(0.0, 1.0, 0.0);
|
|
13
|
+
}
|
|
14
|
+
if (astuv.y < (intensity.y + intensity.x)/2.0) {
|
|
15
|
+
fragColor.rgb += vec3(0.0, 0.0, 1.0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fragColor.rgb += vec3(0.0, 0.0, 0.4*(intensity.x + intensity.y)*(1.0 - astuv.y));
|
|
19
|
+
fragColor.a = 1;
|
|
20
|
+
}
|