diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-28 11:50:13 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-28 11:50:13 +0100 |
| commit | b9c2a0394343ff3586880d118b7d549b3e748cad (patch) | |
| tree | bfc437f805c6b7344951107df8c7cd69a7ec421f /src | |
| parent | 21d8a0b86ceda19812e9869a72e49c56c90ae3da (diff) | |
refactor(effects): co-locate effect WGSL shaders with their .h/.cc in src/effects/
Move 13 effect-specific shaders from workspaces/main/shaders/ to src/effects/
so each effect's .h, .cc, and .wgsl are together. Update assets.txt paths in
both main and test workspaces. Update EFFECT_WORKFLOW.md to reflect new location.
Shared/reusable snippets remain in src/shaders/.
handoff(Gemini): shaders moved; src/effects/ now has .h, .cc, and .wgsl per effect.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/effects/chroma_aberration.wgsl | 24 | ||||
| -rw-r--r-- | src/effects/distort.wgsl | 20 | ||||
| -rw-r--r-- | src/effects/ellipse.wgsl | 12 | ||||
| -rw-r--r-- | src/effects/flash.wgsl | 27 | ||||
| -rw-r--r-- | src/effects/gaussian_blur.wgsl | 33 | ||||
| -rw-r--r-- | src/effects/particle_compute.wgsl | 31 | ||||
| -rw-r--r-- | src/effects/particle_render.wgsl | 53 | ||||
| -rw-r--r-- | src/effects/particle_spray_compute.wgsl | 35 | ||||
| -rw-r--r-- | src/effects/rotating_cube.wgsl | 89 | ||||
| -rw-r--r-- | src/effects/scene1.wgsl | 165 | ||||
| -rw-r--r-- | src/effects/scene2.wgsl | 37 | ||||
| -rw-r--r-- | src/effects/solarize.wgsl | 46 | ||||
| -rw-r--r-- | src/effects/vignette.wgsl | 24 |
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 |
