Phase 6.3: RT ambient occlusion with bilateral blur
- 8 cosine-weighted hemisphere rays per pixel (inline ray queries, SM 6.5) - Distance-weighted AO: quadratic falloff (1-hitT/aoRadius)² instead of binary hit/miss - World-space hash seed: voxel coord + tangent-plane frac position (stable, no flicker) - Bilateral blur pipeline: 2-pass separable (H+V), radius 6, depth+normal edge-stopping - 4-pass dispatch: shadow+rawAO → blur H → blur V → apply - AO written to separate R8_UNORM texture, blurred, then applied to color buffer - Debug mode (F5 x3): grayscale AO visualization
This commit is contained in:
parent
6b41da0932
commit
9de53e5293
7 changed files with 449 additions and 123 deletions
28
CLAUDE.md
28
CLAUDE.md
|
|
@ -33,7 +33,9 @@ bvle-voxels/
|
||||||
│ ├── voxelSmoothVS.hlsl # Vertex shader smooth Surface Nets (vertex pulling, t6)
|
│ ├── voxelSmoothVS.hlsl # Vertex shader smooth Surface Nets (vertex pulling, t6)
|
||||||
│ ├── voxelSmoothPS.hlsl # Pixel shader smooth (triplanar + material blending)
|
│ ├── voxelSmoothPS.hlsl # Pixel shader smooth (triplanar + material blending)
|
||||||
│ ├── voxelBLASExtractCS.hlsl # Compute shader BLAS position extraction (Phase 6.1)
|
│ ├── voxelBLASExtractCS.hlsl # Compute shader BLAS position extraction (Phase 6.1)
|
||||||
│ └── voxelShadowCS.hlsl # Compute shader RT shadows (inline ray queries, Phase 6.2)
|
│ ├── voxelShadowCS.hlsl # Compute shader RT shadows + raw AO (inline ray queries, Phase 6.2+6.3)
|
||||||
|
│ ├── voxelAOBlurCS.hlsl # Compute shader bilateral AO blur (separable H/V, Phase 6.3)
|
||||||
|
│ └── voxelAOApplyCS.hlsl # Compute shader AO apply to color buffer (Phase 6.3)
|
||||||
└── CLAUDE.md
|
└── CLAUDE.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -583,15 +585,29 @@ Système de biseaux décoratifs (« topings ») sur les faces +Y exposées pour
|
||||||
- Pre : `voxelDepth_` DEPTHSTENCIL→SHADER_RESOURCE + `voxelRT_` SHADER_RESOURCE→UAV
|
- Pre : `voxelDepth_` DEPTHSTENCIL→SHADER_RESOURCE + `voxelRT_` SHADER_RESOURCE→UAV
|
||||||
- Post : `voxelDepth_` SHADER_RESOURCE→DEPTHSTENCIL + `voxelRT_` UAV→SHADER_RESOURCE
|
- Post : `voxelDepth_` SHADER_RESOURCE→DEPTHSTENCIL + `voxelRT_` UAV→SHADER_RESOURCE
|
||||||
- **Mode debug** (F5 × 2 = DBG) : rouge=shadow hit, vert=miss, bleu=back-facing, gris foncé=ciel
|
- **Mode debug** (F5 × 2 = DBG) : rouge=shadow hit, vert=miss, bleu=back-facing, gris foncé=ciel
|
||||||
- **Toggle** : F5 cycle OFF→ON→DBG→OFF
|
- **Toggle** : F5 cycle OFF→ON→DBG_SHADOW→DBG_AO→OFF
|
||||||
- **CB** : `inverseViewProjection` (float4x4) ajouté après `viewProjection` dans VoxelCB (HLSL + C++)
|
- **CB** : `inverseViewProjection` (float4x4) ajouté après `viewProjection` dans VoxelCB (HLSL + C++)
|
||||||
- **Push constants** : width, height, normalBias, maxDistance, debugMode
|
- **Push constants** : width, height, normalBias, maxDistance, debugMode
|
||||||
|
|
||||||
#### Phase 6.3 - RT AO [A FAIRE]
|
#### Phase 6.3 - RT AO [FAIT]
|
||||||
|
|
||||||
- 4-8 hemisphere rays per pixel, short range
|
- **Intégré dans `voxelShadowCS.hlsl`** : 8 rayons hémisphère cosine-weighted par pixel + 1 rayon soleil
|
||||||
- Cosine-weighted random directions from normal
|
- **Distance-weighted AO** : `(1 - hitT/aoRadius)²` — falloff quadratique, valeurs continues au lieu de binaire hit/miss
|
||||||
- Output: AO factor (R8_UNORM)
|
- **World-space hash stable** : seed = `floor(worldPos - N*0.5)` (voxel solide derrière la surface) + `frac(dot(worldPos, T/B)) * 256` (position fractionnaire sur les axes tangents uniquement — l'axe normal est exclu car il oscille à cause de la précision du depth buffer)
|
||||||
|
- **Bilateral blur séparable** (`voxelAOBlurCS.hlsl`) : 2 passes H+V, rayon 6 (kernel 13×13), edge-stopping sur depth + normals
|
||||||
|
- **Pipeline 4 passes** :
|
||||||
|
1. Shadow CS : shadow in-place sur `voxelRT_` + AO brut → `aoRawTexture_` (R8_UNORM, u1)
|
||||||
|
2. Blur H : `aoRawTexture_` → `aoBlurredTexture_` (bilateral, depth/normal edge-stopping)
|
||||||
|
3. Blur V : `aoBlurredTexture_` → `aoRawTexture_` (idem, direction verticale)
|
||||||
|
4. Apply : `aoRawTexture_` × `voxelRT_` → modulation finale (ou debug AO grayscale si debugMode=2)
|
||||||
|
- **Frisvad orthonormal basis** : construction robuste de (T,B) depuis N pour le hemisphere sampling
|
||||||
|
- **Cosine-weighted hemisphere** : `sqrt(u1)` distribution pour importance sampling
|
||||||
|
- **Push constants** : width, height, normalBias, shadowMaxDist, debugMode, aoRadius, aoRayCount, aoStrength
|
||||||
|
- **Pièges résolus** :
|
||||||
|
- **Hash screen-space → suit la caméra** : résolu en utilisant uniquement des coordonnées world-space
|
||||||
|
- **Hash `asuint(worldPos)` → clignote** : trop sensible aux variations FP du depth buffer, résolu par quantification au voxel + tangent frac
|
||||||
|
- **Hash `frac(worldPos)` sur axe normal → clignote sur ~30% des faces** : l'axe normal est à une frontière entière (ex: face +Y à y=5.0000) où `frac()` oscille entre ~0 et ~1. Résolu en projetant sur T/B uniquement
|
||||||
|
- **`floor(worldPos + 0.5)` → artefact au milieu des faces** : la coordonnée traverse 0.5 au centre de la face. Résolu par offset `-N*0.5` pour atterrir dans le voxel solide
|
||||||
|
|
||||||
#### Phase 6.4 - Fallback [A FAIRE]
|
#### Phase 6.4 - Fallback [A FAIRE]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ add_custom_command(TARGET BVLEVoxels POST_BUILD
|
||||||
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelSmoothCS.cso
|
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelSmoothCS.cso
|
||||||
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelBLASExtractCS.cso
|
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelBLASExtractCS.cso
|
||||||
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelShadowCS.cso
|
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelShadowCS.cso
|
||||||
|
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelAOBlurCS.cso
|
||||||
|
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelAOApplyCS.cso
|
||||||
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelCommon.hlsli.cso
|
$<TARGET_FILE_DIR:BVLEVoxels>/shaders/hlsl6/voxel/voxelCommon.hlsli.cso
|
||||||
COMMENT "Clearing stale voxel shader cache (forces recompilation from current .hlsl sources)"
|
COMMENT "Clearing stale voxel shader cache (forces recompilation from current .hlsl sources)"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
32
shaders/voxelAOApplyCS.hlsl
Normal file
32
shaders/voxelAOApplyCS.hlsl
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
// BVLE Voxels - AO Apply Compute Shader (Phase 6.3)
|
||||||
|
// Multiplies the blurred AO factor onto the color buffer.
|
||||||
|
|
||||||
|
#include "voxelCommon.hlsli"
|
||||||
|
|
||||||
|
Texture2D<float> aoBlurred : register(t0);
|
||||||
|
RWTexture2D<float4> colorOutput : register(u0);
|
||||||
|
|
||||||
|
struct ApplyPush {
|
||||||
|
uint width;
|
||||||
|
uint height;
|
||||||
|
uint debugMode; // 0=normal, 2=debug AO (show AO as grayscale)
|
||||||
|
uint pad[9];
|
||||||
|
};
|
||||||
|
[[vk::push_constant]] ConstantBuffer<ApplyPush> 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 ao = aoBlurred[DTid.xy];
|
||||||
|
|
||||||
|
if (push.debugMode == 2) {
|
||||||
|
// Debug AO: grayscale visualization of blurred AO
|
||||||
|
colorOutput[DTid.xy] = float4(ao, ao, ao, 1);
|
||||||
|
} else {
|
||||||
|
float4 color = colorOutput[DTid.xy];
|
||||||
|
color.rgb *= ao;
|
||||||
|
colorOutput[DTid.xy] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
shaders/voxelAOBlurCS.hlsl
Normal file
83
shaders/voxelAOBlurCS.hlsl
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// BVLE Voxels - Bilateral AO Blur Compute Shader (Phase 6.3)
|
||||||
|
// Separable bilateral blur: preserves edges using depth + normal comparison.
|
||||||
|
// Run twice: horizontal (direction=0) then vertical (direction=1).
|
||||||
|
|
||||||
|
#include "voxelCommon.hlsli"
|
||||||
|
|
||||||
|
// Input AO (raw or partially blurred)
|
||||||
|
Texture2D<float> aoInput : register(t0);
|
||||||
|
|
||||||
|
// Depth + normals for edge-stopping
|
||||||
|
Texture2D<float> depthTexture : register(t1);
|
||||||
|
Texture2D<float4> normalTexture : register(t2);
|
||||||
|
|
||||||
|
// Output blurred AO
|
||||||
|
RWTexture2D<float> aoOutput : register(u0);
|
||||||
|
|
||||||
|
struct BlurPush {
|
||||||
|
uint width;
|
||||||
|
uint height;
|
||||||
|
uint direction; // 0 = horizontal, 1 = vertical
|
||||||
|
uint radius; // blur kernel radius (e.g. 4 = 9x1 kernel)
|
||||||
|
float depthThreshold; // edge-stopping depth sensitivity
|
||||||
|
float normalThreshold; // edge-stopping normal sensitivity (dot product)
|
||||||
|
uint pad[6];
|
||||||
|
};
|
||||||
|
[[vk::push_constant]] ConstantBuffer<BlurPush> 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 centerAO = aoInput[DTid.xy];
|
||||||
|
float centerDepth = depthTexture[DTid.xy];
|
||||||
|
float3 centerN = normalTexture[DTid.xy].xyz;
|
||||||
|
|
||||||
|
// Skip sky pixels
|
||||||
|
if (centerDepth == 0.0) {
|
||||||
|
aoOutput[DTid.xy] = 1.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gaussian-ish weights (sigma ≈ radius/2)
|
||||||
|
float totalWeight = 1.0;
|
||||||
|
float totalAO = centerAO;
|
||||||
|
|
||||||
|
int2 step = (push.direction == 0) ? int2(1, 0) : int2(0, 1);
|
||||||
|
int r = (int)push.radius;
|
||||||
|
|
||||||
|
for (int i = -r; i <= r; i++) {
|
||||||
|
if (i == 0) continue;
|
||||||
|
|
||||||
|
int2 coord = int2(DTid.xy) + step * i;
|
||||||
|
if (coord.x < 0 || coord.x >= (int)push.width ||
|
||||||
|
coord.y < 0 || coord.y >= (int)push.height)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float sampleAO = aoInput[coord];
|
||||||
|
float sampleDepth = depthTexture[coord];
|
||||||
|
float3 sampleN = normalTexture[coord].xyz;
|
||||||
|
|
||||||
|
// Skip sky
|
||||||
|
if (sampleDepth == 0.0) continue;
|
||||||
|
|
||||||
|
// Edge-stopping: depth difference
|
||||||
|
float depthDiff = abs(centerDepth - sampleDepth);
|
||||||
|
float depthWeight = exp(-depthDiff * depthDiff / (push.depthThreshold * push.depthThreshold));
|
||||||
|
|
||||||
|
// Edge-stopping: normal difference
|
||||||
|
float normalDot = max(0.0, dot(centerN, sampleN));
|
||||||
|
float normalWeight = (normalDot > push.normalThreshold) ? normalDot : 0.0;
|
||||||
|
|
||||||
|
// Spatial weight (Gaussian falloff)
|
||||||
|
float dist = float(abs(i)) / float(r + 1);
|
||||||
|
float spatialWeight = exp(-dist * dist * 2.0);
|
||||||
|
|
||||||
|
float w = spatialWeight * depthWeight * normalWeight;
|
||||||
|
totalWeight += w;
|
||||||
|
totalAO += sampleAO * w;
|
||||||
|
}
|
||||||
|
|
||||||
|
aoOutput[DTid.xy] = totalAO / totalWeight;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// BVLE Voxels - RT Shadow Compute Shader (Phase 6.2)
|
// BVLE Voxels - RT Shadow + AO Compute Shader (Phase 6.2 + 6.3)
|
||||||
// Traces shadow rays from each pixel toward the sun using inline ray queries.
|
// Per-pixel: traces 1 shadow ray toward sun + N hemisphere rays for AO.
|
||||||
// Reads depth + normal to reconstruct world position, modulates voxelRT_ in-place.
|
// Modulates voxelRT_ in-place via RWTexture2D.
|
||||||
|
|
||||||
#include "voxelCommon.hlsli"
|
#include "voxelCommon.hlsli"
|
||||||
|
|
||||||
|
|
@ -9,29 +9,71 @@ Texture2D<float> depthTexture : register(t0); // voxelDepth_ (D32_FLOAT as R3
|
||||||
Texture2D<float4> normalTexture : register(t1); // voxelNormalRT_ (R16G16B16A16_SNORM)
|
Texture2D<float4> normalTexture : register(t1); // voxelNormalRT_ (R16G16B16A16_SNORM)
|
||||||
RaytracingAccelerationStructure tlas : register(t2); // TLAS with blocky + smooth instances
|
RaytracingAccelerationStructure tlas : register(t2); // TLAS with blocky + smooth instances
|
||||||
|
|
||||||
// UAV: read-modify-write voxelRT_ (each thread handles exactly one pixel, no race)
|
// UAV outputs
|
||||||
RWTexture2D<float4> colorOutput : register(u0);
|
RWTexture2D<float4> colorOutput : register(u0); // voxelRT_ (shadow applied in-place)
|
||||||
|
RWTexture2D<float> aoOutput : register(u1); // raw AO factor (blurred separately)
|
||||||
|
|
||||||
// Push constants
|
// Push constants
|
||||||
struct ShadowPush {
|
struct ShadowPush {
|
||||||
uint width;
|
uint width;
|
||||||
uint height;
|
uint height;
|
||||||
float normalBias;
|
float normalBias;
|
||||||
float maxDistance;
|
float shadowMaxDist;
|
||||||
uint debugMode; // 0=normal, 1=debug visualization
|
uint debugMode; // 0=normal, 1=debug shadows, 2=debug AO
|
||||||
uint pad[7];
|
float aoRadius; // max distance for AO rays (e.g. 8.0 voxels)
|
||||||
|
uint aoRayCount; // number of hemisphere rays (e.g. 6)
|
||||||
|
float aoStrength; // how dark full occlusion is (e.g. 0.35 = 65% darkening)
|
||||||
|
uint pad[4];
|
||||||
};
|
};
|
||||||
[[vk::push_constant]] ConstantBuffer<ShadowPush> push : register(b999);
|
[[vk::push_constant]] ConstantBuffer<ShadowPush> push : register(b999);
|
||||||
|
|
||||||
|
// ── Hash-based pseudo-random for AO ray directions ──────────────
|
||||||
|
// Golden ratio hash: deterministic, no texture lookup, good distribution
|
||||||
|
float hashF(uint x) {
|
||||||
|
x ^= x >> 16;
|
||||||
|
x *= 0x45d9f3bu;
|
||||||
|
x ^= x >> 16;
|
||||||
|
return float(x & 0xFFFFFF) / float(0xFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint hashU(uint a, uint b) {
|
||||||
|
a ^= b * 0x9E3779B9u;
|
||||||
|
a ^= a >> 16;
|
||||||
|
a *= 0x45d9f3bu;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build orthonormal basis from normal (Frisvad's method, robust for all N)
|
||||||
|
void buildBasis(float3 N, out float3 T, out float3 B) {
|
||||||
|
if (N.z < -0.9999) {
|
||||||
|
T = float3(0, -1, 0);
|
||||||
|
B = float3(-1, 0, 0);
|
||||||
|
} else {
|
||||||
|
float a = 1.0 / (1.0 + N.z);
|
||||||
|
float b = -N.x * N.y * a;
|
||||||
|
T = float3(1.0 - N.x * N.x * a, b, -N.x);
|
||||||
|
B = float3(b, 1.0 - N.y * N.y * a, -N.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cosine-weighted hemisphere sample (probability ∝ cos(θ))
|
||||||
|
float3 cosineSampleHemisphere(float u1, float u2, float3 N, float3 T, float3 B) {
|
||||||
|
float r = sqrt(u1);
|
||||||
|
float phi = 6.28318530718 * u2;
|
||||||
|
float x = r * cos(phi);
|
||||||
|
float y = r * sin(phi);
|
||||||
|
float z = sqrt(max(0.0, 1.0 - u1));
|
||||||
|
return normalize(x * T + y * B + z * N);
|
||||||
|
}
|
||||||
|
|
||||||
[RootSignature(VOXEL_ROOTSIG)]
|
[RootSignature(VOXEL_ROOTSIG)]
|
||||||
[numthreads(8, 8, 1)]
|
[numthreads(8, 8, 1)]
|
||||||
void main(uint3 DTid : SV_DispatchThreadID) {
|
void main(uint3 DTid : SV_DispatchThreadID) {
|
||||||
if (DTid.x >= push.width || DTid.y >= push.height) return;
|
if (DTid.x >= push.width || DTid.y >= push.height) return;
|
||||||
|
|
||||||
float depth = depthTexture[DTid.xy];
|
float depth = depthTexture[DTid.xy];
|
||||||
// depth == 0 means sky (reverse-Z: 0 = far plane)
|
|
||||||
if (depth == 0.0) {
|
if (depth == 0.0) {
|
||||||
if (push.debugMode > 0) colorOutput[DTid.xy] = float4(0.1, 0.1, 0.1, 1); // dark gray = sky
|
if (push.debugMode > 0) colorOutput[DTid.xy] = float4(0.1, 0.1, 0.1, 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,64 +84,104 @@ void main(uint3 DTid : SV_DispatchThreadID) {
|
||||||
float4 worldPos4 = mul(inverseViewProjection, clipPos);
|
float4 worldPos4 = mul(inverseViewProjection, clipPos);
|
||||||
float3 worldPos = worldPos4.xyz / worldPos4.w;
|
float3 worldPos = worldPos4.xyz / worldPos4.w;
|
||||||
|
|
||||||
// Read world-space normal
|
|
||||||
float3 N = normalTexture[DTid.xy].xyz;
|
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;
|
float3 origin = worldPos + N * push.normalBias;
|
||||||
|
|
||||||
|
// ── Shadow ray toward sun ──────────────────────────────────
|
||||||
|
float3 L = normalize(-sunDirection.xyz);
|
||||||
|
float NdotL = dot(N, L);
|
||||||
|
|
||||||
|
float shadowFactor = 1.0;
|
||||||
|
if (NdotL <= 0.0) {
|
||||||
|
shadowFactor = 0.3; // back-facing = fully in shadow
|
||||||
|
} else {
|
||||||
RayDesc ray;
|
RayDesc ray;
|
||||||
ray.Origin = origin;
|
ray.Origin = origin;
|
||||||
ray.Direction = L;
|
ray.Direction = L;
|
||||||
ray.TMin = 0.01;
|
ray.TMin = 0.01;
|
||||||
ray.TMax = push.maxDistance;
|
ray.TMax = push.shadowMaxDist;
|
||||||
|
|
||||||
// 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;
|
RayQuery<RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH> q;
|
||||||
q.TraceRayInline(tlas, 0, 0xFF, ray);
|
q.TraceRayInline(tlas, 0, 0xFF, ray);
|
||||||
// With FLAG_OPAQUE geometry + ACCEPT_FIRST_HIT, Proceed() handles everything
|
[loop] while (q.Proceed()) {}
|
||||||
while (q.Proceed()) {}
|
|
||||||
|
|
||||||
if (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) {
|
if (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) {
|
||||||
if (push.debugMode > 0) {
|
shadowFactor = 0.3;
|
||||||
colorOutput[DTid.xy] = float4(1.0, 0.0, 0.0, 1); // RED = shadow ray hit (cast shadow!)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── AO: hemisphere rays ────────────────────────────────────
|
||||||
|
float aoFactor = 1.0;
|
||||||
|
uint rayCount = push.aoRayCount;
|
||||||
|
if (rayCount > 0) {
|
||||||
|
float3 T, B;
|
||||||
|
buildBasis(N, T, B);
|
||||||
|
|
||||||
|
// Fully world-space seed: solid voxel coord + tangent-plane frac position
|
||||||
|
// vc: offset by -N*0.5 → inside the solid voxel (stable per face)
|
||||||
|
// Sub-voxel: only use the 2 tangent axes (T,B), NOT the normal axis.
|
||||||
|
// The normal axis sits at an integer boundary (e.g. face +Y → y=5.0000)
|
||||||
|
// where frac() oscillates between ~0 and ~1 due to depth precision → flicker.
|
||||||
|
// Tangent axes vary smoothly across the face → always stable.
|
||||||
|
int3 vc = int3(floor(worldPos - N * 0.5));
|
||||||
|
float tFrac = frac(dot(worldPos, T));
|
||||||
|
float bFrac = frac(dot(worldPos, B));
|
||||||
|
uint st = uint(tFrac * 256.0);
|
||||||
|
uint sb = uint(bFrac * 256.0);
|
||||||
|
uint baseSeed = hashU(hashU((uint)(vc.x + 32768), (uint)(vc.y + 32768)), (uint)(vc.z + 32768));
|
||||||
|
uint pixelSeed = hashU(baseSeed, hashU(st, sb));
|
||||||
|
float totalOcclusion = 0.0;
|
||||||
|
|
||||||
|
[loop]
|
||||||
|
for (uint i = 0; i < rayCount; i++) {
|
||||||
|
// Per-ray random: hash(pixelSeed, rayIndex)
|
||||||
|
uint seed = hashU(pixelSeed, i);
|
||||||
|
float u1 = hashF(seed);
|
||||||
|
float u2 = hashF(seed ^ 0xA5A5A5A5u);
|
||||||
|
|
||||||
|
float3 dir = cosineSampleHemisphere(u1, u2, N, T, B);
|
||||||
|
|
||||||
|
RayDesc aoRay;
|
||||||
|
aoRay.Origin = origin;
|
||||||
|
aoRay.Direction = dir;
|
||||||
|
aoRay.TMin = 0.05; // larger TMin for AO to avoid edge self-intersection
|
||||||
|
aoRay.TMax = push.aoRadius;
|
||||||
|
|
||||||
|
RayQuery<RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH> aoQ;
|
||||||
|
aoQ.TraceRayInline(tlas, 0, 0xFF, aoRay);
|
||||||
|
[loop] while (aoQ.Proceed()) {}
|
||||||
|
|
||||||
|
if (aoQ.CommittedStatus() == COMMITTED_TRIANGLE_HIT) {
|
||||||
|
// Distance-weighted: close hits = strong occlusion, far hits = weak
|
||||||
|
float hitT = aoQ.CommittedRayT();
|
||||||
|
float falloff = 1.0 - saturate(hitT / push.aoRadius);
|
||||||
|
totalOcclusion += falloff * falloff; // quadratic for natural falloff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float occlusionRatio = totalOcclusion / float(rayCount);
|
||||||
|
aoFactor = 1.0 - occlusionRatio * push.aoStrength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Write AO to separate buffer (will be blurred), apply shadow in-place ──
|
||||||
|
aoOutput[DTid.xy] = aoFactor;
|
||||||
|
|
||||||
|
if (push.debugMode == 1) {
|
||||||
|
// Debug shadows: red=shadow, green=lit, blue=backface
|
||||||
|
if (NdotL <= 0.0)
|
||||||
|
colorOutput[DTid.xy] = float4(0, 0, 0.5, 1);
|
||||||
|
else if (shadowFactor < 1.0)
|
||||||
|
colorOutput[DTid.xy] = float4(1, 0, 0, 1);
|
||||||
|
else
|
||||||
|
colorOutput[DTid.xy] = float4(0, 1, 0, 1);
|
||||||
|
} else if (push.debugMode == 2) {
|
||||||
|
// Debug AO: raw AO written to aoOutput, will be visualized after blur
|
||||||
|
// Write white to color so blur apply pass shows AO only
|
||||||
|
colorOutput[DTid.xy] = float4(1, 1, 1, 1);
|
||||||
} else {
|
} else {
|
||||||
|
// Apply shadow only — AO applied after blur in a separate pass
|
||||||
float4 color = colorOutput[DTid.xy];
|
float4 color = colorOutput[DTid.xy];
|
||||||
color.rgb *= 0.3;
|
color.rgb *= shadowFactor;
|
||||||
colorOutput[DTid.xy] = color;
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,14 +197,16 @@ void VoxelRenderer::initialize(GraphicsDevice* dev) {
|
||||||
rtAvailable_ = false;
|
rtAvailable_ = false;
|
||||||
wi::backlog::post("VoxelRenderer: RT available but BLAS extraction shader failed", wi::backlog::LogLevel::Warning);
|
wi::backlog::post("VoxelRenderer: RT available but BLAS extraction shader failed", wi::backlog::LogLevel::Warning);
|
||||||
}
|
}
|
||||||
// ── RT Shadows (Phase 6.2) ────────────────────────────────────
|
// ── RT Shadows + AO (Phase 6.2 + 6.3) ────────────────────────
|
||||||
wi::renderer::LoadShader(ShaderStage::CS, shadowShader_, "voxel/voxelShadowCS.cso",
|
wi::renderer::LoadShader(ShaderStage::CS, shadowShader_, "voxel/voxelShadowCS.cso",
|
||||||
wi::graphics::ShaderModel::SM_6_5);
|
wi::graphics::ShaderModel::SM_6_5);
|
||||||
if (shadowShader_.IsValid()) {
|
wi::renderer::LoadShader(ShaderStage::CS, aoBlurShader_, "voxel/voxelAOBlurCS.cso");
|
||||||
|
wi::renderer::LoadShader(ShaderStage::CS, aoApplyShader_, "voxel/voxelAOApplyCS.cso");
|
||||||
|
if (shadowShader_.IsValid() && aoBlurShader_.IsValid() && aoApplyShader_.IsValid()) {
|
||||||
rtShadowsEnabled_ = true;
|
rtShadowsEnabled_ = true;
|
||||||
wi::backlog::post("VoxelRenderer: RT shadows available");
|
wi::backlog::post("VoxelRenderer: RT shadows + AO blur available");
|
||||||
} else {
|
} else {
|
||||||
wi::backlog::post("VoxelRenderer: RT shadow shader failed to compile",
|
wi::backlog::post("VoxelRenderer: RT shadow/AO shader(s) failed to compile",
|
||||||
wi::backlog::LogLevel::Warning);
|
wi::backlog::LogLevel::Warning);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1200,7 +1202,7 @@ void VoxelRenderer::buildAccelerationStructures(CommandList cmd) const {
|
||||||
rtDirty_ = false;
|
rtDirty_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── RT Shadow dispatch (Phase 6.2) ──────────────────────────────
|
// ── RT Shadow + AO dispatch (Phase 6.2 + 6.3) ──────────────────
|
||||||
void VoxelRenderer::dispatchShadows(CommandList cmd,
|
void VoxelRenderer::dispatchShadows(CommandList cmd,
|
||||||
const Texture& depthBuffer,
|
const Texture& depthBuffer,
|
||||||
const Texture& renderTarget,
|
const Texture& renderTarget,
|
||||||
|
|
@ -1212,48 +1214,136 @@ void VoxelRenderer::dispatchShadows(CommandList cmd,
|
||||||
auto* dev = device_;
|
auto* dev = device_;
|
||||||
uint32_t w = renderTarget.GetDesc().width;
|
uint32_t w = renderTarget.GetDesc().width;
|
||||||
uint32_t h = renderTarget.GetDesc().height;
|
uint32_t h = renderTarget.GetDesc().height;
|
||||||
|
uint32_t gx = (w + 7) / 8;
|
||||||
|
uint32_t gy = (h + 7) / 8;
|
||||||
|
|
||||||
// Pre-barriers:
|
// ── Pass 1: Shadow + raw AO ────────────────────────────────────
|
||||||
// - voxelDepth_: DEPTHSTENCIL → SHADER_RESOURCE (for depth reads)
|
{
|
||||||
// - voxelRT_: SHADER_RESOURCE → UNORDERED_ACCESS (for in-place shadow modulation)
|
|
||||||
// - voxelNormalRT_ is already in SHADER_RESOURCE state from render pass
|
|
||||||
GPUBarrier preBarriers[] = {
|
GPUBarrier preBarriers[] = {
|
||||||
GPUBarrier::Image(&const_cast<Texture&>(depthBuffer),
|
GPUBarrier::Image(&const_cast<Texture&>(depthBuffer),
|
||||||
ResourceState::DEPTHSTENCIL, ResourceState::SHADER_RESOURCE),
|
ResourceState::DEPTHSTENCIL, ResourceState::SHADER_RESOURCE),
|
||||||
GPUBarrier::Image(&const_cast<Texture&>(renderTarget),
|
GPUBarrier::Image(&const_cast<Texture&>(renderTarget),
|
||||||
ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS),
|
ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS),
|
||||||
|
GPUBarrier::Image(&aoRawTexture_,
|
||||||
|
ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS),
|
||||||
};
|
};
|
||||||
dev->Barrier(preBarriers, 2, cmd);
|
dev->Barrier(preBarriers, 3, cmd);
|
||||||
|
|
||||||
dev->BindComputeShader(&shadowShader_, cmd);
|
dev->BindComputeShader(&shadowShader_, cmd);
|
||||||
|
|
||||||
// Bind resources
|
|
||||||
dev->BindResource(&depthBuffer, 0, cmd); // t0 = depth
|
dev->BindResource(&depthBuffer, 0, cmd); // t0 = depth
|
||||||
dev->BindResource(&normalTarget, 1, cmd); // t1 = normals
|
dev->BindResource(&normalTarget, 1, cmd); // t1 = normals
|
||||||
dev->BindResource(&tlas_, 2, cmd); // t2 = TLAS
|
dev->BindResource(&tlas_, 2, cmd); // t2 = TLAS
|
||||||
dev->BindUAV(&renderTarget, 0, cmd); // u0 = color (read-modify-write)
|
dev->BindUAV(&renderTarget, 0, cmd); // u0 = color
|
||||||
dev->BindConstantBuffer(&constantBuffer_, 0, cmd); // b0 = VoxelCB
|
dev->BindUAV(&aoRawTexture_, 1, cmd); // u1 = raw AO output
|
||||||
|
dev->BindConstantBuffer(&constantBuffer_, 0, cmd);
|
||||||
|
|
||||||
// Push constants
|
|
||||||
struct ShadowPush {
|
struct ShadowPush {
|
||||||
uint32_t width;
|
uint32_t width, height;
|
||||||
uint32_t height;
|
float normalBias, shadowMaxDist;
|
||||||
float normalBias;
|
|
||||||
float maxDistance;
|
|
||||||
uint32_t debugMode;
|
uint32_t debugMode;
|
||||||
uint32_t pad[7];
|
float aoRadius;
|
||||||
|
uint32_t aoRayCount;
|
||||||
|
float aoStrength;
|
||||||
|
uint32_t pad[4];
|
||||||
} pushData = {};
|
} pushData = {};
|
||||||
pushData.width = w;
|
pushData.width = w;
|
||||||
pushData.height = h;
|
pushData.height = h;
|
||||||
pushData.normalBias = 0.15f; // offset along normal to avoid self-intersection
|
pushData.normalBias = 0.15f;
|
||||||
pushData.maxDistance = 512.0f; // max shadow ray distance
|
pushData.shadowMaxDist = 512.0f;
|
||||||
pushData.debugMode = rtShadowDebug_ ? 1 : 0;
|
pushData.debugMode = rtShadowDebug_;
|
||||||
|
pushData.aoRadius = 8.0f;
|
||||||
|
pushData.aoRayCount = 8;
|
||||||
|
pushData.aoStrength = 0.7f;
|
||||||
dev->PushConstants(&pushData, sizeof(pushData), cmd);
|
dev->PushConstants(&pushData, sizeof(pushData), cmd);
|
||||||
|
dev->Dispatch(gx, gy, 1, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch: 8×8 thread groups covering the screen
|
// ── Pass 2: Bilateral blur horizontal (aoRaw → aoBlurred) ──────
|
||||||
dev->Dispatch((w + 7) / 8, (h + 7) / 8, 1, cmd);
|
{
|
||||||
|
GPUBarrier barriers[] = {
|
||||||
|
GPUBarrier::Image(&aoRawTexture_,
|
||||||
|
ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE),
|
||||||
|
GPUBarrier::Image(&aoBlurredTexture_,
|
||||||
|
ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS),
|
||||||
|
};
|
||||||
|
dev->Barrier(barriers, 2, cmd);
|
||||||
|
|
||||||
// Post-barriers: restore states for Compose()
|
dev->BindComputeShader(&aoBlurShader_, cmd);
|
||||||
|
dev->BindResource(&aoRawTexture_, 0, cmd); // t0 = AO input
|
||||||
|
dev->BindResource(&depthBuffer, 1, cmd); // t1 = depth
|
||||||
|
dev->BindResource(&normalTarget, 2, cmd); // t2 = normals
|
||||||
|
dev->BindUAV(&aoBlurredTexture_, 0, cmd); // u0 = AO output
|
||||||
|
|
||||||
|
struct BlurPush {
|
||||||
|
uint32_t width, height, direction, radius;
|
||||||
|
float depthThreshold, normalThreshold;
|
||||||
|
uint32_t pad[6];
|
||||||
|
} blurPush = {};
|
||||||
|
blurPush.width = w;
|
||||||
|
blurPush.height = h;
|
||||||
|
blurPush.direction = 0; // horizontal
|
||||||
|
blurPush.radius = 6;
|
||||||
|
blurPush.depthThreshold = 0.001f;
|
||||||
|
blurPush.normalThreshold = 0.9f;
|
||||||
|
dev->PushConstants(&blurPush, sizeof(blurPush), cmd);
|
||||||
|
dev->Dispatch(gx, gy, 1, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Pass 3: Bilateral blur vertical (aoBlurred → aoRaw) ────────
|
||||||
|
{
|
||||||
|
GPUBarrier barriers[] = {
|
||||||
|
GPUBarrier::Image(&aoBlurredTexture_,
|
||||||
|
ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE),
|
||||||
|
GPUBarrier::Image(&aoRawTexture_,
|
||||||
|
ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS),
|
||||||
|
};
|
||||||
|
dev->Barrier(barriers, 2, cmd);
|
||||||
|
|
||||||
|
dev->BindComputeShader(&aoBlurShader_, cmd);
|
||||||
|
dev->BindResource(&aoBlurredTexture_, 0, cmd); // t0 = AO input (H-blurred)
|
||||||
|
dev->BindResource(&depthBuffer, 1, cmd);
|
||||||
|
dev->BindResource(&normalTarget, 2, cmd);
|
||||||
|
dev->BindUAV(&aoRawTexture_, 0, cmd); // u0 = AO output (fully blurred)
|
||||||
|
|
||||||
|
struct BlurPush {
|
||||||
|
uint32_t width, height, direction, radius;
|
||||||
|
float depthThreshold, normalThreshold;
|
||||||
|
uint32_t pad[6];
|
||||||
|
} blurPush = {};
|
||||||
|
blurPush.width = w;
|
||||||
|
blurPush.height = h;
|
||||||
|
blurPush.direction = 1; // vertical
|
||||||
|
blurPush.radius = 6;
|
||||||
|
blurPush.depthThreshold = 0.001f;
|
||||||
|
blurPush.normalThreshold = 0.9f;
|
||||||
|
dev->PushConstants(&blurPush, sizeof(blurPush), cmd);
|
||||||
|
dev->Dispatch(gx, gy, 1, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Pass 4: Apply blurred AO to color ──────────────────────────
|
||||||
|
{
|
||||||
|
GPUBarrier barriers[] = {
|
||||||
|
GPUBarrier::Image(&aoRawTexture_,
|
||||||
|
ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE),
|
||||||
|
};
|
||||||
|
dev->Barrier(barriers, 1, cmd);
|
||||||
|
|
||||||
|
dev->BindComputeShader(&aoApplyShader_, cmd);
|
||||||
|
dev->BindResource(&aoRawTexture_, 0, cmd); // t0 = blurred AO
|
||||||
|
dev->BindUAV(&renderTarget, 0, cmd); // u0 = color
|
||||||
|
|
||||||
|
struct ApplyPush {
|
||||||
|
uint32_t width, height, debugMode;
|
||||||
|
uint32_t pad[9];
|
||||||
|
} applyPush = {};
|
||||||
|
applyPush.width = w;
|
||||||
|
applyPush.height = h;
|
||||||
|
applyPush.debugMode = rtShadowDebug_;
|
||||||
|
dev->PushConstants(&applyPush, sizeof(applyPush), cmd);
|
||||||
|
dev->Dispatch(gx, gy, 1, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Restore resource states ────────────────────────────────────
|
||||||
GPUBarrier postBarriers[] = {
|
GPUBarrier postBarriers[] = {
|
||||||
GPUBarrier::Image(&const_cast<Texture&>(depthBuffer),
|
GPUBarrier::Image(&const_cast<Texture&>(depthBuffer),
|
||||||
ResourceState::SHADER_RESOURCE, ResourceState::DEPTHSTENCIL),
|
ResourceState::SHADER_RESOURCE, ResourceState::DEPTHSTENCIL),
|
||||||
|
|
@ -2263,7 +2353,21 @@ void VoxelRenderPath::createRenderTargets() {
|
||||||
depthDesc.layout = wi::graphics::ResourceState::DEPTHSTENCIL;
|
depthDesc.layout = wi::graphics::ResourceState::DEPTHSTENCIL;
|
||||||
device->CreateTexture(&depthDesc, nullptr, &voxelDepth_);
|
device->CreateTexture(&depthDesc, nullptr, &voxelDepth_);
|
||||||
|
|
||||||
rtCreated_ = voxelRT_.IsValid() && voxelNormalRT_.IsValid() && voxelDepth_.IsValid();
|
// AO textures (R8_UNORM) for bilateral blur pipeline
|
||||||
|
wi::graphics::TextureDesc aoDesc;
|
||||||
|
aoDesc.type = wi::graphics::TextureDesc::Type::TEXTURE_2D;
|
||||||
|
aoDesc.width = w;
|
||||||
|
aoDesc.height = h;
|
||||||
|
aoDesc.format = wi::graphics::Format::R8_UNORM;
|
||||||
|
aoDesc.bind_flags = wi::graphics::BindFlag::SHADER_RESOURCE | wi::graphics::BindFlag::UNORDERED_ACCESS;
|
||||||
|
aoDesc.mip_levels = 1;
|
||||||
|
aoDesc.sample_count = 1;
|
||||||
|
aoDesc.layout = wi::graphics::ResourceState::SHADER_RESOURCE;
|
||||||
|
device->CreateTexture(&aoDesc, nullptr, &renderer.aoRawTexture_);
|
||||||
|
device->CreateTexture(&aoDesc, nullptr, &renderer.aoBlurredTexture_);
|
||||||
|
|
||||||
|
rtCreated_ = voxelRT_.IsValid() && voxelNormalRT_.IsValid() && voxelDepth_.IsValid()
|
||||||
|
&& renderer.aoRawTexture_.IsValid() && renderer.aoBlurredTexture_.IsValid();
|
||||||
wi::backlog::post("VoxelRenderPath: render targets " + std::string(rtCreated_ ? "OK" : "FAILED")
|
wi::backlog::post("VoxelRenderPath: render targets " + std::string(rtCreated_ ? "OK" : "FAILED")
|
||||||
+ " (" + std::to_string(w) + "x" + std::to_string(h) + ")");
|
+ " (" + std::to_string(w) + "x" + std::to_string(h) + ")");
|
||||||
}
|
}
|
||||||
|
|
@ -2291,18 +2395,21 @@ void VoxelRenderPath::handleInput(float dt) {
|
||||||
wi::backlog::post(renderer.debugBlend_ ? "Blend debug: ON" : "Blend debug: OFF");
|
wi::backlog::post(renderer.debugBlend_ ? "Blend debug: ON" : "Blend debug: OFF");
|
||||||
}
|
}
|
||||||
if (wi::input::Press(wi::input::KEYBOARD_BUTTON_F5)) {
|
if (wi::input::Press(wi::input::KEYBOARD_BUTTON_F5)) {
|
||||||
// Cycle: OFF → ON → DEBUG → OFF
|
// Cycle: OFF → ON → DBG_SHADOW → DBG_AO → OFF
|
||||||
if (!renderer.rtShadowsEnabled_) {
|
if (!renderer.rtShadowsEnabled_) {
|
||||||
renderer.rtShadowsEnabled_ = true;
|
renderer.rtShadowsEnabled_ = true;
|
||||||
renderer.rtShadowDebug_ = false;
|
renderer.rtShadowDebug_ = 0;
|
||||||
wi::backlog::post("RT Shadows: ON");
|
wi::backlog::post("RT Shadows+AO: ON");
|
||||||
} else if (!renderer.rtShadowDebug_) {
|
} else if (renderer.rtShadowDebug_ == 0) {
|
||||||
renderer.rtShadowDebug_ = true;
|
renderer.rtShadowDebug_ = 1;
|
||||||
wi::backlog::post("RT Shadows: DEBUG (red=shadow, green=lit, blue=backface)");
|
wi::backlog::post("RT Debug: SHADOWS (red=shadow, green=lit, blue=backface)");
|
||||||
|
} else if (renderer.rtShadowDebug_ == 1) {
|
||||||
|
renderer.rtShadowDebug_ = 2;
|
||||||
|
wi::backlog::post("RT Debug: AO (white=open, black=occluded)");
|
||||||
} else {
|
} else {
|
||||||
renderer.rtShadowsEnabled_ = false;
|
renderer.rtShadowsEnabled_ = false;
|
||||||
renderer.rtShadowDebug_ = false;
|
renderer.rtShadowDebug_ = 0;
|
||||||
wi::backlog::post("RT Shadows: OFF");
|
wi::backlog::post("RT Shadows+AO: OFF");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (wi::input::Press(wi::input::MOUSE_BUTTON_RIGHT)) {
|
if (wi::input::Press(wi::input::MOUSE_BUTTON_RIGHT)) {
|
||||||
|
|
@ -2613,7 +2720,7 @@ void VoxelRenderPath::Compose(CommandList cmd) const {
|
||||||
stats += "RT: TLAS ready | Blocky "
|
stats += "RT: TLAS ready | Blocky "
|
||||||
+ std::to_string(renderer.getRTBlockyTriCount()) + " tris | Smooth "
|
+ std::to_string(renderer.getRTBlockyTriCount()) + " tris | Smooth "
|
||||||
+ std::to_string(renderer.getRTSmoothTriCount()) + " tris"
|
+ std::to_string(renderer.getRTSmoothTriCount()) + " tris"
|
||||||
+ " | Shadows " + std::string(renderer.rtShadowDebug_ ? "DEBUG" : (renderer.isRTShadowsEnabled() ? "ON" : "OFF")) + "\n";
|
+ " | Shadows+AO " + std::string(renderer.rtShadowDebug_ == 1 ? "DBG_SHD" : (renderer.rtShadowDebug_ == 2 ? "DBG_AO" : (renderer.isRTShadowsEnabled() ? "ON" : "OFF"))) + "\n";
|
||||||
} else {
|
} else {
|
||||||
stats += "RT: building...\n";
|
stats += "RT: building...\n";
|
||||||
}
|
}
|
||||||
|
|
@ -2623,7 +2730,7 @@ void VoxelRenderPath::Compose(CommandList cmd) const {
|
||||||
stats += "WASD+Space/Ctrl: move | Shift: fast | Right-click: capture mouse\n";
|
stats += "WASD+Space/Ctrl: move | Shift: fast | Right-click: capture mouse\n";
|
||||||
stats += "F2: console | F3: anim [" + std::string(animatedTerrain_ ? "ON" : "OFF")
|
stats += "F2: console | F3: anim [" + std::string(animatedTerrain_ ? "ON" : "OFF")
|
||||||
+ "] | F4: dbg [" + std::string(renderer.debugBlend_ ? "ON" : "OFF")
|
+ "] | F4: dbg [" + std::string(renderer.debugBlend_ ? "ON" : "OFF")
|
||||||
+ "] | F5: shadows [" + std::string(renderer.rtShadowDebug_ ? "DBG" : (renderer.isRTShadowsEnabled() ? "ON" : "OFF")) + "]";
|
+ "] | F5: shd+ao [" + std::string(renderer.rtShadowDebug_ == 1 ? "SHD" : (renderer.rtShadowDebug_ == 2 ? "AO" : (renderer.isRTShadowsEnabled() ? "ON" : "OFF"))) + "]";
|
||||||
|
|
||||||
wi::font::Draw(stats, fp, cmd);
|
wi::font::Draw(stats, fp, cmd);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,10 +206,14 @@ private:
|
||||||
void dispatchBLASExtract(wi::graphics::CommandList cmd) const;
|
void dispatchBLASExtract(wi::graphics::CommandList cmd) const;
|
||||||
void buildAccelerationStructures(wi::graphics::CommandList cmd) const;
|
void buildAccelerationStructures(wi::graphics::CommandList cmd) const;
|
||||||
|
|
||||||
// ── RT Shadows (Phase 6.2) ─────────────────────────────────────
|
// ── RT Shadows + AO (Phase 6.2 + 6.3) ──────────────────────────
|
||||||
wi::graphics::Shader shadowShader_; // voxelShadowCS compute shader
|
wi::graphics::Shader shadowShader_; // voxelShadowCS compute shader
|
||||||
|
wi::graphics::Shader aoBlurShader_; // voxelAOBlurCS compute shader
|
||||||
|
wi::graphics::Shader aoApplyShader_; // voxelAOApplyCS compute shader
|
||||||
|
mutable wi::graphics::Texture aoRawTexture_; // R8_UNORM: raw AO from shadow CS
|
||||||
|
mutable wi::graphics::Texture aoBlurredTexture_; // R8_UNORM: after bilateral blur
|
||||||
mutable bool rtShadowsEnabled_ = false; // true when shader + TLAS ready
|
mutable bool rtShadowsEnabled_ = false; // true when shader + TLAS ready
|
||||||
mutable bool rtShadowDebug_ = false; // debug visualization mode
|
mutable uint32_t rtShadowDebug_ = 0; // 0=off, 1=debug shadows, 2=debug AO
|
||||||
|
|
||||||
void dispatchShadows(wi::graphics::CommandList cmd,
|
void dispatchShadows(wi::graphics::CommandList cmd,
|
||||||
const wi::graphics::Texture& depthBuffer,
|
const wi::graphics::Texture& depthBuffer,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue