From b9c2a0394343ff3586880d118b7d549b3e748cad Mon Sep 17 00:00:00 2001 From: skal Date: Sat, 28 Feb 2026 11:50:13 +0100 Subject: 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 --- src/effects/scene1.wgsl | 165 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/effects/scene1.wgsl (limited to 'src/effects/scene1.wgsl') 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 uniforms: UniformsSequenceParams; +@group(0) @binding(3) var 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); +} -- cgit v1.2.3