summaryrefslogtreecommitdiff
path: root/workspaces
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-10 18:19:30 +0100
committerskal <pascal.massimino@gmail.com>2026-02-10 18:19:30 +0100
commit225ff0313c5021c3f1ec2bf5dd61114032b925a7 (patch)
tree33fce14571651158a409d9acf8494cff69dfe8dc /workspaces
parent532220d2abb2b48307f049c4ad5f545d4c8934d6 (diff)
feat: Add Scene1 effect from ShaderToy (raymarching cube & sphere)
Converted ShaderToy shader (Saturday cubism experiment) to Scene1Effect following EFFECT_WORKFLOW.md automation guidelines. **Changes:** - Created Scene1Effect (.h, .cc) as scene effect (not post-process) - Converted GLSL to WGSL with manual fixes: - Replaced RESOLUTION/iTime with uniforms.resolution/time - Fixed const expressions (normalize not allowed in const) - Converted mainImage() to fs_main() return value - Manual matrix rotation for scene transformation - Added shader asset to workspaces/main/assets.txt - Registered in CMakeLists.txt (both GPU_SOURCES sections) - Added to demo_effects.h and shaders declarations - Added to timeline.seq at 22.5s for 10s duration - Added to test_demo_effects.cc scene_effects list **Shader features:** - Raymarching cube and sphere with ground plane - Reflections and soft shadows - Sky rendering with sun and horizon glow - ACES tonemapping and sRGB output - Time-based rotation animation **Tests:** All effects tests passing (5/9 scene, 9/9 post-process) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'workspaces')
-rw-r--r--workspaces/main/assets.txt1
-rw-r--r--workspaces/main/shaders/scene1.wgsl256
-rw-r--r--workspaces/main/timeline.seq3
3 files changed, 260 insertions, 0 deletions
diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt
index 53c8b3e..af8b9e9 100644
--- a/workspaces/main/assets.txt
+++ b/workspaces/main/assets.txt
@@ -67,3 +67,4 @@ SHADER_COMPUTE_GEN_MASK, NONE, shaders/compute/gen_mask.wgsl, "GPU Mask Composit
CIRCLE_MASK_COMPUTE_SHADER, NONE, shaders/circle_mask_compute.wgsl, "Circle mask compute shader"
CIRCLE_MASK_RENDER_SHADER, NONE, shaders/circle_mask_render.wgsl, "Circle mask render shader"
MASKED_CUBE_SHADER, NONE, shaders/masked_cube.wgsl, "Masked cube shader"
+SHADER_SCENE1, NONE, shaders/scene1.wgsl, "Scene1 effect shader"
diff --git a/workspaces/main/shaders/scene1.wgsl b/workspaces/main/shaders/scene1.wgsl
new file mode 100644
index 0000000..b0ec2ab
--- /dev/null
+++ b/workspaces/main/shaders/scene1.wgsl
@@ -0,0 +1,256 @@
+// Scene1 effect shader - ShaderToy conversion (raymarching cube & sphere)
+// Source: Saturday cubism experiment by skal
+
+#include "common_uniforms"
+
+@group(0) @binding(0) var<uniform> uniforms: CommonUniforms;
+
+const PI: f32 = 3.141592654;
+const TAU: f32 = 6.283185307;
+const TOLERANCE: f32 = 0.0005;
+const MAX_RAY_LENGTH: f32 = 20.0;
+const MAX_RAY_MARCHES: i32 = 80;
+const MAX_SHD_MARCHES: i32 = 20;
+const NORM_OFF: f32 = 0.005;
+
+fn rot(a: f32) -> mat2x2<f32> {
+ let c = cos(a);
+ let s = sin(a);
+ return mat2x2<f32>(c, s, -s, c);
+}
+
+// HSV to RGB conversion
+const hsv2rgb_K = vec4<f32>(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+fn hsv2rgb(c: vec3<f32>) -> vec3<f32> {
+ let p = abs(fract(c.xxx + hsv2rgb_K.xyz) * 6.0 - hsv2rgb_K.www);
+ return c.z * mix(hsv2rgb_K.xxx, clamp(p - hsv2rgb_K.xxx, vec3<f32>(0.0), vec3<f32>(1.0)), c.y);
+}
+
+// Colors (precomputed HSV conversions)
+const skyCol = vec3<f32>(0.176, 0.235, 0.25); // HSV(0.57, 0.90, 0.25)
+const skylineCol = vec3<f32>(0.5, 0.125, 0.025); // HSV(0.02, 0.95, 0.5)
+const sunCol = vec3<f32>(0.5, 0.163, 0.025); // HSV(0.07, 0.95, 0.5)
+const diffCol1 = vec3<f32>(0.4, 1.0, 1.0); // HSV(0.60, 0.90, 1.0)
+const diffCol2 = vec3<f32>(0.325, 1.0, 0.975); // HSV(0.55, 0.90, 1.0)
+
+// Lighting (normalized manually)
+const sunDir1 = vec3<f32>(0.0, 0.04997, -0.99875); // normalize(0, 0.05, -1)
+const lightPos1 = vec3<f32>(10.0, 10.0, 10.0);
+const lightPos2 = vec3<f32>(-10.0, 10.0, -10.0);
+
+fn sRGB(t: vec3<f32>) -> vec3<f32> {
+ return mix(1.055 * pow(t, vec3<f32>(1.0/2.4)) - 0.055, 12.92 * t, step(t, vec3<f32>(0.0031308)));
+}
+
+fn aces_approx(v_in: vec3<f32>) -> vec3<f32> {
+ var v = max(v_in, vec3<f32>(0.0));
+ v *= 0.6;
+ let a = 2.51;
+ let b = 0.03;
+ let c = 2.43;
+ let d = 0.59;
+ let e = 0.14;
+ return clamp((v * (a * v + b)) / (v * (c * v + d) + e), vec3<f32>(0.0), vec3<f32>(1.0));
+}
+
+fn tanh_approx(x: f32) -> f32 {
+ let x2 = x * x;
+ return clamp(x * (27.0 + x2) / (27.0 + 9.0 * x2), -1.0, 1.0);
+}
+
+fn rayPlane(ro: vec3<f32>, rd: vec3<f32>, plane: vec4<f32>) -> f32 {
+ return -(dot(ro, plane.xyz) + plane.w) / dot(rd, plane.xyz);
+}
+
+fn box2d(p: vec2<f32>, b: vec2<f32>) -> f32 {
+ let d = abs(p) - b;
+ return length(max(d, vec2<f32>(0.0))) + min(max(d.x, d.y), 0.0);
+}
+
+fn box3d(p: vec3<f32>, b: vec3<f32>) -> f32 {
+ let q = abs(p) - b;
+ return length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0);
+}
+
+fn sphere(p: vec3<f32>, r: f32) -> f32 {
+ return length(p) - r;
+}
+
+var<private> g_rot0: mat2x2<f32>;
+
+fn render0(ro: vec3<f32>, rd: vec3<f32>) -> vec3<f32> {
+ var col = vec3<f32>(0.0);
+ var sf = 1.0001 - max(dot(sunDir1, rd), 0.0);
+ col += skyCol * pow((1.0 - abs(rd.y)), 8.0);
+ col += clamp(vec3<f32>(mix(0.0025, 0.125, tanh_approx(0.005 / sf)) / abs(rd.y)) * skylineCol, vec3<f32>(0.0), vec3<f32>(10.0));
+ sf *= sf;
+ col += sunCol * 0.00005 / sf;
+
+ let tp1 = rayPlane(ro, rd, vec4<f32>(0.0, -1.0, 0.0, 6.0));
+ if (tp1 > 0.0) {
+ let pos = ro + tp1 * rd;
+ let pp = pos.xz;
+ let db = box2d(pp, vec2<f32>(5.0, 9.0)) - 3.0;
+ col += vec3<f32>(4.0) * skyCol * rd.y * rd.y * smoothstep(0.25, 0.0, db);
+ col += vec3<f32>(0.8) * skyCol * exp(-0.5 * max(db, 0.0));
+ }
+
+ return clamp(col, vec3<f32>(0.0), vec3<f32>(10.0));
+}
+
+fn df(p_in: vec3<f32>) -> f32 {
+ var p = p_in;
+ p.x = p_in.x * g_rot0[0][0] + p_in.z * g_rot0[0][1];
+ p.z = p_in.x * g_rot0[1][0] + p_in.z * g_rot0[1][1];
+
+ // Cube
+ var pc = p;
+ pc -= vec3<f32>(-1.9, 0.0, 0.0);
+ let dCube = box3d(pc, vec3<f32>(1.6));
+
+ // Sphere
+ var ps = p;
+ ps -= vec3<f32>(1.3, 0.0, 0.0);
+ let dSphere = sphere(ps, 1.2);
+
+ // Ground plane
+ let dPlane = p.y + 1.0;
+
+ // Union
+ var d = min(dCube, dSphere);
+ d = min(d, dPlane);
+
+ return d;
+}
+
+fn normal(pos: vec3<f32>) -> vec3<f32> {
+ let eps = vec2<f32>(NORM_OFF, 0.0);
+ var nor: vec3<f32>;
+ nor.x = df(pos + eps.xyy) - df(pos - eps.xyy);
+ nor.y = df(pos + eps.yxy) - df(pos - eps.yxy);
+ nor.z = df(pos + eps.yyx) - df(pos - eps.yyx);
+ return normalize(nor);
+}
+
+fn rayMarch(ro: vec3<f32>, rd: vec3<f32>, initt: f32) -> f32 {
+ var t = initt;
+ for (var i = 0; i < MAX_RAY_MARCHES; i++) {
+ if (t > MAX_RAY_LENGTH) {
+ t = MAX_RAY_LENGTH;
+ break;
+ }
+ let d = df(ro + rd * t);
+ if (d < TOLERANCE) {
+ break;
+ }
+ t += d;
+ }
+ return t;
+}
+
+fn shadow(lp: vec3<f32>, ld: vec3<f32>, mint: f32, maxt: f32) -> f32 {
+ let ds = 1.0 - 0.4;
+ var t = mint;
+ var nd = 1e6;
+ let soff = 0.05;
+ let smul = 1.5;
+ for (var i = 0; i < MAX_SHD_MARCHES; i++) {
+ let p = lp + ld * t;
+ let d = df(p);
+ if (d < TOLERANCE || t >= maxt) {
+ let sd = 1.0 - exp(-smul * max(t / maxt - soff, 0.0));
+ return select(mix(sd, 1.0, smoothstep(0.0, 0.025, nd)), sd, t >= maxt);
+ }
+ nd = min(nd, d);
+ t += ds * d;
+ }
+ let sd = 1.0 - exp(-smul * max(t / maxt - soff, 0.0));
+ return sd;
+}
+
+fn boxCol(col: vec3<f32>, nsp: vec3<f32>, rd: vec3<f32>, nnor: vec3<f32>, nrcol: vec3<f32>, nshd1: f32, nshd2: f32) -> vec3<f32> {
+ 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 = vec3<f32>(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(vec3<f32>(0.25), vec3<f32>(0.5, 0.5, 1.0), nfre);
+
+ return mix(col, scol, rf * smoothstep(90.0, 20.0, dot(nsp, nsp)));
+}
+
+fn render1(ro: vec3<f32>, rd: vec3<f32>) -> vec3<f32> {
+ let skyCol_local = render0(ro, rd);
+ var col = skyCol_local;
+
+ let nt = rayMarch(ro, rd, 0.0);
+ if (nt < MAX_RAY_LENGTH) {
+ let nsp = ro + rd * nt;
+ let nnor = normal(nsp);
+
+ let nref = reflect(rd, nnor);
+ let nrt = rayMarch(nsp, nref, 0.2);
+ var nrcol = render0(nsp, nref);
+
+ if (nrt < MAX_RAY_LENGTH) {
+ let nrsp = nsp + nref * nrt;
+ let nrnor = normal(nrsp);
+ let nrref = reflect(nref, nrnor);
+ nrcol = boxCol(nrcol, nrsp, nref, nrnor, render0(nrsp, nrref), 1.0, 1.0);
+ }
+
+ let nshd1 = mix(0.0, 1.0, shadow(nsp, normalize(lightPos1 - nsp), 0.1, distance(lightPos1, nsp)));
+ let nshd2 = mix(0.0, 1.0, shadow(nsp, normalize(lightPos2 - nsp), 0.1, distance(lightPos2, nsp)));
+
+ col = boxCol(col, nsp, rd, nnor, nrcol, nshd1, nshd2);
+ }
+
+ return col;
+}
+
+fn effect(p: vec2<f32>) -> vec3<f32> {
+ g_rot0 = rot(-0.2 * uniforms.time);
+
+ let fov = tan(TAU / 6.0);
+ let ro = vec3<f32>(0.0, 2.5, 5.0);
+ let la = vec3<f32>(0.0, 0.0, 0.0);
+ let up = vec3<f32>(0.1, 1.0, 0.0);
+
+ let ww = normalize(la - ro);
+ let uu = normalize(cross(up, ww));
+ let vv = cross(ww, uu);
+ let rd = normalize(-p.x * uu + p.y * vv + fov * ww);
+
+ return render1(ro, rd);
+}
+
+@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
+ var pos = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>(3.0, -1.0),
+ vec2<f32>(-1.0, 3.0)
+ );
+ return vec4<f32>(pos[i], 0.0, 1.0);
+}
+
+@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> {
+ let q = p.xy / uniforms.resolution;
+ var coord = -1.0 + 2.0 * q;
+ coord.x *= uniforms.resolution.x / uniforms.resolution.y;
+ var col = effect(coord);
+ col = aces_approx(col);
+ col = sRGB(col);
+ return vec4<f32>(col, 1.0);
+}
diff --git a/workspaces/main/timeline.seq b/workspaces/main/timeline.seq
index 083e60a..5cf5574 100644
--- a/workspaces/main/timeline.seq
+++ b/workspaces/main/timeline.seq
@@ -39,6 +39,9 @@ SEQUENCE 10.50 0 "CNN effect"
# EFFECT + Hybrid3DEffect 0.00 12.00
EFFECT + CNNEffect 1.0 12.0 layers=3 blend=1.5
+SEQUENCE 22.50 0 "Scene1 demo"
+ EFFECT + Scene1Effect 0.0 10.0
+
SEQUENCE 22.0 0 "buggy"
EFFECT + HeptagonEffect 0.00 0.20
EFFECT + FadeEffect 0.11 1.01