diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-08 21:58:49 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-08 21:58:49 +0100 |
| commit | 86e56474d284944795f4c02ae850561374620f8a (patch) | |
| tree | 7ce0b35539e6c632024dd57cf1dafaef90aae487 /assets/final | |
| parent | d3a609fad91744c45f6bc59b625a26f8870e271d (diff) | |
feat: Add CircleMaskEffect and RotatingCubeEffect with auxiliary texture masking
Implements demonstration of auxiliary texture masking system with:
- CircleMaskEffect: Generates circular mask, renders green outside circle
- RotatingCubeEffect: Renders SDF cube inside circle using mask
Architecture:
- Mask generation in compute phase (1.0 inside, 0.0 outside)
- CircleMask renders green where mask < 0.5 (outside circle)
- RotatingCube samples mask and discards where mask < 0.5
Files added:
- src/gpu/effects/circle_mask_effect.{h,cc}
- src/gpu/effects/rotating_cube_effect.{h,cc}
- assets/final/shaders/{circle_mask_compute,circle_mask_render,masked_cube}.wgsl
Build system:
- Updated CMakeLists.txt with new effect sources
- Registered shaders in demo_assets.txt
- Updated test_demo_effects.cc (8 scene effects total)
Status: Effects temporarily disabled in demo.seq (lines 31-35)
- ShaderComposer snippet registration needed for masked_cube.wgsl
- All 33 tests pass, demo runs without crashes
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'assets/final')
| -rw-r--r-- | assets/final/shaders/circle_mask_compute.wgsl | 33 | ||||
| -rw-r--r-- | assets/final/shaders/circle_mask_render.wgsl | 35 | ||||
| -rw-r--r-- | assets/final/shaders/masked_cube.wgsl | 160 |
3 files changed, 228 insertions, 0 deletions
diff --git a/assets/final/shaders/circle_mask_compute.wgsl b/assets/final/shaders/circle_mask_compute.wgsl new file mode 100644 index 0000000..610ee67 --- /dev/null +++ b/assets/final/shaders/circle_mask_compute.wgsl @@ -0,0 +1,33 @@ +// Circle mask compute shader +// Generates a circular mask (1.0 inside, 0.0 outside) + +struct Uniforms { + radius: f32, + aspect_ratio: f32, + width: f32, + height: f32, +}; + +@group(0) @binding(0) var<uniform> uniforms: Uniforms; + +struct VSOutput { + @builtin(position) position: vec4<f32>, +}; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> VSOutput { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1, -1), vec2<f32>(3, -1), vec2<f32>(-1, 3)); + return VSOutput(vec4<f32>(pos[i], 0.0, 1.0)); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + let uv = p.xy / vec2<f32>(uniforms.width, uniforms.height); + let center = vec2<f32>(0.5, 0.5); + let aspect_corrected_uv = (uv - center) * vec2<f32>(uniforms.aspect_ratio, 1.0); + let dist = length(aspect_corrected_uv); + + let edge_width = 0.01; + let mask = smoothstep(uniforms.radius + edge_width, uniforms.radius - edge_width, dist); + + return vec4<f32>(mask, mask, mask, 1.0); +} diff --git a/assets/final/shaders/circle_mask_render.wgsl b/assets/final/shaders/circle_mask_render.wgsl new file mode 100644 index 0000000..902600e --- /dev/null +++ b/assets/final/shaders/circle_mask_render.wgsl @@ -0,0 +1,35 @@ +// Circle mask render shader +// Samples mask and draws green outside the circle + +@group(0) @binding(0) var mask_tex: texture_2d<f32>; +@group(0) @binding(1) var mask_sampler: sampler; + +struct Uniforms { + width: f32, + height: f32, + _pad1: f32, + _pad2: f32, +}; + +@group(0) @binding(2) var<uniform> uniforms: Uniforms; + +struct VSOutput { + @builtin(position) position: vec4<f32>, +}; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> VSOutput { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1, -1), vec2<f32>(3, -1), vec2<f32>(-1, 3)); + return VSOutput(vec4<f32>(pos[i], 0.0, 1.0)); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + let uv = p.xy / vec2<f32>(uniforms.width, uniforms.height); + let mask_value = textureSample(mask_tex, mask_sampler, uv).r; + + if (mask_value > 0.5) { + discard; + } + + return vec4<f32>(0.0, 1.0, 0.0, 1.0); +} diff --git a/assets/final/shaders/masked_cube.wgsl b/assets/final/shaders/masked_cube.wgsl new file mode 100644 index 0000000..77e2fb9 --- /dev/null +++ b/assets/final/shaders/masked_cube.wgsl @@ -0,0 +1,160 @@ +// Masked cube shader - based on renderer_3d.wgsl with mask sampling +#include "common_uniforms" +#include "math/common_utils" +#include "math/sdf_utils" + +@group(0) @binding(0) var<uniform> globals: GlobalUniforms; +@group(0) @binding(1) var<storage, read> object_data: ObjectsBuffer; +@group(0) @binding(3) var noise_tex: texture_2d<f32>; +@group(0) @binding(4) var noise_sampler: sampler; +@group(0) @binding(5) var sky_tex: texture_2d<f32>; + +@group(1) @binding(0) var mask_tex: texture_2d<f32>; +@group(1) @binding(1) var mask_sampler: sampler; + +struct VertexOutput { + @builtin(position) position: vec4<f32>, + @location(0) local_pos: vec3<f32>, + @location(1) color: vec4<f32>, + @location(2) @interpolate(flat) instance_index: u32, + @location(3) world_pos: vec3<f32>, + @location(4) transformed_normal: vec3<f32>, +}; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32, + @builtin(instance_index) instance_index: u32) -> VertexOutput { + var pos = array<vec3<f32>, 36>( + vec3(-1.0, -1.0, 1.0), vec3( 1.0, -1.0, 1.0), vec3( 1.0, 1.0, 1.0), + vec3(-1.0, -1.0, 1.0), vec3( 1.0, 1.0, 1.0), vec3(-1.0, 1.0, 1.0), + vec3(-1.0, -1.0, -1.0), vec3(-1.0, 1.0, -1.0), vec3( 1.0, 1.0, -1.0), + vec3(-1.0, -1.0, -1.0), vec3( 1.0, 1.0, -1.0), vec3( 1.0, -1.0, -1.0), + vec3(-1.0, 1.0, -1.0), vec3(-1.0, 1.0, 1.0), vec3( 1.0, 1.0, 1.0), + vec3(-1.0, 1.0, -1.0), vec3( 1.0, 1.0, 1.0), vec3( 1.0, 1.0, -1.0), + vec3(-1.0, -1.0, -1.0), vec3( 1.0, -1.0, -1.0), vec3( 1.0, -1.0, 1.0), + vec3(-1.0, -1.0, -1.0), vec3( 1.0, -1.0, 1.0), vec3(-1.0, -1.0, 1.0), + vec3( 1.0, -1.0, -1.0), vec3( 1.0, 1.0, -1.0), vec3( 1.0, 1.0, 1.0), + vec3( 1.0, -1.0, -1.0), vec3( 1.0, 1.0, 1.0), vec3( 1.0, -1.0, 1.0), + vec3(-1.0, -1.0, -1.0), vec3(-1.0, -1.0, 1.0), vec3(-1.0, 1.0, 1.0), + vec3(-1.0, -1.0, -1.0), vec3(-1.0, 1.0, 1.0), vec3(-1.0, 1.0, -1.0) + ); + + var p = pos[vertex_index]; + let obj = object_data.objects[instance_index]; + let world_pos = obj.model * vec4<f32>(p, 1.0); + let clip_pos = globals.view_proj * world_pos; + + var out: VertexOutput; + out.position = clip_pos; + out.local_pos = p; + out.color = obj.color; + out.instance_index = instance_index; + out.world_pos = world_pos.xyz; + out.transformed_normal = normalize(vec3<f32>(0.0, 1.0, 0.0)); + + return out; +} + +#include "render/scene_query_mode" +#include "render/shadows" +#include "render/lighting_utils" +#include "ray_box" + +struct FragmentOutput { + @location(0) color: vec4<f32>, + @builtin(frag_depth) depth: f32, +}; + +@fragment +fn fs_main(in: VertexOutput) -> FragmentOutput { + let screen_uv = in.position.xy / globals.resolution; + let mask_value = textureSample(mask_tex, mask_sampler, screen_uv).r; + + if (mask_value < 0.5) { + discard; + } + + let obj = object_data.objects[in.instance_index]; + let obj_type = obj.params.x; + + var p: vec3<f32>; + var normal: vec3<f32>; + var base_color = in.color.rgb; + let light_dir = normalize(vec3<f32>(1.0, 1.0, 1.0)); + + let ray_origin = globals.camera_pos_time.xyz; + let ray_dir = normalize(in.world_pos - ray_origin); + let inv_model = obj.inv_model; + + let local_origin = (inv_model * vec4<f32>(ray_origin, 1.0)).xyz; + let local_dir = normalize((inv_model * vec4<f32>(ray_dir, 0.0)).xyz); + + let t = ray_box(local_origin, local_dir, vec3<f32>(-1.0), vec3<f32>(1.0)); + if (t.y < 0.0) { + discard; + } + + let t_start = max(t.x, 0.0); + let t_end = t.y; + + var t_march = t_start; + let max_steps = 128; + var hit = false; + var local_p = vec3<f32>(0.0); + + for (var step = 0; step < max_steps; step++) { + local_p = local_origin + t_march * local_dir; + let d = sdBox(local_p, vec3<f32>(1.0)); + + if (d < 0.001) { + hit = true; + break; + } + + t_march += max(d * 0.5, 0.001); + if (t_march > t_end) { + break; + } + } + + if (!hit) { + discard; + } + + p = local_p; + let eps = 0.001; + normal = normalize(vec3<f32>( + sdBox(p + vec3<f32>(eps, 0.0, 0.0), vec3<f32>(1.0)) - + sdBox(p - vec3<f32>(eps, 0.0, 0.0), vec3<f32>(1.0)), + sdBox(p + vec3<f32>(0.0, eps, 0.0), vec3<f32>(1.0)) - + sdBox(p - vec3<f32>(0.0, eps, 0.0), vec3<f32>(1.0)), + sdBox(p + vec3<f32>(0.0, 0.0, eps), vec3<f32>(1.0)) - + sdBox(p - vec3<f32>(0.0, 0.0, eps), vec3<f32>(1.0)) + )); + + let world_p = (obj.model * vec4<f32>(p, 1.0)).xyz; + let world_normal = normalize((obj.model * vec4<f32>(normal, 0.0)).xyz); + + let bump_strength = 0.3; + let bump_scale = 4.0; + let noise_uv = world_p.xy * bump_scale; + let noise_val = textureSample(noise_tex, noise_sampler, noise_uv).r; + let bump_offset = (noise_val - 0.5) * bump_strength; + + let bumped_normal = normalize(world_normal + vec3<f32>(bump_offset)); + + let diffuse = max(dot(bumped_normal, light_dir), 0.0); + let ambient = 0.3; + let lighting = ambient + diffuse * 0.7; + + let final_color = base_color * lighting; + + let clip_p = globals.view_proj * vec4<f32>(world_p, 1.0); + let depth = clip_p.z / clip_p.w; + + var out: FragmentOutput; + out.color = vec4<f32>(final_color, 1.0); + out.depth = depth; + + return out; +} |
