yta-video-opengl 0.0.22__py3-none-any.whl → 0.0.24__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.
- yta_video_opengl/editor.py +333 -0
- yta_video_opengl/nodes/__init__.py +32 -28
- yta_video_opengl/nodes/audio/__init__.py +164 -55
- yta_video_opengl/nodes/video/__init__.py +27 -1
- yta_video_opengl/nodes/video/{opengl.py → opengl/__init__.py} +8 -4
- yta_video_opengl/nodes/video/opengl/experimental.py +760 -0
- yta_video_opengl/tests.py +236 -358
- yta_video_opengl/utils.py +9 -421
- {yta_video_opengl-0.0.22.dist-info → yta_video_opengl-0.0.24.dist-info}/METADATA +2 -6
- yta_video_opengl-0.0.24.dist-info/RECORD +13 -0
- yta_video_opengl/audio.py +0 -219
- yta_video_opengl/classes.py +0 -1276
- yta_video_opengl/complete/__init__.py +0 -0
- yta_video_opengl/complete/frame_combinator.py +0 -204
- yta_video_opengl/complete/frame_generator.py +0 -319
- yta_video_opengl/complete/frame_wrapper.py +0 -135
- yta_video_opengl/complete/timeline.py +0 -571
- yta_video_opengl/complete/track/__init__.py +0 -500
- yta_video_opengl/complete/track/media/__init__.py +0 -222
- yta_video_opengl/complete/track/parts.py +0 -267
- yta_video_opengl/complete/track/utils.py +0 -78
- yta_video_opengl/media.py +0 -347
- yta_video_opengl/reader/__init__.py +0 -710
- yta_video_opengl/reader/cache/__init__.py +0 -253
- yta_video_opengl/reader/cache/audio.py +0 -195
- yta_video_opengl/reader/cache/utils.py +0 -48
- yta_video_opengl/reader/cache/video.py +0 -113
- yta_video_opengl/t.py +0 -233
- yta_video_opengl/video.py +0 -277
- yta_video_opengl/writer.py +0 -278
- yta_video_opengl-0.0.22.dist-info/RECORD +0 -31
- {yta_video_opengl-0.0.22.dist-info → yta_video_opengl-0.0.24.dist-info}/LICENSE +0 -0
- {yta_video_opengl-0.0.22.dist-info → yta_video_opengl-0.0.24.dist-info}/WHEEL +0 -0
yta_video_opengl/classes.py
DELETED
@@ -1,1276 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
TODO: Please, rename, refactor and move.
|
3
|
-
|
4
|
-
Opengl doesn't know how to draw a quad
|
5
|
-
or any other complex shape. The basics
|
6
|
-
that opengl can handle are triangles,
|
7
|
-
so we use different triangles to build
|
8
|
-
our shapes (quad normally).
|
9
|
-
"""
|
10
|
-
from yta_validation.parameter import ParameterValidator
|
11
|
-
from yta_validation import PythonValidator
|
12
|
-
from yta_video_opengl.utils import frame_to_texture, get_fullscreen_quad_vao
|
13
|
-
from abc import ABC, abstractmethod
|
14
|
-
from typing import Union
|
15
|
-
|
16
|
-
import av
|
17
|
-
import moderngl
|
18
|
-
import numpy as np
|
19
|
-
|
20
|
-
|
21
|
-
class _Uniforms:
|
22
|
-
"""
|
23
|
-
Class to wrap the functionality related to
|
24
|
-
handling the opengl program uniforms.
|
25
|
-
"""
|
26
|
-
|
27
|
-
@property
|
28
|
-
def uniforms(
|
29
|
-
self
|
30
|
-
) -> dict:
|
31
|
-
"""
|
32
|
-
The uniforms in the program, as a dict, in
|
33
|
-
the format `{key, value}`.
|
34
|
-
"""
|
35
|
-
return {
|
36
|
-
key: self.program[key].value
|
37
|
-
for key in self.program
|
38
|
-
if PythonValidator.is_instance_of(self.program[key], moderngl.Uniform)
|
39
|
-
}
|
40
|
-
|
41
|
-
def __init__(
|
42
|
-
self,
|
43
|
-
program: moderngl.Program
|
44
|
-
):
|
45
|
-
self.program: moderngl.Program = program
|
46
|
-
"""
|
47
|
-
The program instance this handler class
|
48
|
-
belongs to.
|
49
|
-
"""
|
50
|
-
|
51
|
-
def get(
|
52
|
-
self,
|
53
|
-
name: str
|
54
|
-
) -> Union[any, None]:
|
55
|
-
"""
|
56
|
-
Get the value of the uniform with the
|
57
|
-
given 'name'.
|
58
|
-
"""
|
59
|
-
return self.uniforms.get(name, None)
|
60
|
-
|
61
|
-
# TODO: I need to refactor these method to
|
62
|
-
# accept a **kwargs maybe, or to auto-detect
|
63
|
-
# the type and add the uniform as it must be
|
64
|
-
# done
|
65
|
-
def set(
|
66
|
-
self,
|
67
|
-
name: str,
|
68
|
-
value
|
69
|
-
) -> '_Uniforms':
|
70
|
-
"""
|
71
|
-
Set the provided 'value' to the normal type
|
72
|
-
uniform with the given 'name'. Here you have
|
73
|
-
some examples of defined uniforms we can set
|
74
|
-
with this method:
|
75
|
-
- `uniform float name;`
|
76
|
-
|
77
|
-
TODO: Add more examples
|
78
|
-
"""
|
79
|
-
if name in self.program:
|
80
|
-
self.program[name].value = value
|
81
|
-
|
82
|
-
return self
|
83
|
-
|
84
|
-
def set_vec(
|
85
|
-
self,
|
86
|
-
name: str,
|
87
|
-
values
|
88
|
-
) -> '_Uniforms':
|
89
|
-
"""
|
90
|
-
Set the provided 'value' to the normal type
|
91
|
-
uniform with the given 'name'. Here you have
|
92
|
-
some examples of defined uniforms we can set
|
93
|
-
with this method:
|
94
|
-
- `uniform vec2 name;`
|
95
|
-
|
96
|
-
TODO: Is this example ok? I didn't use it yet
|
97
|
-
"""
|
98
|
-
if name in self.program:
|
99
|
-
self.program[name].write(np.array(values, dtype = 'f4').tobytes())
|
100
|
-
|
101
|
-
return self
|
102
|
-
|
103
|
-
def set_mat(
|
104
|
-
self,
|
105
|
-
name: str,
|
106
|
-
value
|
107
|
-
) -> '_Uniforms':
|
108
|
-
"""
|
109
|
-
Set the provided 'value' to a `matN` type
|
110
|
-
uniform with the given 'name'. The 'value'
|
111
|
-
must be a NxN matrix (maybe numpy array)
|
112
|
-
transformed to bytes ('.tobytes()').
|
113
|
-
|
114
|
-
This uniform must be defined in the vertex
|
115
|
-
like this:
|
116
|
-
- `uniform matN name;`
|
117
|
-
|
118
|
-
TODO: Maybe we can accept a NxN numpy
|
119
|
-
array and do the .tobytes() by ourselves...
|
120
|
-
"""
|
121
|
-
if name in self.program:
|
122
|
-
self.program[name].write(value)
|
123
|
-
|
124
|
-
return self
|
125
|
-
|
126
|
-
def print(
|
127
|
-
self
|
128
|
-
) -> '_Uniforms':
|
129
|
-
"""
|
130
|
-
Print the defined uniforms in console.
|
131
|
-
"""
|
132
|
-
for key, value in self.uniforms.items():
|
133
|
-
print(f'"{key}": {str(value)}')
|
134
|
-
|
135
|
-
# TODO: Moved to 'nodes.opengl.py'
|
136
|
-
class BaseNode:
|
137
|
-
"""
|
138
|
-
The basic class of a node to manipulate frames
|
139
|
-
as opengl textures. This node will process the
|
140
|
-
frame as an input texture and will generate
|
141
|
-
also a texture as the output.
|
142
|
-
|
143
|
-
Nodes can be chained and the result from one
|
144
|
-
node can be applied on another node.
|
145
|
-
"""
|
146
|
-
|
147
|
-
@property
|
148
|
-
@abstractmethod
|
149
|
-
def vertex_shader(
|
150
|
-
self
|
151
|
-
) -> str:
|
152
|
-
"""
|
153
|
-
The code of the vertex shader.
|
154
|
-
"""
|
155
|
-
pass
|
156
|
-
|
157
|
-
@property
|
158
|
-
@abstractmethod
|
159
|
-
def fragment_shader(
|
160
|
-
self
|
161
|
-
) -> str:
|
162
|
-
"""
|
163
|
-
The code of the fragment shader.
|
164
|
-
"""
|
165
|
-
pass
|
166
|
-
|
167
|
-
def __init__(
|
168
|
-
self,
|
169
|
-
context: moderngl.Context,
|
170
|
-
size: tuple[int, int],
|
171
|
-
**kwargs
|
172
|
-
):
|
173
|
-
ParameterValidator.validate_mandatory_instance_of('context', context, moderngl.Context)
|
174
|
-
# TODO: Validate size
|
175
|
-
|
176
|
-
self.context: moderngl.Context = context
|
177
|
-
"""
|
178
|
-
The context of the program.
|
179
|
-
"""
|
180
|
-
self.size: tuple[int, int] = size
|
181
|
-
"""
|
182
|
-
The size we want to use for the frame buffer
|
183
|
-
in a (width, height) format.
|
184
|
-
"""
|
185
|
-
# Compile shaders within the program
|
186
|
-
self.program: moderngl.Program = self.context.program(
|
187
|
-
vertex_shader = self.vertex_shader,
|
188
|
-
fragment_shader = self.fragment_shader
|
189
|
-
)
|
190
|
-
|
191
|
-
# Create the fullscreen quad
|
192
|
-
self.quad = get_fullscreen_quad_vao(
|
193
|
-
context = self.context,
|
194
|
-
program = self.program
|
195
|
-
)
|
196
|
-
|
197
|
-
# Create the output fbo
|
198
|
-
self.output_tex = self.context.texture(self.size, 4)
|
199
|
-
self.output_tex.filter = (moderngl.LINEAR, moderngl.LINEAR)
|
200
|
-
self.fbo = self.context.framebuffer(color_attachments = [self.output_tex])
|
201
|
-
|
202
|
-
self.uniforms: _Uniforms = _Uniforms(self.program)
|
203
|
-
"""
|
204
|
-
Shortcut to the uniforms functionality.
|
205
|
-
"""
|
206
|
-
# Auto set uniforms dynamically if existing
|
207
|
-
for key, value in kwargs.items():
|
208
|
-
self.uniforms.set(key, value)
|
209
|
-
|
210
|
-
def process(
|
211
|
-
self,
|
212
|
-
input: Union[moderngl.Texture, 'VideoFrame', 'np.ndarray']
|
213
|
-
) -> moderngl.Texture:
|
214
|
-
"""
|
215
|
-
Apply the shader to the 'input', that
|
216
|
-
must be a frame or a texture, and return
|
217
|
-
the new resulting texture.
|
218
|
-
|
219
|
-
We use and return textures to maintain
|
220
|
-
the process in GPU and optimize it.
|
221
|
-
"""
|
222
|
-
# TODO: Maybe we can accept a VideoFrame
|
223
|
-
# or a numpy array and transform it here
|
224
|
-
# into a texture, ready to be used:
|
225
|
-
# frame_to_texture(
|
226
|
-
# # TODO: Do not use Pillow
|
227
|
-
# frame = np.array(Image.open("input.jpg").convert("RGBA")),
|
228
|
-
# context = self.context,
|
229
|
-
# numpy_format = 'rgba'
|
230
|
-
# )
|
231
|
-
if PythonValidator.is_instance_of(input, ['VideoFrame', 'ndarray']):
|
232
|
-
# TODO: What about the numpy format (?)
|
233
|
-
input = frame_to_texture(input, self.context)
|
234
|
-
|
235
|
-
self.fbo.use()
|
236
|
-
self.context.clear(0.0, 0.0, 0.0, 0.0)
|
237
|
-
|
238
|
-
input.use(location = 0)
|
239
|
-
|
240
|
-
if 'texture' in self.program:
|
241
|
-
self.program['texture'] = 0
|
242
|
-
|
243
|
-
self.quad.render()
|
244
|
-
|
245
|
-
return self.output_tex
|
246
|
-
|
247
|
-
class WavingNode(BaseNode):
|
248
|
-
"""
|
249
|
-
Just an example, without the shaders code
|
250
|
-
actually, to indicate that we can use
|
251
|
-
custom parameters to make it work.
|
252
|
-
"""
|
253
|
-
|
254
|
-
@property
|
255
|
-
def vertex_shader(
|
256
|
-
self
|
257
|
-
) -> str:
|
258
|
-
return (
|
259
|
-
'''
|
260
|
-
#version 330
|
261
|
-
in vec2 in_vert;
|
262
|
-
in vec2 in_texcoord;
|
263
|
-
out vec2 v_uv;
|
264
|
-
void main() {
|
265
|
-
v_uv = in_texcoord;
|
266
|
-
gl_Position = vec4(in_vert, 0.0, 1.0);
|
267
|
-
}
|
268
|
-
'''
|
269
|
-
)
|
270
|
-
|
271
|
-
@property
|
272
|
-
def fragment_shader(
|
273
|
-
self
|
274
|
-
) -> str:
|
275
|
-
return (
|
276
|
-
'''
|
277
|
-
#version 330
|
278
|
-
uniform sampler2D tex;
|
279
|
-
uniform float time;
|
280
|
-
uniform float amplitude;
|
281
|
-
uniform float frequency;
|
282
|
-
uniform float speed;
|
283
|
-
in vec2 v_uv;
|
284
|
-
out vec4 f_color;
|
285
|
-
void main() {
|
286
|
-
float wave = sin(v_uv.x * frequency + time * speed) * amplitude;
|
287
|
-
vec2 uv = vec2(v_uv.x, v_uv.y + wave);
|
288
|
-
f_color = texture(tex, uv);
|
289
|
-
}
|
290
|
-
'''
|
291
|
-
)
|
292
|
-
|
293
|
-
def __init__(
|
294
|
-
self,
|
295
|
-
context: moderngl.Context,
|
296
|
-
size: tuple[int, int],
|
297
|
-
amplitude: float = 0.05,
|
298
|
-
frequency: float = 10.0,
|
299
|
-
speed: float = 2.0
|
300
|
-
):
|
301
|
-
super().__init__(
|
302
|
-
context = context,
|
303
|
-
size = size,
|
304
|
-
amplitude = amplitude,
|
305
|
-
frequency = frequency,
|
306
|
-
speed = speed
|
307
|
-
)
|
308
|
-
|
309
|
-
# This is just an example and we are not
|
310
|
-
# using the parameters actually, but we
|
311
|
-
# could set those specific uniforms to be
|
312
|
-
# processed by the code
|
313
|
-
def process(
|
314
|
-
self,
|
315
|
-
input: Union[moderngl.Texture, 'VideoFrame', 'np.ndarray'],
|
316
|
-
t: float = 0.0,
|
317
|
-
) -> moderngl.Texture:
|
318
|
-
"""
|
319
|
-
Apply the shader to the 'input', that
|
320
|
-
must be a frame or a texture, and return
|
321
|
-
the new resulting texture.
|
322
|
-
|
323
|
-
We use and return textures to maintain
|
324
|
-
the process in GPU and optimize it.
|
325
|
-
"""
|
326
|
-
self.uniforms.set('time', t)
|
327
|
-
|
328
|
-
return super().process(input)
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
"""
|
333
|
-
TODO: I should try to use the Node classes
|
334
|
-
to manipulate the frames because this is how
|
335
|
-
Davinci Resolve and other editors work.
|
336
|
-
"""
|
337
|
-
|
338
|
-
|
339
|
-
class FrameShaderBase(ABC):
|
340
|
-
"""
|
341
|
-
Class to be inherited by any of our own
|
342
|
-
custom opengl program classes.
|
343
|
-
|
344
|
-
This shader base class must be used by all
|
345
|
-
the classes that are modifying the frames
|
346
|
-
one by one.
|
347
|
-
"""
|
348
|
-
|
349
|
-
@property
|
350
|
-
@abstractmethod
|
351
|
-
def vertex_shader(
|
352
|
-
self
|
353
|
-
) -> str:
|
354
|
-
"""
|
355
|
-
Source code of the vertex shader.
|
356
|
-
"""
|
357
|
-
pass
|
358
|
-
|
359
|
-
@property
|
360
|
-
@abstractmethod
|
361
|
-
def fragment_shader(
|
362
|
-
self
|
363
|
-
) -> str:
|
364
|
-
"""
|
365
|
-
Source code of the fragment shader.
|
366
|
-
"""
|
367
|
-
pass
|
368
|
-
|
369
|
-
def __init__(
|
370
|
-
self,
|
371
|
-
size: tuple[int, int],
|
372
|
-
first_frame: Union['VideoFrame', 'np.ndarray'],
|
373
|
-
context: Union[moderngl.Context, None] = None,
|
374
|
-
):
|
375
|
-
context = (
|
376
|
-
moderngl.create_context(standalone = True)
|
377
|
-
if context is None else
|
378
|
-
context
|
379
|
-
)
|
380
|
-
|
381
|
-
self.size: tuple[int, int] = size
|
382
|
-
"""
|
383
|
-
The size we want to use for the frame buffer
|
384
|
-
in a (width, height) format.
|
385
|
-
"""
|
386
|
-
self.first_frame: Union['VideoFrame', 'np.ndarray'] = first_frame
|
387
|
-
"""
|
388
|
-
The first frame of the video in which we will
|
389
|
-
apply the effect. Needed to build the texture.
|
390
|
-
"""
|
391
|
-
self.context: moderngl.Context = context
|
392
|
-
"""
|
393
|
-
The context of the program.
|
394
|
-
"""
|
395
|
-
self.program: moderngl.Program = None
|
396
|
-
"""
|
397
|
-
The opengl program.
|
398
|
-
"""
|
399
|
-
self.fbo: moderngl.Framebuffer = None
|
400
|
-
"""
|
401
|
-
The frame buffer object.
|
402
|
-
"""
|
403
|
-
self.uniforms: _Uniforms = None
|
404
|
-
"""
|
405
|
-
Shortcut to the uniforms functionality.
|
406
|
-
"""
|
407
|
-
|
408
|
-
self._initialize_program()
|
409
|
-
|
410
|
-
def _initialize_program(
|
411
|
-
self
|
412
|
-
):
|
413
|
-
"""
|
414
|
-
This method is to allow the effects to
|
415
|
-
change their '__init__' method to be able
|
416
|
-
to provide parameters that will be set as
|
417
|
-
uniforms.
|
418
|
-
"""
|
419
|
-
# Compile shaders within the program
|
420
|
-
self.program: moderngl.Program = self.context.program(
|
421
|
-
vertex_shader = self.vertex_shader,
|
422
|
-
fragment_shader = self.fragment_shader
|
423
|
-
)
|
424
|
-
|
425
|
-
# Create frame buffer
|
426
|
-
self.fbo = self.context.simple_framebuffer(self.size)
|
427
|
-
# Create quad vertex array
|
428
|
-
self.vao: moderngl.VertexArray = get_fullscreen_quad_vao(self.context, self.program)
|
429
|
-
self.uniforms: _Uniforms = _Uniforms(self.program)
|
430
|
-
|
431
|
-
# TODO: How do I manage these textures (?)
|
432
|
-
self.textures = {}
|
433
|
-
|
434
|
-
# TODO: Should we do this here (?)
|
435
|
-
texture: moderngl.Texture = frame_to_texture(self.first_frame, self.context)
|
436
|
-
texture.build_mipmaps()
|
437
|
-
|
438
|
-
# TODO: I'm not using this method, but sounds
|
439
|
-
# interesting to simplify the 'process_frame'
|
440
|
-
# method in different mini actions
|
441
|
-
def load_texture(
|
442
|
-
self,
|
443
|
-
image: np.ndarray,
|
444
|
-
uniform_name: str,
|
445
|
-
texture_unit = 0
|
446
|
-
):
|
447
|
-
"""
|
448
|
-
Load a texture with the given 'image' and set
|
449
|
-
it to the uniform with the given 'uniform_name'.
|
450
|
-
|
451
|
-
TODO: Understand better the 'texture_unit'
|
452
|
-
"""
|
453
|
-
# This is to receive a path (str) to an image
|
454
|
-
#img = Image.open(path).transpose(Image.FLIP_TOP_BOTTOM).convert("RGBA")
|
455
|
-
image = np.flipud(image)
|
456
|
-
tex = self.context.texture((image.shape[1], image.shape[0]), 4, image.tobytes())
|
457
|
-
tex.use(texture_unit)
|
458
|
-
self.textures[uniform_name] = tex
|
459
|
-
self.uniforms.set(uniform_name, texture_unit)
|
460
|
-
|
461
|
-
@abstractmethod
|
462
|
-
def _prepare_frame(
|
463
|
-
self,
|
464
|
-
t: float
|
465
|
-
):
|
466
|
-
"""
|
467
|
-
Set the uniforms we need to process that
|
468
|
-
specific frame and the code to calculate
|
469
|
-
those uniforms we need.
|
470
|
-
"""
|
471
|
-
pass
|
472
|
-
|
473
|
-
def process_frame(
|
474
|
-
self,
|
475
|
-
frame: Union['VideoFrame', np.ndarray],
|
476
|
-
t: float,
|
477
|
-
numpy_format: str = 'rgb24'
|
478
|
-
) -> 'VideoFrame':
|
479
|
-
# TODO: This method accepts 'np.ndarray' to
|
480
|
-
# prepare it to frames coming from other source
|
481
|
-
# different than reading a video here (that
|
482
|
-
# will be processed as VideoFrame). Check the
|
483
|
-
# sizes and [0], [1] indexes.
|
484
|
-
ParameterValidator.validate_mandatory_instance_of('frame', frame, ['VideoFrame', 'ndarray'])
|
485
|
-
|
486
|
-
# By now I call this here because I don't need
|
487
|
-
# to send nothing specific when calculating the
|
488
|
-
# frame...
|
489
|
-
self._prepare_frame(t)
|
490
|
-
|
491
|
-
# Set frame as a texture
|
492
|
-
texture = frame_to_texture(frame, self.context, numpy_format)
|
493
|
-
# TODO: Why 0 (?)
|
494
|
-
#texture.use(0)
|
495
|
-
texture.use()
|
496
|
-
|
497
|
-
# # TODO: Check this
|
498
|
-
# if 'u_texture' in self.program:
|
499
|
-
# self.program['u_texture'].value = 0
|
500
|
-
|
501
|
-
# Set the frame buffer a a whole black frame
|
502
|
-
self.context.clear(0.0, 0.0, 0.0)
|
503
|
-
# TODO: No 'self.fbo.use()' here (?)
|
504
|
-
self.fbo.use()
|
505
|
-
self.vao.render(moderngl.TRIANGLE_STRIP)
|
506
|
-
|
507
|
-
# Read output of fbo
|
508
|
-
output = np.flipud(
|
509
|
-
np.frombuffer(
|
510
|
-
self.fbo.read(components = 3, alignment = 1),
|
511
|
-
dtype = np.uint8
|
512
|
-
).reshape((texture.size[1], texture.size[0], 3))
|
513
|
-
#).reshape((self.size[1], self.size[0], 3))
|
514
|
-
)
|
515
|
-
|
516
|
-
# We want a VideoFrame instance because we
|
517
|
-
# we can send it directly to the mux to
|
518
|
-
# write
|
519
|
-
output: 'VideoFrame' = av.VideoFrame.from_ndarray(output, format = numpy_format)
|
520
|
-
|
521
|
-
return output
|
522
|
-
|
523
|
-
# Example classes below
|
524
|
-
class WavingFrame(FrameShaderBase):
|
525
|
-
"""
|
526
|
-
The frame but waving as a flag.
|
527
|
-
"""
|
528
|
-
|
529
|
-
@property
|
530
|
-
def vertex_shader(
|
531
|
-
self
|
532
|
-
) -> str:
|
533
|
-
return (
|
534
|
-
'''
|
535
|
-
#version 330
|
536
|
-
in vec2 in_vert;
|
537
|
-
in vec2 in_texcoord;
|
538
|
-
out vec2 v_uv;
|
539
|
-
void main() {
|
540
|
-
v_uv = in_texcoord;
|
541
|
-
gl_Position = vec4(in_vert, 0.0, 1.0);
|
542
|
-
}
|
543
|
-
'''
|
544
|
-
)
|
545
|
-
|
546
|
-
@property
|
547
|
-
def fragment_shader(
|
548
|
-
self
|
549
|
-
) -> str:
|
550
|
-
return (
|
551
|
-
'''
|
552
|
-
#version 330
|
553
|
-
uniform sampler2D tex;
|
554
|
-
uniform float time;
|
555
|
-
uniform float amp;
|
556
|
-
uniform float freq;
|
557
|
-
uniform float speed;
|
558
|
-
in vec2 v_uv;
|
559
|
-
out vec4 f_color;
|
560
|
-
void main() {
|
561
|
-
float wave = sin(v_uv.x * freq + time * speed) * amp;
|
562
|
-
vec2 uv = vec2(v_uv.x, v_uv.y + wave);
|
563
|
-
f_color = texture(tex, uv);
|
564
|
-
}
|
565
|
-
'''
|
566
|
-
)
|
567
|
-
|
568
|
-
def __init__(
|
569
|
-
self,
|
570
|
-
size,
|
571
|
-
first_frame,
|
572
|
-
context = None,
|
573
|
-
amplitude: float = 0.05,
|
574
|
-
frequency: float = 10.0,
|
575
|
-
speed: float = 2.0
|
576
|
-
):
|
577
|
-
super().__init__(size, first_frame, context)
|
578
|
-
|
579
|
-
# TODO: Use automatic way of detecting the
|
580
|
-
# parameters that are not 'self', 'size',
|
581
|
-
# 'first_frame' nor 'context' and set those
|
582
|
-
# as uniforms automatically
|
583
|
-
|
584
|
-
self.uniforms.set('amp', amplitude)
|
585
|
-
self.uniforms.set('freq', frequency)
|
586
|
-
self.uniforms.set('speed', speed)
|
587
|
-
|
588
|
-
def _prepare_frame(
|
589
|
-
self,
|
590
|
-
t: float
|
591
|
-
) -> 'WavingFrame':
|
592
|
-
"""
|
593
|
-
Precalculate all the things we need to process
|
594
|
-
a frame, like the uniforms, etc.
|
595
|
-
"""
|
596
|
-
self.uniforms.set('time', t)
|
597
|
-
|
598
|
-
return self
|
599
|
-
|
600
|
-
class BreathingFrame(FrameShaderBase):
|
601
|
-
"""
|
602
|
-
The frame but as if it was breathing.
|
603
|
-
"""
|
604
|
-
|
605
|
-
@property
|
606
|
-
def vertex_shader(
|
607
|
-
self
|
608
|
-
) -> str:
|
609
|
-
return (
|
610
|
-
'''
|
611
|
-
#version 330
|
612
|
-
in vec2 in_vert;
|
613
|
-
in vec2 in_texcoord;
|
614
|
-
out vec2 v_text;
|
615
|
-
void main() {
|
616
|
-
gl_Position = vec4(in_vert, 0.0, 1.0);
|
617
|
-
v_text = in_texcoord;
|
618
|
-
}
|
619
|
-
'''
|
620
|
-
)
|
621
|
-
|
622
|
-
@property
|
623
|
-
def fragment_shader(
|
624
|
-
self
|
625
|
-
) -> str:
|
626
|
-
return (
|
627
|
-
'''
|
628
|
-
#version 330
|
629
|
-
uniform sampler2D tex;
|
630
|
-
uniform float time;
|
631
|
-
in vec2 v_text;
|
632
|
-
out vec4 f_color;
|
633
|
-
// Use uniforms to be customizable
|
634
|
-
|
635
|
-
void main() {
|
636
|
-
// Dynamic zoom scaled with t
|
637
|
-
float scale = 1.0 + 0.05 * sin(time * 2.0); // 5% de zoom
|
638
|
-
vec2 center = vec2(0.5, 0.5);
|
639
|
-
|
640
|
-
// Recalculate coords according to center
|
641
|
-
vec2 uv = (v_text - center) / scale + center;
|
642
|
-
|
643
|
-
// Clamp to avoid artifacts
|
644
|
-
uv = clamp(uv, 0.0, 1.0);
|
645
|
-
|
646
|
-
f_color = texture(tex, uv);
|
647
|
-
}
|
648
|
-
'''
|
649
|
-
)
|
650
|
-
|
651
|
-
def _prepare_frame(
|
652
|
-
self,
|
653
|
-
t: float
|
654
|
-
) -> 'BreathingFrame':
|
655
|
-
# TODO: Use automatic way of detecting the
|
656
|
-
# parameters that are not 'self', 'size',
|
657
|
-
# 'first_frame' nor 'context' and set those
|
658
|
-
# as uniforms automatically
|
659
|
-
|
660
|
-
self.uniforms.set('time', t)
|
661
|
-
|
662
|
-
return self
|
663
|
-
|
664
|
-
class HandheldFrame(FrameShaderBase):
|
665
|
-
"""
|
666
|
-
The frame but as if it was being recorder by
|
667
|
-
someone holding a camera, that is not 100%
|
668
|
-
stable.
|
669
|
-
"""
|
670
|
-
|
671
|
-
@property
|
672
|
-
def vertex_shader(
|
673
|
-
self
|
674
|
-
) -> str:
|
675
|
-
return (
|
676
|
-
'''
|
677
|
-
#version 330
|
678
|
-
in vec2 in_vert;
|
679
|
-
in vec2 in_texcoord;
|
680
|
-
out vec2 v_text;
|
681
|
-
|
682
|
-
uniform mat3 transform;
|
683
|
-
|
684
|
-
void main() {
|
685
|
-
vec3 pos = vec3(in_vert, 1.0);
|
686
|
-
pos = transform * pos;
|
687
|
-
gl_Position = vec4(pos.xy, 0.0, 1.0);
|
688
|
-
v_text = in_texcoord;
|
689
|
-
}
|
690
|
-
'''
|
691
|
-
)
|
692
|
-
|
693
|
-
@property
|
694
|
-
def fragment_shader(
|
695
|
-
self
|
696
|
-
) -> str:
|
697
|
-
return (
|
698
|
-
'''
|
699
|
-
#version 330
|
700
|
-
uniform sampler2D tex;
|
701
|
-
in vec2 v_text;
|
702
|
-
out vec4 f_color;
|
703
|
-
|
704
|
-
void main() {
|
705
|
-
f_color = texture(tex, v_text);
|
706
|
-
}
|
707
|
-
'''
|
708
|
-
)
|
709
|
-
|
710
|
-
def _prepare_frame(
|
711
|
-
self,
|
712
|
-
t: float
|
713
|
-
) -> 'HandheldFrame':
|
714
|
-
import math
|
715
|
-
def handheld_matrix_exaggerated(t):
|
716
|
-
# Rotación más notoria
|
717
|
-
angle = smooth_noise(t, freq=0.8, scale=0.05) # antes 0.02
|
718
|
-
|
719
|
-
# Traslaciones más grandes
|
720
|
-
tx = smooth_noise(t, freq=1.1, scale=0.04) # antes 0.015
|
721
|
-
ty = smooth_noise(t, freq=1.4, scale=0.04)
|
722
|
-
|
723
|
-
# Zoom más agresivo
|
724
|
-
zoom = 1.0 + smooth_noise(t, freq=0.5, scale=0.06) # antes 0.02
|
725
|
-
|
726
|
-
cos_a, sin_a = math.cos(angle), math.sin(angle)
|
727
|
-
|
728
|
-
return np.array([
|
729
|
-
[ cos_a * zoom, -sin_a * zoom, tx],
|
730
|
-
[ sin_a * zoom, cos_a * zoom, ty],
|
731
|
-
[ 0.0, 0.0, 1.0]
|
732
|
-
], dtype="f4")
|
733
|
-
|
734
|
-
def smooth_noise(t, freq=1.5, scale=1.0):
|
735
|
-
"""Pequeño ruido orgánico usando senos y cosenos mezclados"""
|
736
|
-
return (
|
737
|
-
math.sin(t * freq) +
|
738
|
-
0.5 * math.cos(t * freq * 0.5 + 1.7) +
|
739
|
-
0.25 * math.sin(t * freq * 0.25 + 2.5)
|
740
|
-
) * scale
|
741
|
-
|
742
|
-
def handheld_matrix(t):
|
743
|
-
# Rotación ligera (en radianes)
|
744
|
-
angle = smooth_noise(t, freq=0.8, scale=0.02)
|
745
|
-
|
746
|
-
# Traslación horizontal/vertical
|
747
|
-
tx = smooth_noise(t, freq=1.1, scale=0.015)
|
748
|
-
ty = smooth_noise(t, freq=1.4, scale=0.015)
|
749
|
-
|
750
|
-
# Zoom (escala)
|
751
|
-
zoom = 1.0 + smooth_noise(t, freq=0.5, scale=0.02)
|
752
|
-
|
753
|
-
cos_a, sin_a = math.cos(angle), math.sin(angle)
|
754
|
-
|
755
|
-
# Matriz de transformación: Zoom * Rotación + Traslación
|
756
|
-
return np.array([
|
757
|
-
[ cos_a * zoom, -sin_a * zoom, tx],
|
758
|
-
[ sin_a * zoom, cos_a * zoom, ty],
|
759
|
-
[ 0.0, 0.0, 1.0]
|
760
|
-
], dtype = "f4")
|
761
|
-
|
762
|
-
self.uniforms.set_mat('transform', handheld_matrix_exaggerated(t).tobytes())
|
763
|
-
|
764
|
-
return self
|
765
|
-
|
766
|
-
class OrbitingFrame(FrameShaderBase):
|
767
|
-
"""
|
768
|
-
The frame but orbiting around the camera.
|
769
|
-
"""
|
770
|
-
|
771
|
-
@property
|
772
|
-
def vertex_shader(
|
773
|
-
self
|
774
|
-
) -> str:
|
775
|
-
return (
|
776
|
-
'''
|
777
|
-
#version 330
|
778
|
-
|
779
|
-
in vec2 in_vert;
|
780
|
-
in vec2 in_texcoord;
|
781
|
-
|
782
|
-
out vec2 v_uv;
|
783
|
-
|
784
|
-
uniform mat4 mvp; // Model-View-Projection matrix
|
785
|
-
|
786
|
-
void main() {
|
787
|
-
v_uv = in_texcoord;
|
788
|
-
// El quad está en XY, lo pasamos a XYZ con z=0
|
789
|
-
vec4 pos = vec4(in_vert, 0.0, 1.0);
|
790
|
-
gl_Position = mvp * pos;
|
791
|
-
}
|
792
|
-
'''
|
793
|
-
)
|
794
|
-
|
795
|
-
@property
|
796
|
-
def fragment_shader(
|
797
|
-
self
|
798
|
-
) -> str:
|
799
|
-
return (
|
800
|
-
'''
|
801
|
-
#version 330
|
802
|
-
|
803
|
-
uniform sampler2D tex;
|
804
|
-
in vec2 v_uv;
|
805
|
-
out vec4 f_color;
|
806
|
-
|
807
|
-
void main() {
|
808
|
-
f_color = texture(tex, v_uv);
|
809
|
-
}
|
810
|
-
'''
|
811
|
-
)
|
812
|
-
|
813
|
-
def _prepare_frame(
|
814
|
-
self,
|
815
|
-
t: float
|
816
|
-
) -> 'OrbitingFrame':
|
817
|
-
def perspective(fov_y_rad, aspect, near, far):
|
818
|
-
f = 1.0 / np.tan(fov_y_rad / 2.0)
|
819
|
-
m = np.zeros((4,4), dtype='f4')
|
820
|
-
m[0,0] = f / aspect
|
821
|
-
m[1,1] = f
|
822
|
-
m[2,2] = (far + near) / (near - far)
|
823
|
-
m[2,3] = (2 * far * near) / (near - far)
|
824
|
-
m[3,2] = -1.0
|
825
|
-
return m
|
826
|
-
|
827
|
-
def look_at(eye, target, up=(0,1,0)):
|
828
|
-
eye = np.array(eye, dtype='f4')
|
829
|
-
target = np.array(target, dtype='f4')
|
830
|
-
up = np.array(up, dtype='f4')
|
831
|
-
|
832
|
-
f = target - eye
|
833
|
-
f = f / np.linalg.norm(f)
|
834
|
-
s = np.cross(f, up)
|
835
|
-
s = s / np.linalg.norm(s)
|
836
|
-
u = np.cross(s, f)
|
837
|
-
|
838
|
-
m = np.eye(4, dtype='f4')
|
839
|
-
m[0,0:3] = s
|
840
|
-
m[1,0:3] = u
|
841
|
-
m[2,0:3] = -f
|
842
|
-
m[0,3] = -np.dot(s, eye)
|
843
|
-
m[1,3] = -np.dot(u, eye)
|
844
|
-
m[2,3] = np.dot(f, eye)
|
845
|
-
return m
|
846
|
-
|
847
|
-
def translate(x, y, z):
|
848
|
-
m = np.eye(4, dtype='f4')
|
849
|
-
m[0,3] = x
|
850
|
-
m[1,3] = y
|
851
|
-
m[2,3] = z
|
852
|
-
return m
|
853
|
-
|
854
|
-
def rotate_y(angle):
|
855
|
-
c, s = np.cos(angle), np.sin(angle)
|
856
|
-
m = np.eye(4, dtype='f4')
|
857
|
-
m[0,0], m[0,2] = c, s
|
858
|
-
m[2,0], m[2,2] = -s, c
|
859
|
-
return m
|
860
|
-
|
861
|
-
def scale_uniform(k):
|
862
|
-
m = np.eye(4, dtype='f4')
|
863
|
-
m[0,0] = m[1,1] = m[2,2] = k
|
864
|
-
return m
|
865
|
-
|
866
|
-
def carousel_mvp(t, *,
|
867
|
-
aspect,
|
868
|
-
fov_deg=60.0,
|
869
|
-
radius=4.0,
|
870
|
-
center_z=-6.0,
|
871
|
-
speed=1.0,
|
872
|
-
face_center_strength=1.0,
|
873
|
-
extra_scale=1.0):
|
874
|
-
"""
|
875
|
-
t: tiempo en segundos
|
876
|
-
aspect: width/height del framebuffer
|
877
|
-
radius: radio en XZ
|
878
|
-
center_z: desplaza el carrusel entero hacia -Z para que esté frente a cámara
|
879
|
-
speed: velocidad angular
|
880
|
-
face_center_strength: 1.0 = panel mira al centro; 0.0 = no gira con la órbita
|
881
|
-
"""
|
882
|
-
|
883
|
-
# Proyección y vista (cámara en el origen mirando hacia -Z)
|
884
|
-
proj = perspective(np.radians(fov_deg), aspect, 0.1, 100.0)
|
885
|
-
view = np.eye(4, dtype='f4') # o look_at((0,0,0), (0,0,-1))
|
886
|
-
|
887
|
-
# Ángulo de órbita (elige el offset para que "entre" por la izquierda)
|
888
|
-
theta = speed * t - np.pi * 0.5
|
889
|
-
|
890
|
-
# Órbita en XZ con el centro desplazado a center_z
|
891
|
-
# x = radius * np.cos(theta)
|
892
|
-
# z = radius * np.sin(theta) + center_z
|
893
|
-
x = radius * np.cos(theta)
|
894
|
-
z = (radius * 0.2) * np.sin(theta) + center_z
|
895
|
-
|
896
|
-
# Yaw para que el panel apunte al centro (0,0,center_z)
|
897
|
-
# El vector desde panel -> centro es (-x, 0, center_z - z)
|
898
|
-
yaw_to_center = np.arctan2(-x, (center_z - z)) # atan2(X, Z)
|
899
|
-
yaw = face_center_strength * yaw_to_center
|
900
|
-
|
901
|
-
model = translate(x, 0.0, z) @ rotate_y(yaw) @ scale_uniform(extra_scale)
|
902
|
-
|
903
|
-
# ¡IMPORTANTE! OpenGL espera column-major: transponemos al escribir
|
904
|
-
mvp = proj @ view @ model
|
905
|
-
return mvp
|
906
|
-
|
907
|
-
aspect = self.size[0] / self.size[1]
|
908
|
-
mvp = carousel_mvp(t, aspect=aspect, radius=4.0, center_z=-4.0, speed=1.2, face_center_strength=1.0, extra_scale = 1.0)
|
909
|
-
|
910
|
-
self.uniforms.set_mat('mvp', mvp.T.tobytes())
|
911
|
-
|
912
|
-
return self
|
913
|
-
|
914
|
-
class RotatingInCenterFrame(FrameShaderBase):
|
915
|
-
"""
|
916
|
-
The frame but orbiting around the camera.
|
917
|
-
"""
|
918
|
-
|
919
|
-
@property
|
920
|
-
def vertex_shader(
|
921
|
-
self
|
922
|
-
) -> str:
|
923
|
-
return (
|
924
|
-
'''
|
925
|
-
#version 330
|
926
|
-
|
927
|
-
in vec2 in_vert;
|
928
|
-
in vec2 in_texcoord;
|
929
|
-
out vec2 v_uv;
|
930
|
-
|
931
|
-
uniform float time;
|
932
|
-
uniform float speed;
|
933
|
-
|
934
|
-
void main() {
|
935
|
-
v_uv = in_texcoord;
|
936
|
-
|
937
|
-
// Rotación alrededor del eje Y
|
938
|
-
float angle = time * speed; // puedes usar time directamente, o time * speed
|
939
|
-
float cosA = cos(angle);
|
940
|
-
float sinA = sin(angle);
|
941
|
-
|
942
|
-
// Convertimos el quad a 3D (x, y, z)
|
943
|
-
vec3 pos = vec3(in_vert.xy, 0.0);
|
944
|
-
|
945
|
-
// Rotación Y
|
946
|
-
mat3 rotY = mat3(
|
947
|
-
cosA, 0.0, sinA,
|
948
|
-
0.0 , 1.0, 0.0,
|
949
|
-
-sinA, 0.0, cosA
|
950
|
-
);
|
951
|
-
|
952
|
-
pos = rotY * pos;
|
953
|
-
|
954
|
-
gl_Position = vec4(pos, 1.0);
|
955
|
-
}
|
956
|
-
'''
|
957
|
-
)
|
958
|
-
|
959
|
-
@property
|
960
|
-
def fragment_shader(
|
961
|
-
self
|
962
|
-
) -> str:
|
963
|
-
return (
|
964
|
-
'''
|
965
|
-
#version 330
|
966
|
-
|
967
|
-
in vec2 v_uv;
|
968
|
-
out vec4 f_color;
|
969
|
-
|
970
|
-
uniform sampler2D tex;
|
971
|
-
|
972
|
-
void main() {
|
973
|
-
f_color = texture(tex, v_uv);
|
974
|
-
}
|
975
|
-
'''
|
976
|
-
)
|
977
|
-
|
978
|
-
def __init__(
|
979
|
-
self,
|
980
|
-
size,
|
981
|
-
first_frame,
|
982
|
-
context = None,
|
983
|
-
speed: float = 30
|
984
|
-
):
|
985
|
-
super().__init__(size, first_frame, context)
|
986
|
-
|
987
|
-
self.uniforms.set('speed', speed)
|
988
|
-
|
989
|
-
def _prepare_frame(
|
990
|
-
self,
|
991
|
-
t: float
|
992
|
-
) -> 'BreathingFrame':
|
993
|
-
self.uniforms.set('time', t)
|
994
|
-
|
995
|
-
return self
|
996
|
-
|
997
|
-
class StrangeTvFrame(FrameShaderBase):
|
998
|
-
"""
|
999
|
-
Nice effect like a tv screen or something...
|
1000
|
-
"""
|
1001
|
-
|
1002
|
-
@property
|
1003
|
-
def vertex_shader(
|
1004
|
-
self
|
1005
|
-
) -> str:
|
1006
|
-
return (
|
1007
|
-
'''
|
1008
|
-
#version 330
|
1009
|
-
in vec2 in_vert;
|
1010
|
-
in vec2 in_texcoord;
|
1011
|
-
out vec2 v_uv;
|
1012
|
-
|
1013
|
-
void main() {
|
1014
|
-
v_uv = in_texcoord;
|
1015
|
-
gl_Position = vec4(in_vert, 0.0, 1.0);
|
1016
|
-
}
|
1017
|
-
'''
|
1018
|
-
)
|
1019
|
-
|
1020
|
-
@property
|
1021
|
-
def fragment_shader(
|
1022
|
-
self
|
1023
|
-
) -> str:
|
1024
|
-
return (
|
1025
|
-
'''
|
1026
|
-
#version 330
|
1027
|
-
|
1028
|
-
uniform sampler2D tex;
|
1029
|
-
uniform float time;
|
1030
|
-
|
1031
|
-
// ---- Parámetros principales (ajústalos en runtime) ----
|
1032
|
-
uniform float aberr_strength; // 0..3 (fuerza del RGB split radial)
|
1033
|
-
uniform float barrel_k; // -0.5..0.5 (distorsión de lente; positivo = barrel)
|
1034
|
-
uniform float blur_radius; // 0..0.02 (radio de motion blur en UV)
|
1035
|
-
uniform float blur_angle; // en radianes (dirección del arrastre)
|
1036
|
-
uniform int blur_samples; // 4..24 (taps del blur)
|
1037
|
-
uniform float vignette_strength; // 0..2
|
1038
|
-
uniform float grain_amount; // 0..0.1
|
1039
|
-
uniform float flicker_amount; // 0..0.2
|
1040
|
-
uniform float scanline_amount; // 0..0.2
|
1041
|
-
|
1042
|
-
in vec2 v_uv;
|
1043
|
-
out vec4 f_color;
|
1044
|
-
|
1045
|
-
// --- helpers ---
|
1046
|
-
float rand(vec2 co){
|
1047
|
-
return fract(sin(dot(co, vec2(12.9898,78.233))) * 43758.5453);
|
1048
|
-
}
|
1049
|
-
|
1050
|
-
// Barrel distortion (simple, k>0 curva hacia fuera)
|
1051
|
-
vec2 barrel(vec2 uv, float k){
|
1052
|
-
// map to [-1,1]
|
1053
|
-
vec2 p = uv * 2.0 - 1.0;
|
1054
|
-
float r2 = dot(p, p);
|
1055
|
-
p *= (1.0 + k * r2);
|
1056
|
-
// back to [0,1]
|
1057
|
-
return p * 0.5 + 0.5;
|
1058
|
-
}
|
1059
|
-
|
1060
|
-
// Aberración cromática radial
|
1061
|
-
vec3 sample_chromatic(sampler2D t, vec2 uv, vec2 center, float strength){
|
1062
|
-
// Offset radial según distancia al centro
|
1063
|
-
vec2 d = uv - center;
|
1064
|
-
float r = length(d);
|
1065
|
-
vec2 dir = (r > 1e-5) ? d / r : vec2(0.0);
|
1066
|
-
// Cada canal se desplaza un poco distinto
|
1067
|
-
float s = strength * r * 0.005; // escala fina
|
1068
|
-
float sr = s * 1.0;
|
1069
|
-
float sg = s * 0.5;
|
1070
|
-
float sb = s * -0.5; // azul hacia dentro para contraste
|
1071
|
-
|
1072
|
-
float rC = texture(t, uv + dir * sr).r;
|
1073
|
-
float gC = texture(t, uv + dir * sg).g;
|
1074
|
-
float bC = texture(t, uv + dir * sb).b;
|
1075
|
-
return vec3(rC, gC, bC);
|
1076
|
-
}
|
1077
|
-
|
1078
|
-
void main(){
|
1079
|
-
vec2 uv = v_uv;
|
1080
|
-
vec2 center = vec2(0.5, 0.5);
|
1081
|
-
|
1082
|
-
// Lente (barrel/pincushion)
|
1083
|
-
uv = barrel(uv, barrel_k);
|
1084
|
-
|
1085
|
-
// Early out si nos salimos mucho (fade de bordes)
|
1086
|
-
vec2 uv_clamped = clamp(uv, 0.0, 1.0);
|
1087
|
-
float edge = smoothstep(0.0, 0.02, 1.0 - max(max(-uv.x, uv.x-1.0), max(-uv.y, uv.y-1.0)));
|
1088
|
-
|
1089
|
-
// Dirección del motion blur
|
1090
|
-
vec2 dir = vec2(cos(blur_angle), sin(blur_angle));
|
1091
|
-
// Pequeña variación temporal para que “respire”
|
1092
|
-
float jitter = (sin(time * 13.0) * 0.5 + 0.5) * 0.4 + 0.6;
|
1093
|
-
|
1094
|
-
// Acumulación de blur con pesos
|
1095
|
-
vec3 acc = vec3(0.0);
|
1096
|
-
float wsum = 0.0;
|
1097
|
-
|
1098
|
-
int N = max(1, blur_samples);
|
1099
|
-
for(int i = 0; i < 64; ++i){ // hard cap de seguridad
|
1100
|
-
if(i >= N) break;
|
1101
|
-
// t de -1..1 distribuye muestras a ambos lados
|
1102
|
-
float fi = float(i);
|
1103
|
-
float t = (fi / float(N - 1)) * 2.0 - 1.0;
|
1104
|
-
|
1105
|
-
// curva de pesos (gauss approx)
|
1106
|
-
float w = exp(-t*t * 2.5);
|
1107
|
-
// offset base
|
1108
|
-
vec2 ofs = dir * t * blur_radius * jitter;
|
1109
|
-
|
1110
|
-
// micro-jitter por muestra para romper banding
|
1111
|
-
ofs += vec2(rand(uv + fi)*0.0005, rand(uv + fi + 3.14)*0.0005) * blur_radius;
|
1112
|
-
|
1113
|
-
// muestreo con aberración cromática
|
1114
|
-
vec3 c = sample_chromatic(tex, uv + ofs, center, aberr_strength);
|
1115
|
-
|
1116
|
-
acc += c * w;
|
1117
|
-
wsum += w;
|
1118
|
-
}
|
1119
|
-
vec3 col = acc / max(wsum, 1e-6);
|
1120
|
-
|
1121
|
-
// Scanlines + flicker
|
1122
|
-
float scan = 1.0 - scanline_amount * (0.5 + 0.5 * sin((uv.y + time*1.7)*3.14159*480.0));
|
1123
|
-
float flick = 1.0 + flicker_amount * (sin(time*60.0 + uv.x*10.0) * 0.5 + 0.5);
|
1124
|
-
col *= scan * flick;
|
1125
|
-
|
1126
|
-
// Vignette (radial)
|
1127
|
-
float r = distance(uv, center);
|
1128
|
-
float vig = 1.0 - smoothstep(0.7, 1.0, r * (1.0 + 0.5*vignette_strength));
|
1129
|
-
col *= mix(1.0, vig, vignette_strength);
|
1130
|
-
|
1131
|
-
// Grano
|
1132
|
-
float g = (rand(uv * (time*37.0 + 1.0)) - 0.5) * 2.0 * grain_amount;
|
1133
|
-
col += g;
|
1134
|
-
|
1135
|
-
// Fade de bordes por clamp/warp
|
1136
|
-
col *= edge;
|
1137
|
-
|
1138
|
-
f_color = vec4(col, 1.0);
|
1139
|
-
}
|
1140
|
-
'''
|
1141
|
-
)
|
1142
|
-
|
1143
|
-
def __init__(
|
1144
|
-
self,
|
1145
|
-
size,
|
1146
|
-
first_frame,
|
1147
|
-
context = None,
|
1148
|
-
aberr_strength = 1.5,
|
1149
|
-
barrel_k = 0.08,
|
1150
|
-
blur_radius = 0.006,
|
1151
|
-
blur_angle = 0.0, # (0 = horizontal, 1.57 ≈ vertical)
|
1152
|
-
blur_samples = 12,
|
1153
|
-
vignette_strength = 0.8,
|
1154
|
-
grain_amount = 0.02,
|
1155
|
-
flicker_amount = 0.05,
|
1156
|
-
scanline_amount = 0.05
|
1157
|
-
):
|
1158
|
-
super().__init__(size, first_frame, context)
|
1159
|
-
|
1160
|
-
self.uniforms.set('aberr_strength', aberr_strength)
|
1161
|
-
self.uniforms.set('barrel_k', barrel_k)
|
1162
|
-
self.uniforms.set('blur_radius', blur_radius)
|
1163
|
-
self.uniforms.set('blur_angle', blur_angle)
|
1164
|
-
self.uniforms.set('blur_samples', blur_samples)
|
1165
|
-
self.uniforms.set('vignette_strength', vignette_strength)
|
1166
|
-
self.uniforms.set('grain_amount', grain_amount)
|
1167
|
-
self.uniforms.set('flicker_amount', flicker_amount)
|
1168
|
-
self.uniforms.set('scanline_amount', scanline_amount)
|
1169
|
-
|
1170
|
-
def _prepare_frame(
|
1171
|
-
self,
|
1172
|
-
t: float
|
1173
|
-
) -> 'BreathingFrame':
|
1174
|
-
self.uniforms.set('time', t)
|
1175
|
-
|
1176
|
-
return self
|
1177
|
-
|
1178
|
-
class GlitchRgbFrame(FrameShaderBase):
|
1179
|
-
"""
|
1180
|
-
Nice effect like a tv screen or something...
|
1181
|
-
"""
|
1182
|
-
|
1183
|
-
@property
|
1184
|
-
def vertex_shader(
|
1185
|
-
self
|
1186
|
-
) -> str:
|
1187
|
-
return (
|
1188
|
-
'''
|
1189
|
-
#version 330
|
1190
|
-
|
1191
|
-
// ----------- Vertex Shader -----------
|
1192
|
-
in vec2 in_vert;
|
1193
|
-
in vec2 in_texcoord;
|
1194
|
-
|
1195
|
-
out vec2 v_uv;
|
1196
|
-
|
1197
|
-
void main() {
|
1198
|
-
v_uv = in_texcoord;
|
1199
|
-
gl_Position = vec4(in_vert, 0.0, 1.0);
|
1200
|
-
}
|
1201
|
-
'''
|
1202
|
-
)
|
1203
|
-
|
1204
|
-
@property
|
1205
|
-
def fragment_shader(
|
1206
|
-
self
|
1207
|
-
) -> str:
|
1208
|
-
return (
|
1209
|
-
'''
|
1210
|
-
#version 330
|
1211
|
-
|
1212
|
-
// ----------- Fragment Shader -----------
|
1213
|
-
uniform sampler2D tex;
|
1214
|
-
uniform float time;
|
1215
|
-
|
1216
|
-
// Intensidades del efecto
|
1217
|
-
uniform float amp; // amplitud de distorsión
|
1218
|
-
uniform float freq; // frecuencia de la onda
|
1219
|
-
uniform float glitchAmp; // fuerza del glitch
|
1220
|
-
uniform float glitchSpeed;
|
1221
|
-
|
1222
|
-
in vec2 v_uv;
|
1223
|
-
out vec4 f_color;
|
1224
|
-
|
1225
|
-
void main() {
|
1226
|
-
// Distorsión sinusoidal en Y
|
1227
|
-
float wave = sin(v_uv.x * freq + time * 2.0) * amp;
|
1228
|
-
|
1229
|
-
// Pequeño desplazamiento aleatorio (shake)
|
1230
|
-
float shakeX = (fract(sin(time * 12.9898) * 43758.5453) - 0.5) * 0.01;
|
1231
|
-
float shakeY = (fract(sin(time * 78.233) * 12345.6789) - 0.5) * 0.01;
|
1232
|
-
|
1233
|
-
// Coordenadas base con distorsión
|
1234
|
-
vec2 uv = vec2(v_uv.x + shakeX, v_uv.y + wave + shakeY);
|
1235
|
-
|
1236
|
-
// Glitch con separación RGB
|
1237
|
-
float glitch = sin(time * glitchSpeed) * glitchAmp;
|
1238
|
-
vec2 uv_r = uv + vec2(glitch, 0.0);
|
1239
|
-
vec2 uv_g = uv + vec2(-glitch * 0.5, glitch * 0.5);
|
1240
|
-
vec2 uv_b = uv + vec2(0.0, -glitch);
|
1241
|
-
|
1242
|
-
// Muestreo canales desplazados
|
1243
|
-
float r = texture(tex, uv_r).r;
|
1244
|
-
float g = texture(tex, uv_g).g;
|
1245
|
-
float b = texture(tex, uv_b).b;
|
1246
|
-
|
1247
|
-
f_color = vec4(r, g, b, 1.0);
|
1248
|
-
}
|
1249
|
-
'''
|
1250
|
-
)
|
1251
|
-
|
1252
|
-
def __init__(
|
1253
|
-
self,
|
1254
|
-
size,
|
1255
|
-
first_frame,
|
1256
|
-
context = None,
|
1257
|
-
amp = 0.02,
|
1258
|
-
freq = 25.0,
|
1259
|
-
glitchAmp = 0.02,
|
1260
|
-
glitchSpeed = 30.0
|
1261
|
-
):
|
1262
|
-
super().__init__(size, first_frame, context)
|
1263
|
-
|
1264
|
-
self.uniforms.set('amp', amp)
|
1265
|
-
self.uniforms.set('freq', freq)
|
1266
|
-
self.uniforms.set('glitchAmp', glitchAmp)
|
1267
|
-
self.uniforms.set('glitchSpeed', glitchSpeed)
|
1268
|
-
|
1269
|
-
def _prepare_frame(
|
1270
|
-
self,
|
1271
|
-
t: float
|
1272
|
-
) -> 'BreathingFrame':
|
1273
|
-
self.uniforms.set('time', t)
|
1274
|
-
|
1275
|
-
return self
|
1276
|
-
|