94 lines
3.3 KiB
HLSL
94 lines
3.3 KiB
HLSL
|
|
// 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.vertexCountPerInstance = fCnt * 6;
|
||
|
|
args.instanceCount = 1;
|
||
|
|
args.startVertexLocation = faceQuadOffset * 6;
|
||
|
|
args.startInstanceLocation = chunkIdx;
|
||
|
|
indirectArgs[drawIdx] = args;
|
||
|
|
}
|
||
|
|
}
|