bvle-voxels/shaders/voxelCullCS.hlsl

95 lines
3.4 KiB
HLSL
Raw Normal View History

// BVLE Voxels - Frustum + Backface Culling Compute Shader
// 1 thread per chunk: tests AABB vs 6 frustum planes, then emits up to 6 draws
// (one per visible face group, back-facing groups are culled).
#include "voxelCommon.hlsli"
StructuredBuffer<GPUChunkInfo> chunkInfoBuffer : register(t2);
RWStructuredBuffer<IndirectDrawArgsInstanced> indirectArgs : register(u0);
RWByteAddressBuffer drawCount : register(u1);
// Test AABB against 6 frustum planes (returns true if visible)
bool frustumTestAABB(float3 aabbMin, float3 aabbMax)
{
[unroll]
for (uint i = 0; i < 6; i++)
{
float4 plane = frustumPlanes[i];
float3 pVertex;
pVertex.x = (plane.x >= 0.0) ? aabbMax.x : aabbMin.x;
pVertex.y = (plane.y >= 0.0) ? aabbMax.y : aabbMin.y;
pVertex.z = (plane.z >= 0.0) ? aabbMax.z : aabbMin.z;
if (dot(plane.xyz, pVertex) + plane.w < 0.0)
return false;
}
return true;
}
// Face normals: +X, -X, +Y, -Y, +Z, -Z
static const float3 faceNormals[6] = {
float3( 1, 0, 0), float3(-1, 0, 0),
float3( 0, 1, 0), float3( 0,-1, 0),
float3( 0, 0, 1), float3( 0, 0,-1)
};
[RootSignature(VOXEL_ROOTSIG)]
[numthreads(64, 1, 1)]
void main(uint3 DTid : SV_DispatchThreadID)
{
uint chunkIdx = DTid.x;
if (chunkIdx >= chunkCount) return;
GPUChunkInfo info = chunkInfoBuffer[chunkIdx];
if (info.quadCount == 0) return;
float3 aabbMin = info.worldPos.xyz;
float3 aabbMax = aabbMin + (float3)chunkSize;
if (!frustumTestAABB(aabbMin, aabbMax)) return;
// Camera-to-chunk vector for backface test
float3 chunkCenter = (aabbMin + aabbMax) * 0.5;
float3 viewDir = chunkCenter - cameraPosition.xyz;
// Emit one draw per visible face group
[unroll]
for (uint f = 0; f < 6; f++)
{
uint fCnt = getFaceCount(info, f);
if (fCnt == 0) continue;
// Backface cull: if camera sees the back of this face group, skip it.
// A face group with normal N is back-facing if dot(viewDir, N) > 0.
// But we need a per-face test relative to the chunk AABB, not just center:
// face +X: back-facing if camera.x < aabbMin.x (camera is on -X side)
// face -X: back-facing if camera.x > aabbMax.x (camera is on +X side)
// This is more conservative and correct than dot product with center.
bool backFacing = false;
switch (f)
{
case 0: backFacing = (cameraPosition.x < aabbMin.x); break; // +X
case 1: backFacing = (cameraPosition.x > aabbMax.x); break; // -X
case 2: backFacing = (cameraPosition.y < aabbMin.y); break; // +Y
case 3: backFacing = (cameraPosition.y > aabbMax.y); break; // -Y
case 4: backFacing = (cameraPosition.z < aabbMin.z); break; // +Z
case 5: backFacing = (cameraPosition.z > aabbMax.z); break; // -Z
}
if (backFacing) continue;
uint drawIdx;
drawCount.InterlockedAdd(0, 1, drawIdx);
// The face group's quads start at (chunk's mega-buffer offset + face offset within chunk)
uint faceQuadOffset = info.quadOffset + getFaceOffset(info, f);
IndirectDrawArgsInstanced args;
args.pushConstant = 0; // written to b999[0] by ExecuteIndirect (unused in MDI VS path)
args.vertexCountPerInstance = fCnt * 6;
args.instanceCount = 1;
args.startVertexLocation = faceQuadOffset * 6;
args.startInstanceLocation = chunkIdx;
indirectArgs[drawIdx] = args;
}
}