Add shadow compute shader (voxelShadowCS.hlsl) that traces rays toward the sun using DXR inline ray queries (RayQuery<>, SM 6.5). Shadows modulate voxelRT_ in-place via RWTexture2D (no extra render target). Key fixes to Phase 6.1 BLAS/TLAS infrastructure: - Sequential index buffer required: Wicked treats IndexCount=0 with non-null IndexBuffer as "0 indexed triangles" → empty BLAS - Memory barriers between BLAS→TLAS→RT: without GPUBarrier::Memory() the TLAS build races with BLAS builds, causing zero ray hits - inverseViewProjection added to VoxelCB for depth reconstruction F5 toggles shadows OFF→ON→DEBUG (red=hit, green=miss, blue=backface).
105 lines
4 KiB
HLSL
105 lines
4 KiB
HLSL
// 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<float> depthTexture : register(t0); // voxelDepth_ (D32_FLOAT as R32_FLOAT SRV)
|
|
Texture2D<float4> 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<float4> 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<ShadowPush> 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<RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH> 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<RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH> 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)
|
|
}
|
|
}
|
|
}
|
|
}
|