大场景视距裁剪与 Hi-Z 遮挡剔除在移动端的实操与优化
大场景视距裁剪与 Hi-Z 遮挡剔除在移动端的实操与优化在开放世界大场景游戏中仅靠视锥体裁剪Frustum Culling已无法遏制渲染几何负载的膨胀。当玩家站在山谷或城镇街道中央时视野前方虽然包含了数万个建筑、植被和道具但其中超过 70% 的物体实际上完全被前方的大山或临街城墙遮挡。如果将这些完全不可见的物体依然送入渲染管线会产生巨量的顶点吞吐浪费与无效的 Early-Z 深度测试开销。层次深度缓冲Hierarchical Z-Buffer, Hi-Z遮挡剔除利用上一帧生成的深度金字塔Depth Pyramid在 GPU Compute Shader 中对候选包围盒AABB进行快速保守可见性测试结合 GPU Driven 渲染架构的间接绘制DrawMeshInstancedIndirect可以在移动端将 Draw Call 与顶点处理压力削减 60% 以上。Hi-Z 遮挡剔除工作流与原理解析Hi-Z 的核心思想是构建一个类似 Mipmap 的深度纹理金字塔。在标准纹理采样中Mipmap 生成采用平均值Average Downsampling但在 Hi-Z 深度金字塔中每个降采样像素必须取其对应的 4 个子像素的最大深度值在 Reverse-Z 规范下为最小深度值代表最靠近相机的表面以确保遮挡判定的严格保守性Conservative Testing。[原始深度缓冲 (Depth Pre-Pass)] │ ▼ (Max/Min 降采样构建 Mipmap 链) [Hi-Z 深度金字塔 (1/2, 1/4, 1/8, 1/16, ...)] │ ▼ (在 Compute Shader 中输入候选包围盒 AABB) ┌──────────────────────────────────────────────┐ │ 1. 视锥体相交测试 (Frustum Test) │ │ 2. 计算 AABB 屏幕空间包围矩形与对应 Mip 级别 │ │ 3. 采样 Hi-Z 金字塔深度并与 AABB 最近深度比对 │ └──────────────────────────────────────────────┘ │ ▼ (通过测试的实例写入 Culled Instance Buffer) [DrawMeshInstancedIndirect 间接绘制命令触发]移动端 Hi-Z Compute Shader 核心实现以下是基于 HLSL 实现的 GPU 实例剔除着色器代码支持 Reverse-Z 模式#pragma kernel CSMain struct InstanceData { float4x4 objectToWorld; float3 boundsCenter; float3 boundsExtents; }; StructuredBufferInstanceData _InstanceBuffer; AppendStructuredBufferuint _VisibleInstanceArgsBuffer; Texture2Dfloat _HiZDepthTexture; SamplerState sampler_HiZDepthTexture; float4x4 _ViewProjectionMatrix; float4 _CameraPositionWS; float4 _HiZTextureSize; // (Width, Height, 1/Width, 1/Height) float _MaxDrawDistance; // 判断 AABB 是否在视锥体外部 bool IsOutsideFrustum(float3 center, float3 extents) { // 标准 6 个视锥平面的快速点积判别 // 此处省略 6 平面判别循环返回 true 表示在视野外 return false; } [numthreads(64, 1, 1)] void CSMain(uint3 id : SV_DispatchThreadID) { uint instanceIndex id.x; InstanceData inst _InstanceBuffer[instanceIndex]; float3 centerWS mul(inst.objectToWorld, float4(inst.boundsCenter, 1.0)).xyz; float distToCamera distance(_CameraPositionWS.xyz, centerWS); // 1. 距离裁剪 if (distToCamera _MaxDrawDistance) return; // 2. 视锥体裁剪 if (IsOutsideFrustum(centerWS, inst.boundsExtents)) return; // 3. 计算 AABB 在屏幕空间的投影像素范围 float3 minWS centerWS - inst.boundsExtents; float3 maxWS centerWS inst.boundsExtents; float3 boxCorners[8] { float3(minWS.x, minWS.y, minWS.z), float3(maxWS.x, minWS.y, minWS.z), float3(minWS.x, maxWS.y, minWS.z), float3(maxWS.x, maxWS.y, minWS.z), float3(minWS.x, minWS.y, maxWS.z), float3(maxWS.x, minWS.y, maxWS.z), float3(minWS.x, maxWS.y, maxWS.z), float3(maxWS.x, maxWS.y, maxWS.z) }; float2 minScreen float2(1.0, 1.0); float2 maxScreen float2(-1.0, -1.0); float nearestDepth 0.0; // Reverse-Z: 1.0 为近平面, 0.0 为远平面 [unroll] for (int i 0; i 8; i) { float4 clipPos mul(_ViewProjectionMatrix, float4(boxCorners[i], 1.0)); float3 ndc clipPos.xyz / clipPos.w; minScreen min(minScreen, ndc.xy); maxScreen max(maxScreen, ndc.xy); nearestDepth max(nearestDepth, ndc.z); // 找到离相机最近的深度 } // 映射到 [0, 1] 纹理坐标空间 float2 minUV saturate(minScreen * 0.5 0.5); float2 maxUV saturate(maxScreen * 0.5 0.5); float2 uvSize (maxUV - minUV) * _HiZTextureSize.xy; // 根据包围盒像素占位计算最佳采样 Mip 级别 float maxDim max(uvSize.x, uvSize.y); float mipLevel ceil(log2(max(maxDim, 1.0))); // 采样 Hi-Z 金字塔的 4 个脚点深度 float4 depthSamples; depthSamples.x _HiZDepthTexture.SampleLevel(sampler_HiZDepthTexture, minUV, mipLevel).r; depthSamples.y _HiZDepthTexture.SampleLevel(sampler_HiZDepthTexture, maxUV, mipLevel).r; depthSamples.z _HiZDepthTexture.SampleLevel(sampler_HiZDepthTexture, float2(minUV.x, maxUV.y), mipLevel).r; depthSamples.w _HiZDepthTexture.SampleLevel(sampler_HiZDepthTexture, float2(maxUV.x, minUV.y), mipLevel).r; // 在 Reverse-Z 下如果 Hi-Z 记录的最小深度大于等于物体的最近深度说明被完全挡住 float maxOccluderDepth min(min(depthSamples.x, depthSamples.y), min(depthSamples.z, depthSamples.w)); if (nearestDepth maxOccluderDepth) { // 可见压入间接绘制索引缓冲 _VisibleInstanceArgsBuffer.Append(instanceIndex); } }移动端TBDR工程落地的关键避坑上一帧深度缓冲的滞后性Temporal Reprojection直接使用上一帧生成的 Hi-Z 进行当前帧裁剪时快速转动视角会导致视野边缘物体出现 1 帧的“闪烁空洞”。必须使用上一帧的 ViewProjection 矩阵逆变换重新投影 AABB或者对画面边缘区域的包围盒适当外扩Padding。TBDR 深度解析开销Depth Resolve在移动端 Mali / Adreno 架构上将 On-Chip Tile Memory 中的 Depth Buffer 拷贝到系统内存Resolve会消耗显存带宽。建议将 Hi-Z 金字塔的生成放在不透明物体渲染之后的低分辨率拷贝 Pass 中限制金字塔尺寸为 512x256既能保证剔除精度又极大节省下采样带宽。两阶段剔除Two-Phase Culling对于动态移动的物体第一阶段使用上一帧 Hi-Z 进行保守裁剪并绘制已知可见物体第二阶段在完成主要遮挡物绘制后对第一阶段被剔除的疑似可见物体进行二次测试彻底杜绝闪烁。