// BVLE Voxels - RT Shadow Compute Shader (Phase 6.2) // Traces shadow rays from each pixel toward the sun using inline ray queries. // Reads depth + normal to reconstruct world position, modulates voxelRT_ in-place. #include "voxelCommon.hlsli" // SRV bindings Texture2D depthTexture : register(t0); // voxelDepth_ (D32_FLOAT as R32_FLOAT SRV) Texture2D normalTexture : register(t1); // voxelNormalRT_ (R16G16B16A16_SNORM) RaytracingAccelerationStructure tlas : register(t2); // TLAS with blocky + smooth instances // UAV: read-modify-write voxelRT_ (each thread handles exactly one pixel, no race) RWTexture2D colorOutput : register(u0); // Push constants struct ShadowPush { uint width; uint height; float normalBias; float maxDistance; uint debugMode; // 0=normal, 1=debug visualization uint pad[7]; }; [[vk::push_constant]] ConstantBuffer push : register(b999); [RootSignature(VOXEL_ROOTSIG)] [numthreads(8, 8, 1)] void main(uint3 DTid : SV_DispatchThreadID) { if (DTid.x >= push.width || DTid.y >= push.height) return; float depth = depthTexture[DTid.xy]; // depth == 0 means sky (reverse-Z: 0 = far plane) if (depth == 0.0) { if (push.debugMode > 0) colorOutput[DTid.xy] = float4(0.1, 0.1, 0.1, 1); // dark gray = sky return; } // Reconstruct world position from depth via inverse VP float2 uv = (float2(DTid.xy) + 0.5) / float2(push.width, push.height); float2 ndc = float2(uv.x * 2.0 - 1.0, (1.0 - uv.y) * 2.0 - 1.0); float4 clipPos = float4(ndc, depth, 1.0); float4 worldPos4 = mul(inverseViewProjection, clipPos); float3 worldPos = worldPos4.xyz / worldPos4.w; // Read world-space normal float3 N = normalTexture[DTid.xy].xyz; // Light direction: sunDirection is the direction of travel, negate for "toward sun" float3 L = normalize(-sunDirection.xyz); // Skip surfaces facing away from the light (self-shadowed by geometry) float NdotL = dot(N, L); if (NdotL <= 0.0) { if (push.debugMode > 0) { colorOutput[DTid.xy] = float4(0.0, 0.0, 0.5, 1); // blue = back-facing } else { float4 color = colorOutput[DTid.xy]; color.rgb *= 0.3; colorOutput[DTid.xy] = color; } return; } // Offset ray origin along normal to avoid self-intersection float3 origin = worldPos + N * push.normalBias; RayDesc ray; ray.Origin = origin; ray.Direction = L; ray.TMin = 0.01; ray.TMax = push.maxDistance; // Inline ray query: accept first hit (binary shadow, don't need closest) RayQuery q; q.TraceRayInline(tlas, 0, 0xFF, ray); // With FLAG_OPAQUE geometry + ACCEPT_FIRST_HIT, Proceed() handles everything while (q.Proceed()) {} if (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) { if (push.debugMode > 0) { colorOutput[DTid.xy] = float4(1.0, 0.0, 0.0, 1); // RED = shadow ray hit (cast shadow!) } else { float4 color = colorOutput[DTid.xy]; color.rgb *= 0.3; colorOutput[DTid.xy] = color; } } else { if (push.debugMode > 0) { // Debug: trace downward ray from reconstructed worldPos to verify BLAS RayDesc testRay; testRay.Origin = worldPos + float3(0, 5, 0); // 5 units above surface testRay.Direction = float3(0, -1, 0); // straight down testRay.TMin = 0.01; testRay.TMax = 100.0; RayQuery testQ; testQ.TraceRayInline(tlas, 0, 0xFF, testRay); while (testQ.Proceed()) {} if (testQ.CommittedStatus() == COMMITTED_TRIANGLE_HIT) { colorOutput[DTid.xy] = float4(1, 0, 0, 1); // RED = hit (BLAS works!) } else { colorOutput[DTid.xy] = float4(0, 1, 0, 1); // GREEN = miss (BLAS broken) } } } }