bvle-voxels/shaders/voxelAOBlurCS.hlsl
Samuel Bouchet 9de53e5293 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
2026-03-29 09:31:19 +02:00

83 lines
2.7 KiB
HLSL

// 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;
}