以Unity + 移动端 MMORPG为主要场景。⚠️ 不同 GPU 架构、驱动和 Unity 版本的行为差异很大,文中的数字仅为示意。是否值得做、做到哪一层,以真机实测为准。先打个比方:画室里被挡住的东西还是那间画室。经理派单时说"画一座城",画师们老老实实把城墙后面的每一栋房子、房子里的每一张桌子都画了出来,最后城墙一盖,全白画了。遮挡剔除就是在动笔之前先问一句:“这东西会被挡住吗?”问的时机不同,省下的活也不同:什么时候问谁来问省下了什么① 上色前,逐个像素问GPU 硬件自动完成上色工人的活(像素阶段)② 派单前,逐个物体问预计算数据、CPU 或 GPU 计算经理的工单 + 摆姿势工人的活 + 上色工人的活🎯问得越早,省得越多;但问得越早,判断本身也越贵、越容易出错。遮挡剔除优化的核心,就是在"判断成本"和"节省收益"之间找到平衡。一、先分清三种"剔除"类型剔除什么成本引擎是否自动做视锥剔除镜头视野外的物体很低✅ 自动距离剔除太远、太小、看不清的物体很低需要手动设置遮挡剔除在视野内、但被别的东西挡住的物体中到高需要专门方案视野范围 📷 ──────────────────── ╲ 🏠 被城墙挡住 → 遮挡剔除 ╲ ██ 城墙 ╲ 🌳 在视野内、未被挡 → 正常绘制 ╲ 🌲 太远 → 距离剔除 ╲ 🏰 视野外 → 视锥剔除优先级:先把视锥剔除和距离剔除做到位(几乎零成本),再考虑遮挡剔除。二、先判断:你的游戏值不值得做1. 手机 GPU 已经帮你省了一部分手机 GPU 本身有"被挡住的像素不上色"的能力(下文第三部分详述),所以对不透明物体来说,像素阶段的浪费本来就有限。物体级遮挡剔除在手机上真正能省的,主要是:CPU:剔除、排序、提交 Draw Call 的开销;顶点阶段:被挡住的模型依然要做顶点变换、骨骼蒙皮;带宽:分块渲染(TBDR)的 GPU 会把所有三角形的处理结果写回内存再分到各个小块,三角形越多,带宽越大;阴影:被遮挡的物体如果也不投射阴影(需谨慎判断),阴影阶段也能省。2. 场景类型决定收益场景遮挡关系收益主城、街道、城镇建筑密集、互相遮挡⭐⭐⭐ 高室内、地下城副本墙壁、房间分隔⭐⭐⭐ 高山谷、峡谷地形地形遮挡远处⭐⭐ 中开阔草原、海面几乎没有遮挡⭐ 很低,甚至得不偿失俯视角(45° 固定视角)视角高,很少被挡⭐ 很低🧮算一笔账:遮挡剔除本身要花时间。如果剔除花了 1.5ms,只省下 1ms 的渲染时间,那就是负优化。三、第一层:保护好 GPU 内置的像素剔除(免费的午餐)这一层不需要写任何剔除代码,只需要别把它破坏掉。1. 各家 GPU 的内置手段GPU技术原理简述对绘制顺序的依赖Apple、PowerVRHSR(隐藏面消除)一个小块内所有三角形先比完深度,只给最前面的上色低MaliEarly-ZS + FPK(前向像素剔除)提前做深度测试;后来的像素如果更靠前,可以"杀掉"还在排队的旧像素中AdrenoEarly-Z + LRZ(低分辨率深度)先用一张粗糙的深度图快速剔除大块被挡住的区域较高,从前往后画效果最好通用Early-Z上色之前先测深度,被挡住就跳过高2. 哪些写法会让它们失效破坏者为什么对策Alpha Test(clip / discard)GPU 必须先执行 Shader 才知道这个像素要不要丢,无法提前判断它会不会挡住后面的东西少用;树叶用贴合形状的网格;放到不透明物体之后绘制(Unity 的 AlphaTest 队列默认在 2450)在 Shader 中修改深度(输出SV_Depth)深度要算完 Shader 才知道移动端避免使用半透明物体不写深度,无法遮挡别人,还必须全部画控制面积和层数绘制顺序混乱后画的近处物体无法帮先画的远处物体省活不透明物体从近到远绘制(Unity 默认会排序,但合批、自定义队列可能打乱)天空盒先画整个屏幕先被天空涂满天空盒在不透明物体之后绘制(URP 默认如此)中途切换渲染目标、回读深度打断分块渲染流程,深度信息要写回内存减少渲染目标切换3. 深度预渲染(Depth Prepass)要不要开?先只画一遍深度,再正式上色,确保每个像素只上一次色。桌面 GPU手机 GPU收益常常明显对 Apple/PowerVR/Mali 来说,HSR/FPK 已经做了类似的事代价顶点多处理一遍顶点多处理一遍 + 额外带宽建议视情况开启默认不开;只有 Alpha Test 植被特别多、像素 Shader 特别重时,针对性地给这部分物体开🍽️ 这一层就像"把菜从近到远摆上桌":不花钱,只要别乱摆。四、第二层:Unity 内置的预计算遮挡剔除(Umbra)1. 原理在编辑器里提前烘焙:把场景切成很多格子,计算"站在这个格子里,能看到哪些物体",运行时直接查表。烘焙时(编辑器): 场景 ──▶ 切分空间格子 ──▶ 计算可见性 ──▶ 遮挡数据文件 运行时(CPU): 相机所在格子 ──▶ 查表 ──▶ 得到可见物体列表 ──▶ 只绘制这些2. 物体的两种身份身份含义适合Occluder Static(遮挡者)能挡住别人大型建筑、墙壁、山体、大石头Occludee Static(被遮挡者)能被挡住几乎所有静态物件只做被遮挡者,不做遮挡者—小物件、镂空物体(栅栏、树木)、半透明物体动态物体(角色、怪物)不能参与烘焙,但可以在 Renderer 上勾选Dynamic Occlusion,作为被遮挡者查询。3. 关键参数参数含义调整建议Smallest Occluder小于这个尺寸的物体不作为遮挡者调大:数据更小、运行更快,但剔除更保守Smallest Hole小于这个尺寸的缝隙视为不透光太大会导致透过窗户、门缝本应看见的东西被错误剔除Backface Threshold用于剔除"相机不可能到达的位置"的数据场景封闭性好时可降低以减少数据量4. 配套工具Occlusion Area:只在玩家可能到达的区域烘焙,减少数据量;Occlusion Portal:门、闸门打开和关闭时切换遮挡状态,适合副本中的可开关大门。5. 优缺点✅ 优点❌ 缺点开箱即用,不用写代码只支持静态遮挡者,可破坏的墙、移动的大门无法动态遮挡运行时查询相对便宜遮挡数据占内存,大世界可能很大稳定、没有延迟穿帮烘焙时间长,场景一改就要重新烘焙场景分块流式加载时,各块数据要分别管理查询在 CPU 上进行,物体数量极多时也有开销🎮适用:副本、主城等封闭场景。对开放大世界,往往需要配合下面的方案。五、第三层:硬件遮挡查询(Occlusion Query)——手机上慎用原理画一个物体的包围盒(不上色),问 GPU:"这个盒子有没有像素通过深度测试?"没有,就说明整个物体被挡住了。为什么手机上不推荐CPU 第 N 帧: 发出查询 ──────────────────────▶ 等结果…… GPU: (分块渲染,整帧完成后才有结果) CPU 第 N+1 帧或 N+2 帧: 拿到结果问题说明结果延迟CPU 要等 GPU 画完才知道结果,立即等待会让 CPU 和 GPU 互相卡住延迟使用会穿帮用上一两帧的结果,镜头快速转动时会出现物体"晚一拍才出现"TBDR 不友好分块渲染要处理完整帧才能给出结果,延迟更长每个查询本身就是一次绘制物体多时,查询开销抵消了收益🚫 Unity 也没有对外直接暴露通用的遮挡查询接口。手游中一般不作为主力方案。六、第四层:CPU 软件光栅化遮挡剔除(手游大世界的主力)1. 原理在 CPU 上画一张很小的深度图,只用来判断遮挡:① 选出近处的大型遮挡者(建筑、山体、城墙),使用它们的"简化代理模型" ② 在 CPU 上把代理模型光栅化到一张小深度图(例如 256×128) ③ 对每个待检测物体,把它的包围盒投影到屏幕上,和深度图比较 ④ 包围盒覆盖区域全部比深度图更远 → 被遮挡,不绘制CPU 深度缓冲(低分辨率) ┌────────────────────────┐ │░░░░░░░████████░░░░░░░░░│ ████ = 城墙代理模型 │░░░░░░░████████░░░░░░░░░│ │░░░░░░░██[🏠]██░░░░░░░░░│ 🏠 的包围盒完全落在城墙后面 → 剔除 │░░░░░░░████████░░[🌳]░░░│ 🌳 落在空白区域 → 绘制 └────────────────────────┘业界常见实现思路参考 Intel 的Masked Occlusion Culling(SIMD 加速的分层深度缓冲)。Unity 中可以用Job System + Burst实现,在多个核心上并行。2. 为什么适合手游大世界优点说明支持动态遮挡者和被遮挡者移动的大门、可破坏建筑都能参与当帧结果,没有延迟穿帮不依赖 GPU 回读不需要烘焙适合分块流式加载的大世界不占 GPU手机上 GPU 往往比 CPU 更紧张、更容易发热结果还能用于其他系统例如跳过被遮挡角色的动画更新、特效更新3. 关键设计要点建议遮挡者代理模型由美术制作或工具自动生成:几十到几百个三角形的简化盒体,必须完全在原模型内部(不能比原模型大,否则会错误剔除)遮挡者数量每帧只选离相机最近、屏幕占比最大的若干个(如 30~100 个),按"屏幕面积 ÷ 距离"排序深度图分辨率低分辨率即可,过高会让 CPU 成本飙升被遮挡者检测用包围盒,宁可保守(误判为可见),不要激进(误判为不可见会穿帮)分层组织先测试整个区块、建筑群的大包围盒,被挡住就跳过其中所有物体线程放到工作线程,并注意前面章节说的大小核调度:跑在小核上可能来不及完成4. 成本控制剔除成本 = 光栅化遮挡者 + 测试被遮挡者 目标:中端机上整体控制在 0.5~1ms(工作线程,示意值)低端机可以降低深度图分辨率、减少遮挡者数量,或者直接关闭(如果低端机本来视距就很短)。七、第五层:GPU 驱动的 Hi-Z 遮挡剔除1. 原理利用上一帧的深度图,在 GPU 上用计算着色器(Compute Shader)判断每个物体是否可见,再用间接绘制(Indirect Draw)只画可见的物体。CPU 全程不需要知道结果。第 1 步:构建 Hi-Z(分层深度图)把深度图逐级缩小,每一级的一个像素保存下一级 2×2 区域中最远的深度:Mip0(全分辨率) Mip1 Mip2 ┌──┬──┬──┬──┐ ┌─────┬─────┐ ┌───────────┐ │ 3│ 5│ 9│ 9│ │ │ │ │ │ ├──┼──┼──┼──┤ ─▶ │ 5 │ 9 │ ─▶│ 9 │ │ 4│ 2│ 8│ 7│ │ │ │ │ (最远深度) │ └──┴──┴──┴──┘ └─────┴─────┘ └───────────┘第 2 步:测试物体计算着色器(每个线程处理一个物体): ① 把包围盒投影到屏幕,得到屏幕矩形和最近深度 ② 根据矩形大小选择合适的 Mip 级别(让矩形只覆盖 2×2 个像素) ③ 取出这几个像素的最远深度 ④ 物体最近点 比 遮挡物最远点 还远 → 被完全遮挡 ⑤ 可见的物体写入可见列表,并累加间接绘制参数第 3 步:间接绘制DrawMeshInstancedIndirect / Graphics.RenderMeshIndirect → GPU 根据可见列表决定画多少实例,CPU 不参与2. 时间差问题:上一帧的深度对不上这一帧用上一帧深度判断这一帧,镜头移动后会出现:刚从墙后转出来的物体,晚一帧才出现。解决方式说明两阶段剔除① 用上一帧深度剔除并画出可见物体 → ② 用这一帧刚画出的深度构建新 Hi-Z → ③ 对第一阶段被剔除的物体再测一次,补画误判的深度重投影把上一帧深度按相机运动变换到当前视角(会有空洞)保守放大包围盒简单但效果有限镜头切换时禁用传送、过场动画切镜头时当帧不剔除3. 手机上的现实问题问题说明读回深度要付带宽手机 GPU 通常把深度留在片上,画完直接丢弃(不写回内存)。做 Hi-Z 就必须把深度写回内存,以 2400×1080 分辨率、4 字节深度计算,每帧约 10MB 的额外写入,再加上构建 Mip 的读写计算着色器支持需要 OpenGL ES 3.1+ / Vulkan / Metal;老旧机型和部分驱动存在兼容问题间接绘制的驱动问题部分老旧 Adreno、Mali 驱动的间接绘制有性能或正确性问题合批体系要改造需要实例化渲染、统一的物体数据缓冲,和常规 GameObject 渲染流程差异大发热GPU 本来就是手游最紧张的资源,再加计算任务可能得不偿失4. Unity 6 的内置方案Unity 6 在 URP 中提供了GPU Resident Drawer + GPU Occlusion Culling,可以在不手写 Compute Shader 的情况下使用 GPU 遮挡剔除。它有前提条件(例如需要 Forward+ 渲染路径、支持计算着色器的平台、物体使用兼容的渲染方式等),并且在移动端的收益需要逐机型实测。具体要求以你使用的 Unity 版本文档为准。🎯适用:高端机档位、物体数量极大(如大量植被、密集城市)的场景。中低端机建议保留 CPU 方案。八、方案对比与选择方案剔除粒度动态遮挡者成本在哪延迟穿帮手游推荐度GPU 内置像素剔除像素✅免费无⭐⭐⭐ 必须保护Unity 预计算(Umbra)物体❌CPU 查询 + 内存无⭐⭐⭐ 封闭场景硬件遮挡查询物体✅GPU + 同步等待有⭐ 不推荐CPU 软件光栅化物体✅CPU 工作线程无⭐⭐⭐ 大世界主力GPU Hi-Z物体/实例✅GPU + 带宽有(可修正)⭐⭐ 高端机一个常见的组合┌───────────── 所有档位 ─────────────┐ │ 视锥剔除 + 距离剔除 + 保护像素剔除 │ └──────────────────┬────────────────┘ │ ┌──────────────────────────┼──────────────────────────┐ ▼ ▼ ▼ 副本 / 室内 主城 / 大世界 高端机 + 海量实例 Umbra 预计算剔除 CPU 软件光栅化剔除 GPU Hi-Z + 间接绘制 (可叠加 Umbra 处理静态部分) (植被、密集物件)九、MMO 特有场景场景建议主城挤满玩家角色作为被遮挡者参与剔除:被建筑挡住的玩家不仅不画,还可以跳过动画更新、特效、头顶名字和血条第三人称镜头快速旋转优先选择没有延迟的方案(预计算、CPU 光栅化);GPU 方案要做两阶段剔除镜头可拉远到高空视角越高,遮挡越少,剔除收益下降;可以在高空视角时关闭遮挡剔除,改用更激进的距离剔除和 LOD地形遮挡山脉是天然遮挡者,可以生成地形的低精度代理网格参与 CPU 光栅化(“地平线剔除”)分块流式加载遮挡数据和代理模型跟随区块加载和卸载传送、过场动画镜头瞬移时当帧强制全部可见或重置剔除历史阴影被相机视角遮挡的物体仍可能投射可见的影子,例如墙后的高塔把影子投到墙前的地面上。所以不能直接用相机的遮挡结果来剔除阴影投射者。稳妥的做法是:阴影投射者按光源视角单独做视锥剔除;相机遮挡结果只用于"不投射阴影"或"阴影距离之外"的物体;角色可以用"被遮挡 + 离可见区域足够远"作为跳过阴影的条件十、美术与关卡配合:遮挡剔除的效果一半靠内容剔除算法再好,如果场景是"一眼望到底",也没有东西可剔。收益大小首先由关卡和资源决定。1. 关卡设计:主动制造遮挡做法说明视线阻断街道设计成转弯、错位,不要一条直线大道通到地图尽头天然隔断城门、山口、峡谷、隧道把区域分开,过渡处正好可以做流式加载地形起伏小山丘、高低差挡住远处的建筑群室内外分离室内物件单独分组,只在玩家靠近门窗或进入室内时才可能可见🏛️ 很多经典主城的"曲折小巷"既是美术风格,也是性能设计。2. 模型拆分粒度:不能太大,也不能太碎❌ 太大:整条街合成一个网格 → 只要露出一个角,整条街都要画,剔除完全失效 ❌ 太碎:每块砖、每个花盆都是独立物体 → 剔除测试次数暴涨,CPU 开销超过收益 ✅ 适中:以"一栋建筑""一个房间""一组摆件"为单位 → 剔除有效,测试次数可控这里和第六章"合批"存在矛盾:静态合批会把物体合成大网格,削弱剔除效果。常见做法是按空间区块合批(只合并相邻、通常同时可见的物体),而不是全场景一把合并。3. 遮挡代理模型的制作规范规范说明必须在原模型内部代理模型比原模型大 → 会把原本可见的东西错误剔除,造成"物体闪烁消失"面数极低一般几十到一两百个三角形,用盒子、柱体拼出主体轮廓忽略镂空和细节窗户、栏杆、屋檐的镂空部分直接不做,按"不遮挡"处理命名约定例如Building_A_OCC,导入时由工具自动识别、隐藏、注册为遮挡者自动生成 + 人工检查工具可以按体素化或凸包自动生成,再由美术检查是否超出原模型4. 包围盒要准确剔除依赖包围盒。包围盒虚大,物体就永远剔不掉。常见问题对策骨骼动画角色包围盒过大(例如包含了武器挥舞的最大范围)使用合理大小的包围盒;Skinned Mesh Renderer 不要随意开启Update When Offscreen粒子系统包围盒自动计算得非常大手动设置固定包围盒(Custom Bounds)美术导出时模型带了远离原点的游离顶点资源检查工具中检测包围盒与实际体积比例异常的模型5. 不适合做遮挡者的物体镂空的:栅栏、树木、铁丝网、脚手架;半透明的:玻璃、水面、帷幕;太薄或太小的:路牌、灯柱、小石头;会消失的:可破坏物(除非遮挡系统支持动态更新)。十一、调试与验证:确认"剔对了、剔得值"1. 可视化工具工具用途UnityOcclusion Culling 窗口 → Visualization在 Scene 视图中查看 Umbra 当前剔除结果冻结剔除(Freeze Culling)自己实现一个调试开关:冻结当前剔除结果,然后把相机飞到旁边看,哪些被剔除一目了然深度图显示CPU 光栅化方案把低分辨率深度图画到屏幕角落,检查遮挡者是否正确包围盒着色可见物体画绿框,被剔除的画红框,被视锥剔除的不画Frame Debugger / RenderDoc对比开关剔除前后的 Draw Call 列表2. 核心指标指标含义关注点剔除率被遮挡剔除的物体数 ÷ 视锥内物体数主城可能达到 40%~70%;开阔地带接近 0,说明该区域可以关掉剔除耗时遮挡剔除本身在 CPU/GPU 上花的时间必须明显小于节省下来的时间节省量减少的 Draw Call、三角面、蒙皮角色数结合第二章的瓶颈判断,看省下的是不是瓶颈所在最终帧时间开关对比唯一真正的判断标准3. CPU 方案的被遮挡者测试示意下面是一个简化的 Burst Job,演示"包围盒矩形 vs 低分辨率深度图"的测试逻辑(仅示意,实际实现通常会用分层深度和 SIMD 进一步加速):usingUnity.Burst;usingUnity.Collections;usingUnity.Jobs;publicstructScreenRect{publicintminX,minY,maxX,maxY;// 包围盒投影到深度图上的像素范围publicfloatminDepth;// 包围盒离相机最近点的线性深度publicboolcrossesNearPlane;// 包围盒是否跨过近裁剪面}[BurstCompile]publicstructOccludeeTestJob:IJobParallelFor{// 每个像素存遮挡者的最近深度(线性深度,越大越远;没有遮挡者的像素为 +∞)[ReadOnly]publicNativeArray<float>depthBuffer;[ReadOnly]publicNativeArray<ScreenRect>rects;publicintwidth;[WriteOnly]publicNativeArray<bool>visible;publicvoidExecute(inti){ScreenRectr=rects[i];// 包围盒跨过近裁剪面时投影不可靠,保守地视为可见if(r.crossesNearPlane){visible[i]=true;return;}for(inty=r.minY;y<=r.maxY;y++){for(intx=r.minX;x<=r.maxX;x++){// 只要有一个像素上,物体比遮挡者更近 → 可能可见if(r.minDepth<depthBuffer[y*width+x]){visible[i]=true;return;}}}// 覆盖的所有像素都被更近的遮挡者挡住 → 剔除visible[i]=false;}}💡 注意两个"保守"原则:跨近裁剪面的物体直接视为可见;只要有一个像素可能看得见,就算可见。剔除宁可漏剔,不可错剔。4. 必测的"穿帮"场景场景容易出现的问题快速旋转镜头GPU 方案延迟一帧,物体晚一拍出现站在门口、窗边Smallest Hole 设置过大,透过门窗本该看到的东西消失贴着薄墙、站在墙角代理模型过大或包围盒跨近平面处理不当从高处跳下、坐骑飞行视角高度剧烈变化,预计算数据未覆盖该区域传送、切换场景、过场动画剔除历史没有重置区块流式加载边界新加载区块的遮挡数据尚未就绪建议把这些位置做成固定的相机回放路线,加入第十三章所说的性能场景回归,每次构建自动截图对比。十二、被剔除之后:让其他系统也"偷懒"物体不画了,但如果它的动画、脚本、特效还在跑,只省了一半。遮挡结果应该广播给其他系统:系统被遮挡时的处理AnimatorCulling Mode 设为Cull Update Transforms或Cull Completely(注意根运动角色的位置同步)粒子系统设置 Culling Mode 为Pause或Automatic,并配合准确的包围盒头顶名字、血条被遮挡的玩家不显示或降低更新频率布料、头发物理暂停模拟音效被遮挡时做低通滤波(还能顺便增加真实感)自定义 MonoBehaviour用OnBecameVisible/OnBecameInvisible或自己的剔除回调降频⚠️OnBecameVisible/OnBecameInvisible也会被 Scene 视图相机和阴影相机影响,在编辑器里测试时容易误判,以真机结果为准。十三、常见误区误区真相“开了遮挡剔除就一定更快”开阔场景、俯视角游戏里剔除率很低,剔除本身的开销可能让帧率下降“遮挡者越多越精确越好”每多一个遮挡者,光栅化成本就上升;只要近处的大块遮挡者“代理模型大一点更保险”恰恰相反,代理模型比原模型大会导致错误剔除,必须在原模型内部“手机 GPU 有 HSR,不需要物体级剔除”HSR 只省像素阶段;CPU 提交、顶点、蒙皮、TBDR 的几何带宽都省不了“被剔除的物体就不消耗性能了”动画、粒子、脚本、物理可能还在跑,需要联动处理“相机看不见的物体就可以不投射阴影”墙后的物体影子可能投到墙前,阴影要单独判断“GPU 剔除比 CPU 剔除先进,全机型都上”手机上深度回读要付带宽,计算着色器和间接绘制在中低端机上兼容性和收益都不稳定“编辑器里看起来没问题”编辑器的 CPU、GPU 和手机完全不同;穿帮问题也要在真机上快速转镜头去找十四、落地路线第 1 步 测量:用第二章的方法确认瓶颈在 CPU 提交 / 顶点 / 带宽,而不是像素上色 │ 第 2 步 零成本手段:视锥剔除 + 按物件大小的距离剔除 + LOD │ 第 3 步 保护像素剔除:控制 Alpha Test、半透明、绘制顺序,移动端默认不开深度预渲染 │ 第 4 步 封闭场景(副本、室内、主城静态部分):Unity 预计算遮挡剔除 │ 第 5 步 大世界和动态遮挡:CPU 软件光栅化剔除(Job System + Burst) │ 第 6 步 高端机 + 海量实例:评估 GPU Hi-Z / Unity 6 GPU Occlusion Culling │ 第 7 步 联动:把剔除结果用于动画、特效、UI、物理的降频 │ 第 8 步 验证与回归:可视化、剔除率与耗时统计、穿帮路线回放、热机复测按档位配置:档位建议配置低端视锥 + 激进距离剔除 + 短视距雾效 + 预计算剔除;CPU 光栅化可降分辨率或关闭中端预计算剔除 + CPU 光栅化剔除高端以上全部 + 可选 GPU 剔除(逐机型验证后开启)一句话总结🧱 遮挡剔除就是在画室里动笔前先问一句"这东西会被挡住吗"。手机 GPU 已经免费帮你在上色前问了一遍(HSR / FPK / LRZ),你要做的第一件事是别破坏它;想在派单前就省下工单和顶点,就要在预计算、CPU 光栅化、GPU Hi-Z 之间按场景和机型选择。⚖️ 判断永远只有一个标准:剔除本身花的时间,必须远小于它省下的时间;并且宁可漏剔,不可错剔。
以Unity + 移动端 MMORPG为主要场景。⚠️ 不同 GPU 架构、驱动和 Unity 版本的行为差异很大,文中的数字仅为示意。是否值得做、做到哪一层,以真机实测为准。先打个比方:画室里被挡住的东西还是那间画室。经理派单时说"画一座城",画师们老老实实把城墙后面的每一栋房子、房子里的每一张桌子都画了出来,最后城墙一盖,全白画了。遮挡剔除就是在动笔之前先问一句:“这东西会被挡住吗?”问的时机不同,省下的活也不同:什么时候问谁来问省下了什么① 上色前,逐个像素问GPU 硬件自动完成上色工人的活(像素阶段)② 派单前,逐个物体问预计算数据、CPU 或 GPU 计算经理的工单 + 摆姿势工人的活 + 上色工人的活🎯问得越早,省得越多;但问得越早,判断本身也越贵、越容易出错。遮挡剔除优化的核心,就是在"判断成本"和"节省收益"之间找到平衡。一、先分清三种"剔除"类型剔除什么成本引擎是否自动做视锥剔除镜头视野外的物体很低✅ 自动距离剔除太远、太小、看不清的物体很低需要手动设置遮挡剔除在视野内、但被别的东西挡住的物体中到高需要专门方案视野范围 📷 ──────────────────── ╲ 🏠 被城墙挡住 → 遮挡剔除 ╲ ██ 城墙 ╲ 🌳 在视野内、未被挡 → 正常绘制 ╲ 🌲 太远 → 距离剔除 ╲ 🏰 视野外 → 视锥剔除优先级:先把视锥剔除和距离剔除做到位(几乎零成本),再考虑遮挡剔除。二、先判断:你的游戏值不值得做1. 手机 GPU 已经帮你省了一部分手机 GPU 本身有"被挡住的像素不上色"的能力(下文第三部分详述),所以对不透明物体来说,像素阶段的浪费本来就有限。物体级遮挡剔除在手机上真正能省的,主要是:CPU:剔除、排序、提交 Draw Call 的开销;顶点阶段:被挡住的模型依然要做顶点变换、骨骼蒙皮;带宽:分块渲染(TBDR)的 GPU 会把所有三角形的处理结果写回内存再分到各个小块,三角形越多,带宽越大;阴影:被遮挡的物体如果也不投射阴影(需谨慎判断),阴影阶段也能省。2. 场景类型决定收益场景遮挡关系收益主城、街道、城镇建筑密集、互相遮挡⭐⭐⭐ 高室内、地下城副本墙壁、房间分隔⭐⭐⭐ 高山谷、峡谷地形地形遮挡远处⭐⭐ 中开阔草原、海面几乎没有遮挡⭐ 很低,甚至得不偿失俯视角(45° 固定视角)视角高,很少被挡⭐ 很低🧮算一笔账:遮挡剔除本身要花时间。如果剔除花了 1.5ms,只省下 1ms 的渲染时间,那就是负优化。三、第一层:保护好 GPU 内置的像素剔除(免费的午餐)这一层不需要写任何剔除代码,只需要别把它破坏掉。1. 各家 GPU 的内置手段GPU技术原理简述对绘制顺序的依赖Apple、PowerVRHSR(隐藏面消除)一个小块内所有三角形先比完深度,只给最前面的上色低MaliEarly-ZS + FPK(前向像素剔除)提前做深度测试;后来的像素如果更靠前,可以"杀掉"还在排队的旧像素中AdrenoEarly-Z + LRZ(低分辨率深度)先用一张粗糙的深度图快速剔除大块被挡住的区域较高,从前往后画效果最好通用Early-Z上色之前先测深度,被挡住就跳过高2. 哪些写法会让它们失效破坏者为什么对策Alpha Test(clip / discard)GPU 必须先执行 Shader 才知道这个像素要不要丢,无法提前判断它会不会挡住后面的东西少用;树叶用贴合形状的网格;放到不透明物体之后绘制(Unity 的 AlphaTest 队列默认在 2450)在 Shader 中修改深度(输出SV_Depth)深度要算完 Shader 才知道移动端避免使用半透明物体不写深度,无法遮挡别人,还必须全部画控制面积和层数绘制顺序混乱后画的近处物体无法帮先画的远处物体省活不透明物体从近到远绘制(Unity 默认会排序,但合批、自定义队列可能打乱)天空盒先画整个屏幕先被天空涂满天空盒在不透明物体之后绘制(URP 默认如此)中途切换渲染目标、回读深度打断分块渲染流程,深度信息要写回内存减少渲染目标切换3. 深度预渲染(Depth Prepass)要不要开?先只画一遍深度,再正式上色,确保每个像素只上一次色。桌面 GPU手机 GPU收益常常明显对 Apple/PowerVR/Mali 来说,HSR/FPK 已经做了类似的事代价顶点多处理一遍顶点多处理一遍 + 额外带宽建议视情况开启默认不开;只有 Alpha Test 植被特别多、像素 Shader 特别重时,针对性地给这部分物体开🍽️ 这一层就像"把菜从近到远摆上桌":不花钱,只要别乱摆。四、第二层:Unity 内置的预计算遮挡剔除(Umbra)1. 原理在编辑器里提前烘焙:把场景切成很多格子,计算"站在这个格子里,能看到哪些物体",运行时直接查表。烘焙时(编辑器): 场景 ──▶ 切分空间格子 ──▶ 计算可见性 ──▶ 遮挡数据文件 运行时(CPU): 相机所在格子 ──▶ 查表 ──▶ 得到可见物体列表 ──▶ 只绘制这些2. 物体的两种身份身份含义适合Occluder Static(遮挡者)能挡住别人大型建筑、墙壁、山体、大石头Occludee Static(被遮挡者)能被挡住几乎所有静态物件只做被遮挡者,不做遮挡者—小物件、镂空物体(栅栏、树木)、半透明物体动态物体(角色、怪物)不能参与烘焙,但可以在 Renderer 上勾选Dynamic Occlusion,作为被遮挡者查询。3. 关键参数参数含义调整建议Smallest Occluder小于这个尺寸的物体不作为遮挡者调大:数据更小、运行更快,但剔除更保守Smallest Hole小于这个尺寸的缝隙视为不透光太大会导致透过窗户、门缝本应看见的东西被错误剔除Backface Threshold用于剔除"相机不可能到达的位置"的数据场景封闭性好时可降低以减少数据量4. 配套工具Occlusion Area:只在玩家可能到达的区域烘焙,减少数据量;Occlusion Portal:门、闸门打开和关闭时切换遮挡状态,适合副本中的可开关大门。5. 优缺点✅ 优点❌ 缺点开箱即用,不用写代码只支持静态遮挡者,可破坏的墙、移动的大门无法动态遮挡运行时查询相对便宜遮挡数据占内存,大世界可能很大稳定、没有延迟穿帮烘焙时间长,场景一改就要重新烘焙场景分块流式加载时,各块数据要分别管理查询在 CPU 上进行,物体数量极多时也有开销🎮适用:副本、主城等封闭场景。对开放大世界,往往需要配合下面的方案。五、第三层:硬件遮挡查询(Occlusion Query)——手机上慎用原理画一个物体的包围盒(不上色),问 GPU:"这个盒子有没有像素通过深度测试?"没有,就说明整个物体被挡住了。为什么手机上不推荐CPU 第 N 帧: 发出查询 ──────────────────────▶ 等结果…… GPU: (分块渲染,整帧完成后才有结果) CPU 第 N+1 帧或 N+2 帧: 拿到结果问题说明结果延迟CPU 要等 GPU 画完才知道结果,立即等待会让 CPU 和 GPU 互相卡住延迟使用会穿帮用上一两帧的结果,镜头快速转动时会出现物体"晚一拍才出现"TBDR 不友好分块渲染要处理完整帧才能给出结果,延迟更长每个查询本身就是一次绘制物体多时,查询开销抵消了收益🚫 Unity 也没有对外直接暴露通用的遮挡查询接口。手游中一般不作为主力方案。六、第四层:CPU 软件光栅化遮挡剔除(手游大世界的主力)1. 原理在 CPU 上画一张很小的深度图,只用来判断遮挡:① 选出近处的大型遮挡者(建筑、山体、城墙),使用它们的"简化代理模型" ② 在 CPU 上把代理模型光栅化到一张小深度图(例如 256×128) ③ 对每个待检测物体,把它的包围盒投影到屏幕上,和深度图比较 ④ 包围盒覆盖区域全部比深度图更远 → 被遮挡,不绘制CPU 深度缓冲(低分辨率) ┌────────────────────────┐ │░░░░░░░████████░░░░░░░░░│ ████ = 城墙代理模型 │░░░░░░░████████░░░░░░░░░│ │░░░░░░░██[🏠]██░░░░░░░░░│ 🏠 的包围盒完全落在城墙后面 → 剔除 │░░░░░░░████████░░[🌳]░░░│ 🌳 落在空白区域 → 绘制 └────────────────────────┘业界常见实现思路参考 Intel 的Masked Occlusion Culling(SIMD 加速的分层深度缓冲)。Unity 中可以用Job System + Burst实现,在多个核心上并行。2. 为什么适合手游大世界优点说明支持动态遮挡者和被遮挡者移动的大门、可破坏建筑都能参与当帧结果,没有延迟穿帮不依赖 GPU 回读不需要烘焙适合分块流式加载的大世界不占 GPU手机上 GPU 往往比 CPU 更紧张、更容易发热结果还能用于其他系统例如跳过被遮挡角色的动画更新、特效更新3. 关键设计要点建议遮挡者代理模型由美术制作或工具自动生成:几十到几百个三角形的简化盒体,必须完全在原模型内部(不能比原模型大,否则会错误剔除)遮挡者数量每帧只选离相机最近、屏幕占比最大的若干个(如 30~100 个),按"屏幕面积 ÷ 距离"排序深度图分辨率低分辨率即可,过高会让 CPU 成本飙升被遮挡者检测用包围盒,宁可保守(误判为可见),不要激进(误判为不可见会穿帮)分层组织先测试整个区块、建筑群的大包围盒,被挡住就跳过其中所有物体线程放到工作线程,并注意前面章节说的大小核调度:跑在小核上可能来不及完成4. 成本控制剔除成本 = 光栅化遮挡者 + 测试被遮挡者 目标:中端机上整体控制在 0.5~1ms(工作线程,示意值)低端机可以降低深度图分辨率、减少遮挡者数量,或者直接关闭(如果低端机本来视距就很短)。七、第五层:GPU 驱动的 Hi-Z 遮挡剔除1. 原理利用上一帧的深度图,在 GPU 上用计算着色器(Compute Shader)判断每个物体是否可见,再用间接绘制(Indirect Draw)只画可见的物体。CPU 全程不需要知道结果。第 1 步:构建 Hi-Z(分层深度图)把深度图逐级缩小,每一级的一个像素保存下一级 2×2 区域中最远的深度:Mip0(全分辨率) Mip1 Mip2 ┌──┬──┬──┬──┐ ┌─────┬─────┐ ┌───────────┐ │ 3│ 5│ 9│ 9│ │ │ │ │ │ ├──┼──┼──┼──┤ ─▶ │ 5 │ 9 │ ─▶│ 9 │ │ 4│ 2│ 8│ 7│ │ │ │ │ (最远深度) │ └──┴──┴──┴──┘ └─────┴─────┘ └───────────┘第 2 步:测试物体计算着色器(每个线程处理一个物体): ① 把包围盒投影到屏幕,得到屏幕矩形和最近深度 ② 根据矩形大小选择合适的 Mip 级别(让矩形只覆盖 2×2 个像素) ③ 取出这几个像素的最远深度 ④ 物体最近点 比 遮挡物最远点 还远 → 被完全遮挡 ⑤ 可见的物体写入可见列表,并累加间接绘制参数第 3 步:间接绘制DrawMeshInstancedIndirect / Graphics.RenderMeshIndirect → GPU 根据可见列表决定画多少实例,CPU 不参与2. 时间差问题:上一帧的深度对不上这一帧用上一帧深度判断这一帧,镜头移动后会出现:刚从墙后转出来的物体,晚一帧才出现。解决方式说明两阶段剔除① 用上一帧深度剔除并画出可见物体 → ② 用这一帧刚画出的深度构建新 Hi-Z → ③ 对第一阶段被剔除的物体再测一次,补画误判的深度重投影把上一帧深度按相机运动变换到当前视角(会有空洞)保守放大包围盒简单但效果有限镜头切换时禁用传送、过场动画切镜头时当帧不剔除3. 手机上的现实问题问题说明读回深度要付带宽手机 GPU 通常把深度留在片上,画完直接丢弃(不写回内存)。做 Hi-Z 就必须把深度写回内存,以 2400×1080 分辨率、4 字节深度计算,每帧约 10MB 的额外写入,再加上构建 Mip 的读写计算着色器支持需要 OpenGL ES 3.1+ / Vulkan / Metal;老旧机型和部分驱动存在兼容问题间接绘制的驱动问题部分老旧 Adreno、Mali 驱动的间接绘制有性能或正确性问题合批体系要改造需要实例化渲染、统一的物体数据缓冲,和常规 GameObject 渲染流程差异大发热GPU 本来就是手游最紧张的资源,再加计算任务可能得不偿失4. Unity 6 的内置方案Unity 6 在 URP 中提供了GPU Resident Drawer + GPU Occlusion Culling,可以在不手写 Compute Shader 的情况下使用 GPU 遮挡剔除。它有前提条件(例如需要 Forward+ 渲染路径、支持计算着色器的平台、物体使用兼容的渲染方式等),并且在移动端的收益需要逐机型实测。具体要求以你使用的 Unity 版本文档为准。🎯适用:高端机档位、物体数量极大(如大量植被、密集城市)的场景。中低端机建议保留 CPU 方案。八、方案对比与选择方案剔除粒度动态遮挡者成本在哪延迟穿帮手游推荐度GPU 内置像素剔除像素✅免费无⭐⭐⭐ 必须保护Unity 预计算(Umbra)物体❌CPU 查询 + 内存无⭐⭐⭐ 封闭场景硬件遮挡查询物体✅GPU + 同步等待有⭐ 不推荐CPU 软件光栅化物体✅CPU 工作线程无⭐⭐⭐ 大世界主力GPU Hi-Z物体/实例✅GPU + 带宽有(可修正)⭐⭐ 高端机一个常见的组合┌───────────── 所有档位 ─────────────┐ │ 视锥剔除 + 距离剔除 + 保护像素剔除 │ └──────────────────┬────────────────┘ │ ┌──────────────────────────┼──────────────────────────┐ ▼ ▼ ▼ 副本 / 室内 主城 / 大世界 高端机 + 海量实例 Umbra 预计算剔除 CPU 软件光栅化剔除 GPU Hi-Z + 间接绘制 (可叠加 Umbra 处理静态部分) (植被、密集物件)九、MMO 特有场景场景建议主城挤满玩家角色作为被遮挡者参与剔除:被建筑挡住的玩家不仅不画,还可以跳过动画更新、特效、头顶名字和血条第三人称镜头快速旋转优先选择没有延迟的方案(预计算、CPU 光栅化);GPU 方案要做两阶段剔除镜头可拉远到高空视角越高,遮挡越少,剔除收益下降;可以在高空视角时关闭遮挡剔除,改用更激进的距离剔除和 LOD地形遮挡山脉是天然遮挡者,可以生成地形的低精度代理网格参与 CPU 光栅化(“地平线剔除”)分块流式加载遮挡数据和代理模型跟随区块加载和卸载传送、过场动画镜头瞬移时当帧强制全部可见或重置剔除历史阴影被相机视角遮挡的物体仍可能投射可见的影子,例如墙后的高塔把影子投到墙前的地面上。所以不能直接用相机的遮挡结果来剔除阴影投射者。稳妥的做法是:阴影投射者按光源视角单独做视锥剔除;相机遮挡结果只用于"不投射阴影"或"阴影距离之外"的物体;角色可以用"被遮挡 + 离可见区域足够远"作为跳过阴影的条件十、美术与关卡配合:遮挡剔除的效果一半靠内容剔除算法再好,如果场景是"一眼望到底",也没有东西可剔。收益大小首先由关卡和资源决定。1. 关卡设计:主动制造遮挡做法说明视线阻断街道设计成转弯、错位,不要一条直线大道通到地图尽头天然隔断城门、山口、峡谷、隧道把区域分开,过渡处正好可以做流式加载地形起伏小山丘、高低差挡住远处的建筑群室内外分离室内物件单独分组,只在玩家靠近门窗或进入室内时才可能可见🏛️ 很多经典主城的"曲折小巷"既是美术风格,也是性能设计。2. 模型拆分粒度:不能太大,也不能太碎❌ 太大:整条街合成一个网格 → 只要露出一个角,整条街都要画,剔除完全失效 ❌ 太碎:每块砖、每个花盆都是独立物体 → 剔除测试次数暴涨,CPU 开销超过收益 ✅ 适中:以"一栋建筑""一个房间""一组摆件"为单位 → 剔除有效,测试次数可控这里和第六章"合批"存在矛盾:静态合批会把物体合成大网格,削弱剔除效果。常见做法是按空间区块合批(只合并相邻、通常同时可见的物体),而不是全场景一把合并。3. 遮挡代理模型的制作规范规范说明必须在原模型内部代理模型比原模型大 → 会把原本可见的东西错误剔除,造成"物体闪烁消失"面数极低一般几十到一两百个三角形,用盒子、柱体拼出主体轮廓忽略镂空和细节窗户、栏杆、屋檐的镂空部分直接不做,按"不遮挡"处理命名约定例如Building_A_OCC,导入时由工具自动识别、隐藏、注册为遮挡者自动生成 + 人工检查工具可以按体素化或凸包自动生成,再由美术检查是否超出原模型4. 包围盒要准确剔除依赖包围盒。包围盒虚大,物体就永远剔不掉。常见问题对策骨骼动画角色包围盒过大(例如包含了武器挥舞的最大范围)使用合理大小的包围盒;Skinned Mesh Renderer 不要随意开启Update When Offscreen粒子系统包围盒自动计算得非常大手动设置固定包围盒(Custom Bounds)美术导出时模型带了远离原点的游离顶点资源检查工具中检测包围盒与实际体积比例异常的模型5. 不适合做遮挡者的物体镂空的:栅栏、树木、铁丝网、脚手架;半透明的:玻璃、水面、帷幕;太薄或太小的:路牌、灯柱、小石头;会消失的:可破坏物(除非遮挡系统支持动态更新)。十一、调试与验证:确认"剔对了、剔得值"1. 可视化工具工具用途UnityOcclusion Culling 窗口 → Visualization在 Scene 视图中查看 Umbra 当前剔除结果冻结剔除(Freeze Culling)自己实现一个调试开关:冻结当前剔除结果,然后把相机飞到旁边看,哪些被剔除一目了然深度图显示CPU 光栅化方案把低分辨率深度图画到屏幕角落,检查遮挡者是否正确包围盒着色可见物体画绿框,被剔除的画红框,被视锥剔除的不画Frame Debugger / RenderDoc对比开关剔除前后的 Draw Call 列表2. 核心指标指标含义关注点剔除率被遮挡剔除的物体数 ÷ 视锥内物体数主城可能达到 40%~70%;开阔地带接近 0,说明该区域可以关掉剔除耗时遮挡剔除本身在 CPU/GPU 上花的时间必须明显小于节省下来的时间节省量减少的 Draw Call、三角面、蒙皮角色数结合第二章的瓶颈判断,看省下的是不是瓶颈所在最终帧时间开关对比唯一真正的判断标准3. CPU 方案的被遮挡者测试示意下面是一个简化的 Burst Job,演示"包围盒矩形 vs 低分辨率深度图"的测试逻辑(仅示意,实际实现通常会用分层深度和 SIMD 进一步加速):usingUnity.Burst;usingUnity.Collections;usingUnity.Jobs;publicstructScreenRect{publicintminX,minY,maxX,maxY;// 包围盒投影到深度图上的像素范围publicfloatminDepth;// 包围盒离相机最近点的线性深度publicboolcrossesNearPlane;// 包围盒是否跨过近裁剪面}[BurstCompile]publicstructOccludeeTestJob:IJobParallelFor{// 每个像素存遮挡者的最近深度(线性深度,越大越远;没有遮挡者的像素为 +∞)[ReadOnly]publicNativeArray<float>depthBuffer;[ReadOnly]publicNativeArray<ScreenRect>rects;publicintwidth;[WriteOnly]publicNativeArray<bool>visible;publicvoidExecute(inti){ScreenRectr=rects[i];// 包围盒跨过近裁剪面时投影不可靠,保守地视为可见if(r.crossesNearPlane){visible[i]=true;return;}for(inty=r.minY;y<=r.maxY;y++){for(intx=r.minX;x<=r.maxX;x++){// 只要有一个像素上,物体比遮挡者更近 → 可能可见if(r.minDepth<depthBuffer[y*width+x]){visible[i]=true;return;}}}// 覆盖的所有像素都被更近的遮挡者挡住 → 剔除visible[i]=false;}}💡 注意两个"保守"原则:跨近裁剪面的物体直接视为可见;只要有一个像素可能看得见,就算可见。剔除宁可漏剔,不可错剔。4. 必测的"穿帮"场景场景容易出现的问题快速旋转镜头GPU 方案延迟一帧,物体晚一拍出现站在门口、窗边Smallest Hole 设置过大,透过门窗本该看到的东西消失贴着薄墙、站在墙角代理模型过大或包围盒跨近平面处理不当从高处跳下、坐骑飞行视角高度剧烈变化,预计算数据未覆盖该区域传送、切换场景、过场动画剔除历史没有重置区块流式加载边界新加载区块的遮挡数据尚未就绪建议把这些位置做成固定的相机回放路线,加入第十三章所说的性能场景回归,每次构建自动截图对比。十二、被剔除之后:让其他系统也"偷懒"物体不画了,但如果它的动画、脚本、特效还在跑,只省了一半。遮挡结果应该广播给其他系统:系统被遮挡时的处理AnimatorCulling Mode 设为Cull Update Transforms或Cull Completely(注意根运动角色的位置同步)粒子系统设置 Culling Mode 为Pause或Automatic,并配合准确的包围盒头顶名字、血条被遮挡的玩家不显示或降低更新频率布料、头发物理暂停模拟音效被遮挡时做低通滤波(还能顺便增加真实感)自定义 MonoBehaviour用OnBecameVisible/OnBecameInvisible或自己的剔除回调降频⚠️OnBecameVisible/OnBecameInvisible也会被 Scene 视图相机和阴影相机影响,在编辑器里测试时容易误判,以真机结果为准。十三、常见误区误区真相“开了遮挡剔除就一定更快”开阔场景、俯视角游戏里剔除率很低,剔除本身的开销可能让帧率下降“遮挡者越多越精确越好”每多一个遮挡者,光栅化成本就上升;只要近处的大块遮挡者“代理模型大一点更保险”恰恰相反,代理模型比原模型大会导致错误剔除,必须在原模型内部“手机 GPU 有 HSR,不需要物体级剔除”HSR 只省像素阶段;CPU 提交、顶点、蒙皮、TBDR 的几何带宽都省不了“被剔除的物体就不消耗性能了”动画、粒子、脚本、物理可能还在跑,需要联动处理“相机看不见的物体就可以不投射阴影”墙后的物体影子可能投到墙前,阴影要单独判断“GPU 剔除比 CPU 剔除先进,全机型都上”手机上深度回读要付带宽,计算着色器和间接绘制在中低端机上兼容性和收益都不稳定“编辑器里看起来没问题”编辑器的 CPU、GPU 和手机完全不同;穿帮问题也要在真机上快速转镜头去找十四、落地路线第 1 步 测量:用第二章的方法确认瓶颈在 CPU 提交 / 顶点 / 带宽,而不是像素上色 │ 第 2 步 零成本手段:视锥剔除 + 按物件大小的距离剔除 + LOD │ 第 3 步 保护像素剔除:控制 Alpha Test、半透明、绘制顺序,移动端默认不开深度预渲染 │ 第 4 步 封闭场景(副本、室内、主城静态部分):Unity 预计算遮挡剔除 │ 第 5 步 大世界和动态遮挡:CPU 软件光栅化剔除(Job System + Burst) │ 第 6 步 高端机 + 海量实例:评估 GPU Hi-Z / Unity 6 GPU Occlusion Culling │ 第 7 步 联动:把剔除结果用于动画、特效、UI、物理的降频 │ 第 8 步 验证与回归:可视化、剔除率与耗时统计、穿帮路线回放、热机复测按档位配置:档位建议配置低端视锥 + 激进距离剔除 + 短视距雾效 + 预计算剔除;CPU 光栅化可降分辨率或关闭中端预计算剔除 + CPU 光栅化剔除高端以上全部 + 可选 GPU 剔除(逐机型验证后开启)一句话总结🧱 遮挡剔除就是在画室里动笔前先问一句"这东西会被挡住吗"。手机 GPU 已经免费帮你在上色前问了一遍(HSR / FPK / LRZ),你要做的第一件事是别破坏它;想在派单前就省下工单和顶点,就要在预计算、CPU 光栅化、GPU Hi-Z 之间按场景和机型选择。⚖️ 判断永远只有一个标准:剔除本身花的时间,必须远小于它省下的时间;并且宁可漏剔,不可错剔。