# SDF Effect Guide Streamlined workflow for SDF raymarching effects using the `SDFEffect` base class. --- ## Quick Start ```cpp // src/effects/my_sdf_effect.h class MySDFEffect : public SDFEffect { MySDFEffect(const GpuContext& ctx); void render(WGPURenderPassEncoder pass, const CommonPostProcessUniforms& uniforms) override; RenderPass pass_; }; ``` ```cpp // src/effects/my_sdf_effect.cc #include "effects/my_sdf_effect.h" #include "gpu/gpu.h" #include "gpu/shaders.h" MySDFEffect::MySDFEffect(const GpuContext& ctx) : SDFEffect(ctx) { ResourceBinding bindings[] = { {uniforms_.get(), WGPUBufferBindingType_Uniform}, {camera_params_.get(), WGPUBufferBindingType_Uniform}}; pass_ = gpu_create_render_pass(ctx_.device, ctx_.format, my_sdf_shader_wgsl, bindings, 2); pass_.vertex_count = 3; } void MySDFEffect::render(WGPURenderPassEncoder pass, const CommonPostProcessUniforms& uniforms) { uniforms_.update(ctx_.queue, uniforms); // Orbiting camera vec3 cam_pos(std::cos(uniforms.time * 0.5f) * 5.0f, 2.0f, std::sin(uniforms.time * 0.5f) * 5.0f); update_camera(cam_pos, vec3(0, 0, 0), vec3(0, 1, 0), 0.785398f, 0.1f, 100.0f, uniforms.aspect_ratio); wgpuRenderPassEncoderSetPipeline(pass, pass_.pipeline); wgpuRenderPassEncoderSetBindGroup(pass, 0, pass_.bind_group, 0, nullptr); wgpuRenderPassEncoderDraw(pass, pass_.vertex_count, 1, 0, 0); } ``` ```wgsl // workspaces/main/shaders/my_sdf.wgsl #include "common_uniforms" #include "camera_common" #include "math/sdf_shapes" #include "render/raymarching" @group(0) @binding(0) var uniforms: CommonUniforms; @group(0) @binding(1) var camera: CameraParams; fn df(p: vec3) -> f32 { return sdSphere(p, 1.0); } @vertex fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4 { let x = f32((vid & 1u) << 2u) - 1.0; let y = f32((vid & 2u) << 1u) - 1.0; return vec4(x, y, 0.0, 1.0); } @fragment fn fs_main(@builtin(position) pos: vec4) -> @location(0) vec4 { let uv = (pos.xy / uniforms.resolution - 0.5) * 2.0; let ray = getCameraRay(camera, uv); let t = rayMarch(ray.origin, ray.direction, 0.0); var col = vec3(0.1); if (t < MAX_RAY_LENGTH) { let hit_pos = ray.origin + ray.direction * t; let n = normal(hit_pos); col = vec3(n * 0.5 + 0.5); } return vec4(col, 1.0); } ``` --- ## Available Uniforms ### CommonUniforms (binding 0) - `resolution`: vec2 (screen size) - `time`: float (physical seconds) - `beat_time`: float (musical beats) - `beat_phase`: float (0-1 within beat) - `audio_intensity`: float (peak) - `aspect_ratio`: float ### CameraParams (binding 1) - `inv_view`: mat4x4 (inverse view matrix) - `fov`: float (vertical FOV in radians) - `near_plane`, `far_plane`: float - `aspect_ratio`: float --- ## WGSL Helpers From `camera_common.wgsl`: ```wgsl fn getCameraRay(cam: CameraParams, uv: vec2) -> Ray; fn getCameraPosition(cam: CameraParams) -> vec3; fn getCameraForward(cam: CameraParams) -> vec3; fn getCameraUp(cam: CameraParams) -> vec3; fn getCameraRight(cam: CameraParams) -> vec3; ``` From `render/raymarching.wgsl`: ```wgsl fn rayMarch(ro: vec3, rd: vec3, initt: f32) -> f32; fn normal(pos: vec3) -> vec3; fn shadow(lp: vec3, ld: vec3, mint: f32, maxt: f32) -> f32; ``` From `math/sdf_shapes.wgsl`: ```wgsl fn sdSphere(p: vec3, r: float) -> f32; fn sdBox(p: vec3, b: vec3) -> f32; fn sdTorus(p: vec3, t: vec2) -> f32; fn sdPlane(p: vec3, n: vec3, h: f32) -> f32; ``` --- ## Camera Control ```cpp // Method 1: Manual values update_camera(position, target, up, fov, near, far, aspect); // Method 2: Camera object Camera cam; cam.position = vec3(0, 5, 10); cam.target = vec3(0, 0, 0); update_camera(cam, uniforms.aspect_ratio); ``` --- ## Registration Checklist 1. Add shader to `workspaces/main/assets.txt` 2. Add extern declaration to `src/gpu/shaders.h` 3. Add definition to `src/gpu/shaders.cc` 4. Add `.cc` to `cmake/DemoSourceLists.cmake` (both headless & normal) 5. Include header in `src/gpu/demo_effects.h` 6. Add to `src/tests/gpu/test_demo_effects.cc` --- ## Example: workspaces/main/shaders/sdf_test.wgsl