// 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 chunkInfoBuffer : register(t2); RWStructuredBuffer 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; } }