Files
FlaxEngine/Source/Shaders/Utils/Culling.shader
T

121 lines
3.8 KiB
GLSL

// Copyright (c) Wojciech Figat. All rights reserved.
#include "./Flax/Common.hlsl"
#include "./Flax/Math/Math.hlsl"
META_CB_BEGIN(0, OcclusionCullingData)
float4x4 ViewProjectionMatrix;
float2 RTSize;
float MaxMipLevel;
uint CullCount;
META_CB_END
// Vertex Shader function for Hardware Occlusion Culling queries bounds projection
META_VS(true, FEATURE_LEVEL_ES2)
float4 VS_HardwareOcclusionCulling(float3 Position : POSITION0) : SV_Position
{
return mul(float4(Position, 1), ViewProjectionMatrix);
}
#ifdef _CS_HZBCull
RWBuffer<uint> HZBResults : register(u0);
Buffer<float3> BoundsBuffer : register(t0);
Texture2D<float> HiZ : register(t1);
// Compute Shader for HZB culling
// [Reference: https://interplayoflight.wordpress.com/2017/11/15/experiments-in-gpu-based-occlusion-culling/]
// [Reference: https://blog.selfshadow.com/publications/practical-visibility/]
META_CS(true, AUTO)
[numthreads(64, 1, 1)]
void CS_HZBCull(uint DispatchThreadId : SV_DispatchThreadID)
{
if (DispatchThreadId >= CullCount)
return;
// Load object bounds
float3 bondsMin = BoundsBuffer[DispatchThreadId * 2];
float3 bondsMax = BoundsBuffer[DispatchThreadId * 2 + 1];
float3 bondsSize = bondsMax - bondsMin;
// Project bounds onto the screen
float3 boundsCorners[] = {
bondsMin.xyz,
bondsMin.xyz + float3(bondsSize.x,0,0),
bondsMin.xyz + float3(0, bondsSize.y,0),
bondsMin.xyz + float3(0, 0, bondsSize.z),
bondsMin.xyz + float3(bondsSize.xy,0),
bondsMin.xyz + float3(0, bondsSize.yz),
bondsMin.xyz + float3(bondsSize.x, 0, bondsSize.z),
bondsMax.xyz
};
float closestZ = DEPTH_RANGE_MAX;
float2 minUV = 1, maxUV = 0;
UNROLL
for (uint i = 0; i < 8; i++)
{
// Transform world-space bounds to NDC
float4 clipPos = PROJECT_POINT(float4(boundsCorners[i], 1), ViewProjectionMatrix);
clipPos.xyz = clipPos.xyz / clipPos.w;
// Get min/max UVs
clipPos.xy = clipPos.xy * float2(0.5, -0.5) + float2(0.5, 0.5);
clipPos.xy = saturate(clipPos.xy);
minUV = min(clipPos.xy, minUV);
maxUV = max(clipPos.xy, maxUV);
// Get the closest depth
#if REVERSE_Z
if (clipPos.z < 0)
clipPos.z = 1; // Point is behind the camera
closestZ = saturate(max(closestZ, clipPos.z));
#else
closestZ = saturate(min(closestZ, clipPos.z));
#endif
}
// Calculate Hi-Z buffer mip (assumes HZB is power of two)
#if VULKAN || defined(WGSL) || 1
float2 pixelSize = RTSize * (maxUV - minUV) * 2.0f;
float mip = floor(log2(max(max(pixelSize.x, pixelSize.y), 1.0f)));
#else
int2 size = (maxUV - minUV) * RTSize;
float mip = ceil(log2(max(max(size.x, size.y), 1)));
#endif
mip = clamp(mip, 0, MaxMipLevel);
float4 boundsUVs = float4(minUV, maxUV);
// Texel footprint for the lower (finer-grained) level
float mipUp = max(mip - 1, 0);
float2 scale = exp2(-mipUp);
float2 a = floor(boundsUVs.xy * scale);
float2 b = ceil(boundsUVs.zw * scale);
float2 dims = b - a;
// Use the lower level if we only touch <= 2 texels in both dimensions
if (dims.x <= 2 && dims.y <= 2)
mip = mipUp;
// Load depths from Hi-Z buffer
float4 depths = {
SAMPLE_RT_DEPTH_LEVEL(HiZ, boundsUVs.xy, mip),
SAMPLE_RT_DEPTH_LEVEL(HiZ, boundsUVs.zy, mip),
SAMPLE_RT_DEPTH_LEVEL(HiZ, boundsUVs.xw, mip),
SAMPLE_RT_DEPTH_LEVEL(HiZ, boundsUVs.zw, mip)
};
// Find the furthest depth and test it
#if REVERSE_Z
float furthestDepth = Min4(depths);
bool visible = closestZ >= furthestDepth;
#else
float furthestDepth = Max4(depths);
bool visible = closestZ <= furthestDepth;
#endif
// Write culling result
HZBResults[DispatchThreadId] = visible ? 1u : 0u;
}
#endif