121 lines
3.8 KiB
GLSL
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
|