# SDF Effect Guide Workflow for SDF raymarching effects. Canonical example: `Scene1`. --- ## C++ Pattern Inherit from `Effect`. Add `UniformBuffer camera_params_` as a member, passed as `effect_params` to `pp_update_bind_group`. ```cpp // src/effects/my_sdf_effect.h #pragma once #include "gpu/camera_params.h" #include "gpu/effect.h" #include "gpu/uniform_helper.h" #include "gpu/wgpu_resource.h" class MySdfEffect : public Effect { public: MySdfEffect(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs, float start_time, float end_time); void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, NodeRegistry& nodes) override; private: RenderPipeline pipeline_; BindGroup bind_group_; UniformBuffer camera_params_; }; ``` ```cpp // src/effects/my_sdf_effect.cc #include "effects/my_sdf_effect.h" #include "effects/shaders.h" #include "gpu/gpu.h" #include "gpu/post_process_helper.h" #include "util/mini_math.h" MySdfEffect::MySdfEffect(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs, float start_time, float end_time) : Effect(ctx, inputs, outputs, start_time, end_time) { HEADLESS_RETURN_IF_NULL(ctx_.device); create_nearest_sampler(); create_dummy_scene_texture(); camera_params_.init(ctx_.device); pipeline_.set(create_post_process_pipeline( ctx_.device, WGPUTextureFormat_RGBA8Unorm, my_sdf_shader_wgsl)); } void MySdfEffect::render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, NodeRegistry& nodes) { // Update camera (orbiting example) CameraParams cam; const float R = 6.0f; const vec3 ro(R * sin(params.time * 0.5f), 2.0f, R * cos(params.time * 0.5f)); cam.inv_view = mat4::look_at(ro, vec3(0, 0, 0), vec3(0, 1, 0)).inverse(); cam.fov = 1.0472f; // 60 degrees cam.near_plane = 0.1f; cam.far_plane = 100.0f; cam.aspect_ratio = 1.0f; // aspect handled in shader camera_params_.update(ctx_.queue, cam); WGPUTextureView output_view = nodes.get_view(output_nodes_[0]); pp_update_bind_group(ctx_.device, pipeline_.get(), bind_group_.get_address(), dummy_texture_view_.get(), uniforms_buffer_.get(), camera_params_.get()); WGPURenderPassColorAttachment color_attachment = {}; gpu_init_color_attachment(color_attachment, output_view); WGPURenderPassDescriptor pass_desc = {}; pass_desc.colorAttachmentCount = 1; pass_desc.colorAttachments = &color_attachment; WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get()); wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr); wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Fullscreen triangle wgpuRenderPassEncoderEnd(pass); wgpuRenderPassEncoderRelease(pass); } ``` --- ## WGSL Pattern Binding layout (standard post-process): - `@binding(0)` — sampler (unused for SDF, but layout requires it) - `@binding(1)` — input texture (unused for SDF) - `@binding(2)` — `UniformsSequenceParams` - `@binding(3)` — `CameraParams` ```wgsl // workspaces/main/shaders/my_sdf.wgsl #include "sequence_uniforms" #include "camera_common" #include "math/sdf_shapes" #include "render/raymarching" @group(0) @binding(2) var uniforms: UniformsSequenceParams; @group(0) @binding(3) var camera: CameraParams; fn df(p: vec3f) -> f32 { return sdSphere(p, 1.0); } @vertex fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4f { let x = f32((vid & 1u) << 2u) - 1.0; let y = f32((vid & 2u) << 1u) - 1.0; return vec4f(x, y, 0.0, 1.0); } @fragment fn fs_main(@builtin(position) pos: vec4f) -> @location(0) vec4f { let uv = (pos.xy / uniforms.resolution - 0.5) * 2.0 * vec2f(uniforms.aspect_ratio, 1.0); let ray = getCameraRay(camera, uv); let t = rayMarch(ray.origin, ray.direction, 0.0); var col = vec3f(0.1); if (t < MAX_RAY_LENGTH) { let hit_pos = ray.origin + ray.direction * t; let n = normal(hit_pos); col = n * 0.5 + 0.5; } return vec4f(col, 1.0); } ``` --- ## Available Uniforms ### UniformsSequenceParams (binding 2) - `resolution`: vec2f - `time`: f32 (physical seconds) - `beat_time`: f32 (musical beats) - `beat_phase`: f32 (0–1 within beat) - `audio_intensity`: f32 - `aspect_ratio`: f32 ### CameraParams (binding 3) - `inv_view`: mat4x4f (inverse view matrix) - `fov`: f32 (vertical FOV, radians) - `near_plane`, `far_plane`: f32 - `aspect_ratio`: f32 --- ## WGSL Helpers ### `camera_common` ```wgsl fn getCameraRay(cam: CameraParams, uv: vec2f) -> Ray; fn getCameraPosition(cam: CameraParams) -> vec3f; fn getCameraForward(cam: CameraParams) -> vec3f; ``` ### `render/raymarching` ```wgsl fn rayMarch(ro: vec3f, rd: vec3f, initt: f32) -> f32; fn normal(pos: vec3f) -> vec3f; fn shadow(lp: vec3f, ld: vec3f, mint: f32, maxt: f32) -> f32; ``` ### `math/sdf_shapes` ```wgsl fn sdSphere(p: vec3f, r: f32) -> f32; fn sdBox(p: vec3f, b: vec3f) -> f32; fn sdTorus(p: vec3f, t: vec2f) -> f32; fn sdPlane(p: vec3f, n: vec3f, h: f32) -> f32; ``` --- ## Registration Checklist Follow `doc/EFFECT_WORKFLOW.md`. SDF-specific notes: 1. Add shader to `workspaces/main/assets.txt` 2. Add `.cc` to `CMakeLists.txt` GPU_SOURCES (**both** headless and normal sections) 3. Include header in `src/gpu/demo_effects.h` 4. Add to `workspaces/main/timeline.seq` with priority modifier (`+`, `=`, or `-`) 5. Add to `src/tests/gpu/test_demo_effects.cc` under `scene_effects` 6. Build: `cmake --build build -j4 && cd build && ./test_demo_effects`