summaryrefslogtreecommitdiff
path: root/cnn_v3/shaders/gbuf_shadow.wgsl
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-03-22 23:17:50 +0100
committerskal <pascal.massimino@gmail.com>2026-03-22 23:17:50 +0100
commit8fd3eda0ed069b1a817261f8f4d6a35c565b3fe4 (patch)
treeba7a61571d6e08d580ce0db9cddb9ad21584da75 /cnn_v3/shaders/gbuf_shadow.wgsl
parenta2697faa005337c4d8e8e6376d9e57edadf63f44 (diff)
fix(cnn_v3): shadow pass — 5 bugs fixed, labels in gbuf_view
1. Camera Y-inversion: proj.m[5] = -proj.m[5] in upload_scene_data + WGPUFrontFace_CCW on raster pipeline. 2. Shadow formula: replace shadowWithStoredDistance with 64-step IQ soft shadow (8*d/t, unbounded). 3. Local→world SDF scale: d *= length(obj.model[0].xyz). 4. Shadow bias: use rasterized normal from normal_mat_tex (binding 4) instead of light direction — fixes terminator self-shadow on spheres. 5. ShaderComposer: GBufViewEffect now resolves #include via ShaderComposer::Get().Compose(). Also: per-tile channel labels in gbuf_view.wgsl via debug_str. Scene simplified to 1 cube + 1 sphere for debugging (restore TODO). Scale propagation for pulsating sphere confirmed correct end-to-end. handoff(Gemini): shadow validated. Next: restore full scene in GBufferEffect::set_scene() (20 cubes + 4 spheres, 2 lights), then run training pass per cnn_v3/docs/HOWTO.md §3.
Diffstat (limited to 'cnn_v3/shaders/gbuf_shadow.wgsl')
-rw-r--r--cnn_v3/shaders/gbuf_shadow.wgsl35
1 files changed, 27 insertions, 8 deletions
diff --git a/cnn_v3/shaders/gbuf_shadow.wgsl b/cnn_v3/shaders/gbuf_shadow.wgsl
index 0f5f8b4..735e47c 100644
--- a/cnn_v3/shaders/gbuf_shadow.wgsl
+++ b/cnn_v3/shaders/gbuf_shadow.wgsl
@@ -5,11 +5,13 @@
#include "common_uniforms"
#include "camera_common"
#include "math/sdf_shapes"
+#include "math/normal"
#include "render/raymarching_id"
@group(0) @binding(0) var<uniform> globals: GlobalUniforms;
@group(0) @binding(1) var<storage, read> object_data: ObjectsBuffer;
@group(0) @binding(2) var depth_tex: texture_depth_2d;
+@group(0) @binding(4) var normal_mat_tex: texture_2d<f32>;
struct GBufLight {
direction: vec4f, // xyz = toward light (world space, normalized)
@@ -38,12 +40,14 @@ fn dfWithID(p: vec3f) -> RayMarchResult {
let obj = object_data.objects[i];
let lp = (obj.inv_model * vec4f(p, 1.0)).xyz;
let obj_type = u32(obj.params.x);
+ // Scale factor: convert local-space SDF to world-space distance.
+ let scale = length(obj.model[0].xyz);
var d: f32;
switch obj_type {
- case 1u: { d = sdSphere(lp, 1.0); } // SPHERE
- case 2u: { d = sdPlane(lp, vec3f(0.0, 1.0, 0.0), obj.params.y); } // PLANE
- case 3u: { d = sdTorus(lp, vec2f(0.8, 0.2)); } // TORUS
- default: { d = sdBox(lp, vec3f(1.0)); } // CUBE (0) + fallback
+ case 1u: { d = sdSphere(lp, 1.0) * scale; } // SPHERE
+ case 2u: { d = sdPlane(lp, vec3f(0.0, 1.0, 0.0), obj.params.y); } // PLANE
+ case 3u: { d = sdTorus(lp, vec2f(0.8, 0.2)) * scale; } // TORUS
+ default: { d = sdBox(lp, vec3f(1.0)) * scale; } // CUBE (0) + fallback
}
if (d < res.distance) {
res.distance = d;
@@ -53,6 +57,20 @@ fn dfWithID(p: vec3f) -> RayMarchResult {
return res;
}
+// Soft shadow march (IQ formula). Returns 1=lit, 0=shadow.
+// No dmin/dmax bounds: in open space d grows large so 8*d/t >> 1, res stays 1 naturally.
+fn soft_shadow(ro: vec3f, rd: vec3f) -> f32 {
+ var t = 0.001;
+ var res = 1.0;
+ for (var i = 0; i < 64; i++) {
+ let d = dfWithID(ro + rd * t).distance;
+ if (d < 0.0005) { return 0.0; }
+ res = min(res, 8.0 * d / t);
+ t += d;
+ }
+ return clamp(res, 0.0, 1.0);
+}
+
// ---- Vertex: fullscreen triangle ----
@vertex
@@ -82,16 +100,17 @@ fn fs_main(@builtin(position) pos: vec4f) -> @location(0) vec4f {
let clip = globals.inv_view_proj * vec4f(ndc, depth, 1.0);
let world = clip.xyz / clip.w;
- // Surface normal estimated from SDF gradient.
- let nor = normalWithID(world);
- let bias_pos = world + nor * 0.02;
+ // Use rasterized surface normal for bias — correct for sphere impostors.
+ let nm = textureLoad(normal_mat_tex, vec2i(pos.xy), 0);
+ let nor = oct_decode_unorm(nm.rg);
+ let bias_pos = world + nor * 0.05;
// March shadow rays toward each light; take the darkest value.
var shadow_val = 1.0;
let num_lights = u32(lights.params.x);
for (var i = 0u; i < num_lights; i++) {
let ld = lights.lights[i].direction.xyz;
- let s = shadowWithStoredDistance(bias_pos, ld, MAX_RAY_LENGTH);
+ let s = soft_shadow(bias_pos, ld);
shadow_val = min(shadow_val, s);
}