summaryrefslogtreecommitdiff
path: root/src/effects
diff options
context:
space:
mode:
Diffstat (limited to 'src/effects')
-rw-r--r--src/effects/chroma_aberration.wgsl24
-rw-r--r--src/effects/distort.wgsl20
-rw-r--r--src/effects/ellipse.wgsl12
-rw-r--r--src/effects/flash.wgsl27
-rw-r--r--src/effects/gaussian_blur.wgsl33
-rw-r--r--src/effects/particle_compute.wgsl31
-rw-r--r--src/effects/particle_render.wgsl53
-rw-r--r--src/effects/particle_spray_compute.wgsl35
-rw-r--r--src/effects/rotating_cube.wgsl89
-rw-r--r--src/effects/scene1.wgsl165
-rw-r--r--src/effects/scene2.wgsl37
-rw-r--r--src/effects/solarize.wgsl46
-rw-r--r--src/effects/vignette.wgsl24
13 files changed, 596 insertions, 0 deletions
diff --git a/src/effects/chroma_aberration.wgsl b/src/effects/chroma_aberration.wgsl
new file mode 100644
index 0000000..02bdb1b
--- /dev/null
+++ b/src/effects/chroma_aberration.wgsl
@@ -0,0 +1,24 @@
+@group(0) @binding(0) var smplr: sampler;
+@group(0) @binding(1) var txt: texture_2d<f32>;
+
+#include "common_uniforms"
+struct ChromaAberrationParams {
+ offset_scale: f32,
+ angle: f32,
+};
+
+@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
+@group(0) @binding(3) var<uniform> params: ChromaAberrationParams;
+
+#include "render/fullscreen_vs"
+
+@fragment fn fs_main(@builtin(position) p: vec4f) -> @location(0) vec4f {
+ let uv = p.xy / uniforms.resolution;
+
+ let amp = params.offset_scale * uniforms.audio_intensity;
+ let offset = amp * vec2f(cos(params.angle), sin(params.angle));
+ let center = textureSample(txt, smplr, uv);
+ let r = textureSample(txt, smplr, uv + offset).r;
+ let b = textureSample(txt, smplr, uv - offset).b;
+ return vec4f(r, center.g, b, 1.0);
+}
diff --git a/src/effects/distort.wgsl b/src/effects/distort.wgsl
new file mode 100644
index 0000000..e19bd16
--- /dev/null
+++ b/src/effects/distort.wgsl
@@ -0,0 +1,20 @@
+@group(0) @binding(0) var smplr: sampler;
+@group(0) @binding(1) var txt: texture_2d<f32>;
+
+#include "common_uniforms"
+
+struct DistortParams {
+ strength: f32,
+ speed: f32,
+};
+
+@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
+@group(0) @binding(3) var<uniform> params: DistortParams;
+
+#include "render/fullscreen_vs"
+
+@fragment fn fs_main(@builtin(position) p: vec4f) -> @location(0) vec4f {
+ let uv = p.xy / uniforms.resolution;
+ let dist = params.strength * uniforms.audio_intensity * sin(uv.y * 20.0 + uniforms.time * params.speed * 5.0);
+ return textureSample(txt, smplr, uv + vec2f(dist, 0.0));
+}
diff --git a/src/effects/ellipse.wgsl b/src/effects/ellipse.wgsl
new file mode 100644
index 0000000..a377507
--- /dev/null
+++ b/src/effects/ellipse.wgsl
@@ -0,0 +1,12 @@
+#include "common_uniforms"
+#include "render/fullscreen_vs"
+#include "math/sdf_shapes"
+
+@group(0) @binding(0) var<uniform> uniforms: CommonUniforms;
+
+@fragment fn fs_main(@builtin(position) p: vec4f) -> @location(0) vec4f {
+ let uv = (p.xy / uniforms.resolution - 0.5) * 2.0;
+ let movement = vec2f(sin(uniforms.time * 0.7), cos(uniforms.time * 0.5));
+ let d = sdEllipse((uv * vec2f(uniforms.aspect_ratio, 1.0)) - movement, vec2f(0.5, 0.3) * (1.0 + uniforms.beat_phase * 0.2));
+ return mix(vec4f(0.2, 0.8, 0.4, 1.0), vec4f(0.0), smoothstep(0.0, 0.01, d));
+}
diff --git a/src/effects/flash.wgsl b/src/effects/flash.wgsl
new file mode 100644
index 0000000..379086e
--- /dev/null
+++ b/src/effects/flash.wgsl
@@ -0,0 +1,27 @@
+// Flash shader for visual sync testing
+// Pulses white on beat
+#include "sequence_uniforms"
+
+@group(0) @binding(2) var<uniform> uniforms: UniformsSequenceParams;
+
+struct VertexOutput {
+ @builtin(position) position: vec4f,
+ @location(0) uv: vec2f,
+};
+
+@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput {
+ var out: VertexOutput;
+ let x = f32((vid & 1u) << 1u);
+ let y = f32((vid & 2u));
+ out.position = vec4f(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);
+ out.uv = vec2f(x, y);
+ return out;
+}
+
+@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
+ // Strong flash on beat, fades quickly
+ let intensity = pow(1.0 - uniforms.beat_phase, 4.0);
+ // Add audio intensity for extra punch
+ let final_intensity = intensity * (1.0 + uniforms.audio_intensity * 0.5);
+ return vec4f(final_intensity, final_intensity, final_intensity, 1.0);
+}
diff --git a/src/effects/gaussian_blur.wgsl b/src/effects/gaussian_blur.wgsl
new file mode 100644
index 0000000..68e0720
--- /dev/null
+++ b/src/effects/gaussian_blur.wgsl
@@ -0,0 +1,33 @@
+// 5x5 gaussian blur
+
+@group(0) @binding(0) var smplr: sampler;
+@group(0) @binding(1) var txt: texture_2d<f32>;
+
+#include "common_uniforms"
+#include "render/fullscreen_vs"
+
+struct GaussianBlurParams {
+ strength: f32,
+ strength_audio: f32,
+ stretch: f32, // dir_y / dir_x
+ _pad: f32,
+};
+
+@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
+@group(0) @binding(3) var<uniform> params: GaussianBlurParams;
+
+@fragment fn fs_main(@builtin(position) p: vec4f) -> @location(0) vec4f {
+ // Parameterized strength + dramatic beat pulsation
+ let pulse = 1.0 + uniforms.audio_intensity * params.strength_audio; // Pulsate beat
+ let size = params.strength * pulse;
+ let dir = vec2f(1., params.stretch) * size / uniforms.resolution.x;
+
+ let uv = p.xy / uniforms.resolution;
+ var res = vec4f(0.0);
+ for (var x: f32 = -2.0; x <= 2.0; x += 1.0) {
+ for (var y: f32 = -2.0; y <= 2.0; y += 1.0) {
+ res += textureSample(txt, smplr, uv + vec2f(x, y) * dir);
+ }
+ }
+ return res * (1. / 25.0);
+}
diff --git a/src/effects/particle_compute.wgsl b/src/effects/particle_compute.wgsl
new file mode 100644
index 0000000..2960cc4
--- /dev/null
+++ b/src/effects/particle_compute.wgsl
@@ -0,0 +1,31 @@
+// Particle simulation (compute shader) - V2
+struct Particle {
+ pos: vec4f,
+ vel: vec4f,
+ rot: vec4f,
+ color: vec4f,
+};
+
+#include "sequence_uniforms"
+
+@group(0) @binding(0) var<storage, read_write> particles: array<Particle>;
+@group(0) @binding(1) var<uniform> uniforms: UniformsSequenceParams;
+
+@compute @workgroup_size(64)
+fn main(@builtin(global_invocation_id) id: vec3u) {
+ let i = id.x;
+ if (i >= arrayLength(&particles)) {
+ return;
+ }
+ var p = particles[i];
+ let new_pos = p.pos.xyz + p.vel.xyz * 0.016;
+ p.pos = vec4f(new_pos, p.pos.w);
+ p.vel.y = p.vel.y - 0.01 * (1.0 + uniforms.audio_intensity * 5.0);
+ p.rot.x = p.rot.x + p.rot.y * 0.016;
+ if (p.pos.y < -1.5) {
+ p.pos.y = 1.5;
+ p.pos.x = (f32(i % 100u) / 50.0) - 1.0 + (uniforms.audio_intensity * 0.5);
+ p.vel.y = 0.0;
+ }
+ particles[i] = p;
+}
diff --git a/src/effects/particle_render.wgsl b/src/effects/particle_render.wgsl
new file mode 100644
index 0000000..ef0db42
--- /dev/null
+++ b/src/effects/particle_render.wgsl
@@ -0,0 +1,53 @@
+// Particle rendering (vertex + fragment) - V2
+struct Particle {
+ pos: vec4f,
+ vel: vec4f,
+ rot: vec4f,
+ color: vec4f,
+};
+
+#include "sequence_uniforms"
+
+@group(0) @binding(0) var<storage, read> particles: array<Particle>;
+@group(0) @binding(1) var<uniform> uniforms: UniformsSequenceParams;
+
+struct VSOut {
+ @builtin(position) pos: vec4f,
+ @location(0) color: vec4f,
+ @location(1) uv: vec2f,
+};
+
+@vertex fn vs_main(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VSOut {
+ let p = particles[ii];
+ let size = 0.02 + p.pos.z * 0.01 + uniforms.audio_intensity * 0.02;
+ var offsets = array<vec2f, 6>(
+ vec2f(-1, -1),
+ vec2f(1, -1),
+ vec2f(-1, 1),
+ vec2f(-1, 1),
+ vec2f(1, -1),
+ vec2f(1, 1)
+ );
+ let offset = offsets[vi];
+ let c = cos(p.rot.x);
+ let s = sin(p.rot.x);
+ let rotated_offset = vec2f(offset.x * c - offset.y * s, offset.x * s + offset.y * c);
+ let pos = vec2f(p.pos.x + rotated_offset.x * size / uniforms.aspect_ratio, p.pos.y + rotated_offset.y * size);
+
+ // Fade based on lifetime (p.pos.w goes from 1.0 to 0.0)
+ let lifetime_fade = p.pos.w;
+ let color_with_fade = vec4f(p.color.rgb * (0.5 + 0.5 * uniforms.audio_intensity), p.color.a * lifetime_fade);
+
+ return VSOut(vec4f(pos, 0.0, 1.0), color_with_fade, offset);
+}
+
+@fragment fn fs_main(@location(0) color: vec4f, @location(1) uv: vec2f) -> @location(0) vec4f {
+ // Calculate distance from center for circular shape
+ let dist = length(uv);
+
+ // Smooth circular falloff (1.0 at center, 0.0 at edge)
+ let circle_alpha = smoothstep(1.0, 0.5, dist);
+
+ // Apply circular fade to alpha channel
+ return vec4f(color.rgb, color.a * circle_alpha);
+}
diff --git a/src/effects/particle_spray_compute.wgsl b/src/effects/particle_spray_compute.wgsl
new file mode 100644
index 0000000..7bdae88
--- /dev/null
+++ b/src/effects/particle_spray_compute.wgsl
@@ -0,0 +1,35 @@
+struct Particle {
+ pos: vec4f,
+ vel: vec4f,
+ rot: vec4f,
+ color: vec4f,
+};
+
+#include "common_uniforms"
+
+@group(0) @binding(0) var<storage, read_write> particles: array<Particle>;
+@group(0) @binding(1) var<uniform> uniforms: CommonUniforms;
+
+fn hash(p: f32) -> f32 {
+ return fract(sin(p) * 43758.5453);
+}
+
+@compute @workgroup_size(64)
+fn main(@builtin(global_invocation_id) id: vec3u) {
+ let i = id.x;
+ if (i >= arrayLength(&particles)) {
+ return;
+ }
+ var p = particles[i];
+ if (p.pos.w <= 0.0) {
+ let r = hash(f32(i) + uniforms.time);
+ let angle = r * 6.28318;
+ p.pos = vec4f(0.0, 0.0, 0.0, 1.0);
+ p.vel = vec4f(cos(angle), sin(angle), 0.0, 0.0) * (0.5 + hash(r) * 0.5) * (1.0 + uniforms.audio_intensity * 2.0);
+ p.color = vec4f(hash(r + 0.1), hash(r + 0.2), 1.0, 1.0);
+ }
+ let new_pos = p.pos.xyz + p.vel.xyz * 0.016;
+ p.pos = vec4f(new_pos, p.pos.w - 0.01 * (1.0 + uniforms.beat_phase));
+ p.vel.y = p.vel.y - 0.01;
+ particles[i] = p;
+}
diff --git a/src/effects/rotating_cube.wgsl b/src/effects/rotating_cube.wgsl
new file mode 100644
index 0000000..0c75a13
--- /dev/null
+++ b/src/effects/rotating_cube.wgsl
@@ -0,0 +1,89 @@
+// Rotating cube shader v2 (simplified, no masking)
+
+struct Uniforms {
+ view_proj: mat4x4f,
+ inv_view_proj: mat4x4f,
+ camera_pos_time: vec4f,
+ params: vec4f,
+ resolution: vec2f,
+ aspect_ratio: f32,
+ _pad: f32,
+};
+
+struct ObjectData {
+ model: mat4x4f,
+ inv_model: mat4x4f,
+ color: vec4f,
+ params: vec4f,
+};
+
+@group(0) @binding(0) var<uniform> uniforms: Uniforms;
+@group(0) @binding(1) var<storage, read> object: ObjectData;
+
+struct VSOut {
+ @builtin(position) pos: vec4f,
+ @location(0) world_pos: vec3f,
+ @location(1) normal: vec3f,
+};
+
+// Cube vertices (hardcoded)
+fn get_cube_vertex(vid: u32) -> vec3f {
+ let positions = array<vec3f, 36>(
+ // Front face
+ vec3f(-1, -1, 1), vec3f( 1, -1, 1), vec3f( 1, 1, 1),
+ vec3f(-1, -1, 1), vec3f( 1, 1, 1), vec3f(-1, 1, 1),
+ // Back face
+ vec3f( 1, -1, -1), vec3f(-1, -1, -1), vec3f(-1, 1, -1),
+ vec3f( 1, -1, -1), vec3f(-1, 1, -1), vec3f( 1, 1, -1),
+ // Right face
+ vec3f( 1, -1, 1), vec3f( 1, -1, -1), vec3f( 1, 1, -1),
+ vec3f( 1, -1, 1), vec3f( 1, 1, -1), vec3f( 1, 1, 1),
+ // Left face
+ vec3f(-1, -1, -1), vec3f(-1, -1, 1), vec3f(-1, 1, 1),
+ vec3f(-1, -1, -1), vec3f(-1, 1, 1), vec3f(-1, 1, -1),
+ // Top face
+ vec3f(-1, 1, 1), vec3f( 1, 1, 1), vec3f( 1, 1, -1),
+ vec3f(-1, 1, 1), vec3f( 1, 1, -1), vec3f(-1, 1, -1),
+ // Bottom face
+ vec3f(-1, -1, -1), vec3f( 1, -1, -1), vec3f( 1, -1, 1),
+ vec3f(-1, -1, -1), vec3f( 1, -1, 1), vec3f(-1, -1, 1)
+ );
+ return positions[vid];
+}
+
+fn get_cube_normal(vid: u32) -> vec3f {
+ let face_id = vid / 6u;
+ let normals = array<vec3f, 6>(
+ vec3f( 0, 0, 1), // Front
+ vec3f( 0, 0, -1), // Back
+ vec3f( 1, 0, 0), // Right
+ vec3f(-1, 0, 0), // Left
+ vec3f( 0, 1, 0), // Top
+ vec3f( 0, -1, 0) // Bottom
+ );
+ return normals[face_id];
+}
+
+@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VSOut {
+ let local_pos = get_cube_vertex(vid);
+ let local_normal = get_cube_normal(vid);
+
+ let world_pos = object.model * vec4f(local_pos, 1.0);
+ let world_normal = normalize((object.model * vec4f(local_normal, 0.0)).xyz);
+
+ let clip_pos = uniforms.view_proj * world_pos;
+
+ return VSOut(clip_pos, world_pos.xyz, world_normal);
+}
+
+@fragment fn fs_main(@location(0) world_pos: vec3f, @location(1) normal: vec3f) -> @location(0) vec4f {
+ let N = normalize(normal);
+ let light_dir = normalize(vec3f(1.0, 1.0, 1.0));
+ let diffuse = max(dot(N, light_dir), 0.0);
+
+ let ambient = 0.3;
+ let lighting = ambient + diffuse * 0.7;
+
+ let color = object.color.rgb * lighting;
+ return vec4f(color, 1.0);
+}
diff --git a/src/effects/scene1.wgsl b/src/effects/scene1.wgsl
new file mode 100644
index 0000000..291173f
--- /dev/null
+++ b/src/effects/scene1.wgsl
@@ -0,0 +1,165 @@
+// Scene1 effect shader - ShaderToy conversion (raymarching cube & sphere)
+// Source: Saturday cubism experiment by skal
+
+#include "sequence_uniforms"
+#include "camera_common"
+#include "math/color"
+#include "math/utils"
+#include "math/sdf_shapes"
+#include "math/common_utils"
+#include "render/raymarching_id"
+
+@group(0) @binding(2) var<uniform> uniforms: UniformsSequenceParams;
+@group(0) @binding(3) var<uniform> camera: CameraParams;
+
+const skyCol = vec3f(0.176, 0.235, 0.25);
+const skylineCol = vec3f(0.5, 0.125, 0.025);
+const sunCol = vec3f(0.5, 0.163, 0.025);
+const diffCol1 = vec3f(0.4, 1.0, 1.0);
+const diffCol2 = vec3f(0.325, 1.0, 0.975);
+
+// Lighting (normalized manually)
+const sunDir1 = vec3f(0.0, 0.04997, -0.99875); // normalize(0, 0.05, -1)
+const lightPos1 = vec3f(10.0, 10.0, 10.0);
+const lightPos2 = vec3f(-10.0, 10.0, -10.0);
+
+fn rayPlane(ray: Ray, plane: vec4f) -> f32 {
+ return (dot(ray.origin, plane.xyz) - plane.w) / dot(ray.direction, plane.xyz);
+}
+
+fn render0(ray: Ray) -> vec3f {
+ var col = vec3f(0.0);
+ let y = abs(ray.direction.y);
+ let sf = 1.0001 - max(dot(sunDir1, ray.direction), 0.0);
+ col += skyCol * pow(y, 8.0);
+ col += skylineCol * (mix(0.0025, 0.125, tanh_approx(0.005 / sf)) / y);
+ col += sunCol * 0.00005 / (sf * sf);
+
+/*
+ let tp1 = rayPlane(ray, vec4f(0.0, -1.0, 0.0, 6.0));
+ if (tp1 > 0.0) {
+ let pos = ray.origin + tp1 * ray.direction;
+ let pp = pos.xz;
+ let db = sdBox2D(pp, vec2f(5.0, 9.0)) - 3.0;
+ col += vec3f(4.0) * skyCol * y * y * smoothstep(0.25, 0.0, db);
+ col += vec3f(0.8) * skyCol * exp(-0.5 * max(db, 0.0));
+ }
+*/
+ return clamp(col, vec3f(0.0), vec3f(10.0));
+}
+
+const OBJ_BACKGROUND: f32 = 0.0;
+const OBJ_CUBE: f32 = 1.0;
+const OBJ_SPHERE: f32 = 2.0;
+const OBJ_PLANE: f32 = 3.0;
+
+// TODO: remove! (issue with #include macros)
+fn df(p: vec3f) -> f32 {
+ return 0.;
+}
+
+fn dfWithID(p: vec3f) -> RayMarchResult {
+ // Cube
+ var pc = p - vec3f(-1.9, 0.0, 0.0);
+ let dCube = sdBox(pc, vec3f(1.6));
+
+ // Sphere
+ var ps = p - vec3f(1.3, 0.0, 0.0);
+ let dSphere = sdSphere(ps, 1.2);
+
+ // Ground plane
+ let dPlane = p.y + 1.0;
+
+ // Find closest object
+ var result: RayMarchResult;
+ result.distance = dCube;
+ result.object_id = OBJ_CUBE;
+
+ if (dSphere < result.distance) {
+ result.distance = dSphere;
+ result.object_id = OBJ_SPHERE;
+ }
+
+ if (dPlane < result.distance) {
+ result.distance = dPlane;
+ result.object_id = OBJ_PLANE;
+ }
+
+ result.distance_max = result.distance;
+ return result;
+}
+
+fn boxCol(col: vec3f, nsp: vec3f, rd: vec3f, nnor: vec3f, nrcol: vec3f, nshd1: f32, nshd2: f32) -> vec3f {
+ var nfre = 1.0 + dot(rd, nnor);
+ nfre *= nfre;
+
+ let nld1 = normalize(lightPos1 - nsp);
+ let nld2 = normalize(lightPos2 - nsp);
+
+ var ndif1 = max(dot(nld1, nnor), 0.0);
+ ndif1 *= ndif1;
+
+ var ndif2 = max(dot(nld2, nnor), 0.0);
+ ndif2 *= ndif2;
+
+ var scol = vec3f(0.0);
+ let rf = smoothstep(1.0, 0.9, nfre);
+ scol += diffCol1 * ndif1 * nshd1;
+ scol += diffCol2 * ndif2 * nshd2;
+ scol += 0.1 * (skyCol + skylineCol);
+ scol += nrcol * 0.75 * mix(vec3f(0.25), vec3f(0.5, 0.5, 1.0), nfre);
+
+ return mix(col, scol, rf * smoothstep(90.0, 20.0, dot(nsp, nsp)));
+}
+
+fn render1(ray: Ray) -> vec3f {
+ let skyCol_local = render0(ray);
+ var col = skyCol_local;
+
+ var init: RayMarchResult;
+ init.distance = 0.0;
+ init.distance_max = 0.0;
+ init.object_id = OBJ_BACKGROUND;
+
+ let result = rayMarchWithID(ray.origin, ray.direction, init);
+ if (result.distance < MAX_RAY_LENGTH) {
+ let nsp = reconstructPosition(ray, result);
+ let nnor = normalWithID(nsp);
+
+ let nref = reflect(ray.direction, nnor);
+ var refl_init: RayMarchResult;
+ refl_init.distance = 0.2;
+ refl_init.distance_max = 0.2;
+ refl_init.object_id = OBJ_BACKGROUND;
+ let nrt_result = rayMarchWithID(nsp, nref, refl_init);
+ let rRay = Ray(nsp, nref);
+ var nrcol = render0(rRay);
+
+ if (nrt_result.distance < MAX_RAY_LENGTH) {
+ let nrsp = reconstructPosition(Ray(nsp, nref), nrt_result);
+ let nrnor = normalWithID(nrsp);
+ let nrref = reflect(nref, nrnor);
+ nrcol = boxCol(nrcol, nrsp, nref, nrnor, render0(Ray(nrsp, nrref)), 1.0, 1.0);
+ }
+
+ let light_dist1 = distance(lightPos1, nsp);
+ let light_dist2 = distance(lightPos2, nsp);
+ let nshd1 = mix(0.0, 1.0, shadowWithStoredDistance(nsp, normalize(lightPos1 - nsp), light_dist1));
+ let nshd2 = mix(0.0, 1.0, shadowWithStoredDistance(nsp, normalize(lightPos2 - nsp), light_dist2));
+
+ col = boxCol(col, nsp, ray.direction, nnor, nrcol, nshd1, nshd2);
+ }
+
+ return col;
+}
+
+#include "render/fullscreen_vs"
+
+@fragment fn fs_main(@builtin(position) p: vec4f) -> @location(0) vec4f {
+ let coord = getScreenCoord(p, uniforms.resolution);
+ let ray = getCameraRay(camera, coord);
+ var col = render1(ray);
+ col = aces_approx(col);
+ col = sRGB(col);
+ return vec4f(col, 1.0);
+}
diff --git a/src/effects/scene2.wgsl b/src/effects/scene2.wgsl
new file mode 100644
index 0000000..7df91c3
--- /dev/null
+++ b/src/effects/scene2.wgsl
@@ -0,0 +1,37 @@
+// Scene2 effect shader - ShaderToy conversion (volumetric clouds)
+// Generated by convert_shadertoy.py
+// NOTE: Manual review recommended - conversion is basic
+
+#include "sequence_uniforms"
+#include "render/fullscreen_uv_vs"
+
+@group(0) @binding(2) var<uniform> uniforms: UniformsSequenceParams;
+
+fn N(a: vec3f, p: vec3f) -> f32 {
+ return abs(dot(sin(uniforms.time + 0.1 * p.z + 0.3 * p / a), a + a));
+}
+
+@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
+ let r = uniforms.resolution;
+ let frag_coord = vec2f(in.position.x, r.y - in.position.y);
+ let u = (frag_coord + frag_coord - r) / r.y;
+
+ var p = vec3f(0.0);
+ var col = vec4f(0.0);
+ var s: f32 = 0.0;
+
+ for (var i: f32 = 0.0; i < 100.0; i += 1.0) {
+ p += vec3f(u * s, s);
+ s = 6.0 + p.y;
+ s -= N(vec3f(0.08), p);
+ s -= N(vec3f(0.2), p);
+ s -= N(vec3f(0.6), p);
+ s = 0.1 + abs(s) * 0.2;
+ col += vec4f(4.0, 2.0, 1.0, 0.0) / s;
+ }
+
+ col *= smoothstep(0.8, 0.75, abs(u.y));
+ let len_u = max(length(u), 0.0001);
+ col = tanh(col / 2000.0 / len_u);
+ return vec4f(col.rgb, 1.0);
+}
diff --git a/src/effects/solarize.wgsl b/src/effects/solarize.wgsl
new file mode 100644
index 0000000..02065de
--- /dev/null
+++ b/src/effects/solarize.wgsl
@@ -0,0 +1,46 @@
+@group(0) @binding(0) var smplr: sampler;
+@group(0) @binding(1) var txt: texture_2d<f32>;
+
+#include "common_uniforms"
+
+@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
+
+#include "render/fullscreen_vs"
+
+@fragment fn fs_main(@builtin(position) p: vec4f) -> @location(0) vec4f {
+ let uv = p.xy / uniforms.resolution;
+ var col = textureSample(txt, smplr, uv);
+
+ // Patterns are 2 seconds long (120 BPM, 4 beats)
+ let pattern_num = u32(uniforms.time / 2.0);
+ let is_even_pattern = (pattern_num % 2u) == 0u;
+
+ // Reduced threshold range for subtler effect
+ let thr = 0.4 + 0.15 * sin(uniforms.time);
+ let solarize_strength = 0.4; // Reduced from 1.0 for less saturation
+
+ if (is_even_pattern) {
+ // Even patterns: Subtle red-orange tint
+ if (col.r < thr) {
+ col.r = mix(col.r, 1.0 - col.r, solarize_strength);
+ }
+ if (col.g < thr) {
+ col.g = mix(col.g, 1.0 - col.g * 0.7, solarize_strength * 0.7);
+ }
+ if (col.b < thr) {
+ col.b = mix(col.b, col.b * 0.5, solarize_strength);
+ }
+ } else {
+ // Odd patterns: Subtle blue-cyan tint
+ if (col.r < thr) {
+ col.r = mix(col.r, col.r * 0.6, solarize_strength);
+ }
+ if (col.g < thr) {
+ col.g = mix(col.g, 1.0 - col.g * 0.8, solarize_strength * 0.8);
+ }
+ if (col.b < thr) {
+ col.b = mix(col.b, 1.0 - col.b, solarize_strength);
+ }
+ }
+ return col;
+}
diff --git a/src/effects/vignette.wgsl b/src/effects/vignette.wgsl
new file mode 100644
index 0000000..9b98ec9
--- /dev/null
+++ b/src/effects/vignette.wgsl
@@ -0,0 +1,24 @@
+@group(0) @binding(0) var input_sampler: sampler;
+@group(0) @binding(1) var input_tex: texture_2d<f32>;
+#include "common_uniforms"
+
+struct VignetteParams {
+ radius: f32,
+ softness: f32,
+};
+
+@group(0) @binding(2) var<uniform> common_uniforms: CommonUniforms;
+@group(0) @binding(3) var<uniform> params: VignetteParams;
+
+#include "render/fullscreen_vs"
+
+@fragment
+fn fs_main(@builtin(position) pos: vec4f) -> @location(0) vec4f {
+ let uv = pos.xy / common_uniforms.resolution;
+ let color = textureSample(input_tex, input_sampler, uv);
+
+ let d = distance(uv, vec2f(0.5, 0.5));
+ let vignette = smoothstep(params.radius, params.radius - params.softness, d);
+
+ return vec4f(color.rgb * mix(1.0, vignette, common_uniforms.audio_intensity), color.a);
+} \ No newline at end of file