news 2026/9/27 8:28:51

大场景视距裁剪与 Hi-Z 遮挡剔除在移动端的实操与优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大场景视距裁剪与 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; }; StructuredBuffer<InstanceData> _InstanceBuffer; AppendStructuredBuffer<uint> _VisibleInstanceArgsBuffer; Texture2D<float> _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)工程落地的关键避坑

  1. 上一帧深度缓冲的滞后性(Temporal Reprojection):直接使用上一帧生成的 Hi-Z 进行当前帧裁剪时,快速转动视角会导致视野边缘物体出现 1 帧的“闪烁空洞”。必须使用上一帧的 ViewProjection 矩阵逆变换重新投影 AABB,或者对画面边缘区域的包围盒适当外扩(Padding)。
  2. TBDR 深度解析开销(Depth Resolve):在移动端 Mali / Adreno 架构上,将 On-Chip Tile Memory 中的 Depth Buffer 拷贝到系统内存(Resolve)会消耗显存带宽。建议将 Hi-Z 金字塔的生成放在不透明物体渲染之后的低分辨率拷贝 Pass 中,限制金字塔尺寸为 512x256,既能保证剔除精度,又极大节省下采样带宽。
  3. 两阶段剔除(Two-Phase Culling):对于动态移动的物体,第一阶段使用上一帧 Hi-Z 进行保守裁剪并绘制已知可见物体;第二阶段在完成主要遮挡物绘制后,对第一阶段被剔除的疑似可见物体进行二次测试,彻底杜绝闪烁。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 8:28:51

东兴网站建设避坑指南:3步搞定备案与安全

东兴网站建设避坑指南:3步搞定备案与安全 备案流程一头雾水,导致东兴网站建设项目延期?别慌,我见过太多设计师转前端的朋友,卡在ICP备案的迷宫里,甚至因为不懂安全配置,网站上线第一天就被攻击。今天不讲虚的,只聊实战。我们用 免费工具 梳理从域名解析到服务器加固的全链路,帮你把风险扼杀在摇篮里。…

作者头像 李华
网站建设 2026/9/27 8:28:46

奖券世界推广网站避坑指南:5个设计注意事项搞定交付

奖券世界推广网站避坑指南:5个设计注意事项搞定交付 改个需求建站公司拖一周?这种憋屈事我见太多了。很多甲方找外包做奖券世界推广网站,签合同前觉得挺简单,上线后才发现按钮换个位置、文案改两个字,对方就要排期、评估工作量,甚至加收费用。 这背后的核心问题,往往不是开发能力不足,而是 前期设计规范缺失…

作者头像 李华
网站建设 2026/9/27 8:28:23

外贸网站建设及优化ppt:3个技术栈对比与避坑注意事项

外贸网站建设及优化ppt:3个技术栈对比与避坑注意事项 改个需求建站公司拖一周,这种憋屈谁没受过?很多老板拿着精心制作的《外贸网站建设及优化ppt》去跟供应商对线,结果发现对方连基础配置都搞不明白,沟通成本极高。其实, 外贸网站建设及优化ppt…

作者头像 李华
网站建设 2026/9/27 8:28:12

3个实战案例拆解soho的网站怎么做零代码也能排首页

3个实战案例拆解soho的网站怎么做零代码也能排首页 自己不会代码想做网站,却总被那些复杂的术语和昂贵的开发费劝退?别慌,我见过太多SOHO创业者(个人创业者)因为搞不定技术细节,白白浪费几个月时间。今天咱们不整虚的,直接上 实战案例 ,拆解 soho的网站怎么做…

作者头像 李华
网站建设 2026/9/27 8:27:36

做网站导航用什么开元程序及完整流程实操

做网站导航用什么开元程序及完整流程实操 改个导航栏颜色,建站公司拖了一周还没动静,这种憋屈感太熟悉了。很多设计师转前端,或者刚接手公司官网的运营,一遇到导航菜单这种基础但关键的结构调整,就容易卡在技术选型上。其实, 做网站导航用什么开元程序…

作者头像 李华