遮挡剔除能降低Overdraw,但仅在静态遮挡物多、视角变化慢时有效;它跳过被完全遮挡物体的绘制,减少像素着色器执行,尤其利于fill-rate受限设备。

什么是遮挡剔除,它真能降低 Overdraw 吗
遮挡剔除不是“自动优化”,它只在场景中存在大量静态遮挡物(比如墙壁、建筑体块、地形起伏)且相机视角变化较慢时才有效。它通过提前判断哪些物体被其他不透明物体完全挡住,从而跳过它们的绘制调用和像素着色器执行——这直接减少 Overdraw(同一像素被多次着色),尤其对 fill-rate 受限的 GPU(如移动端或低端显卡)效果明显。但若场景以半透明物体为主、或动态物体占比极高(如大量移动角色),Occlusion Culling 的查询开销可能反超收益。
硬件遮挡查询(Hardware Occlusion Query)怎么写才不卡帧
核心是避免同步等待 GPU 返回结果,必须用异步方式轮询。典型错误是直接调用 glGetQueryObjectuiv 或 vkGetQueryPoolResults 并阻塞 CPU,导致帧率骤降。
- 用双缓冲或环形缓冲区管理查询:每帧提交一批
glBeginQuery(GL_ANY_SAMPLES_PASSED, queryID),下一帧再读前一帧的结果 - 查询对象数量要节制:不要为每个模型网格都建一个查询,按包围盒(
AABB)或层级(如Octree节点)聚合,单次查询覆盖多个物体 - 采样频率可降:不是每帧都查所有物体,对远处或小物体可隔 N 帧更新一次可见性状态
- 注意深度预通道(Z-Prepass):确保在查询前已渲染过主要遮挡体的深度,否则
GL_ANY_SAMPLES_PASSED总返回 false
// OpenGL 示例片段(简化) GLuint query; glGenQueries(1, &query); glBeginQuery(GL_ANY_SAMPLES_PASSED, query); // 绘制遮挡体(如墙体)的深度+颜色(或仅深度) glEndQuery(GL_ANY_SAMPLES_PASSED); // ……之后某帧用 glIsQuery(query) + glGetQueryObjectuiv 非阻塞读取
Unity / Unreal 里开个选项就完事?别信
Unity 的 Occlusion Culling 系统(基于预计算的 occlusion culling data)只对 Static 标记物体生效,且烘焙过程不支持运行时变化的遮挡体(比如打开的门、升起的桥)。常见坑:
- 烘焙后移动了静态物体,遮挡关系不会更新,导致误剔除或漏剔除
- 使用
SkinnedMeshRenderer的角色即使标了 Static,也不会参与烘焙(骨骼动画破坏静态假设) -
Camera.nearClipPlane太小会导致近处遮挡体被裁掉,查询失效 - Unreal 的
Early Z Pass和Hierarchical Z Buffer是更轻量的替代方案,但需手动开启r.HZBEnable,且依赖 depth buffer 精度,对 alpha-test 物体支持差
自己实现软件遮挡剔除(Software Occlusion Culling)值不值得
仅当你的场景结构高度规则(如体素世界、格子化建筑群)、且目标平台不支持硬件查询(如 WebAssembly + WebGL 1.0)时才考虑。主流做法是用粗粒度的 Portal Culling 或 PVS(Potentially Visible Set) 预计算,而非逐物体射线检测——后者在 CPU 上跑不动。
- 用
frustum culling先筛一轮,再对剩余物体做遮挡测试(比如从相机投射到物体中心点的深度比较) - 拒绝使用
raycast判断每个三角面:开销远高于 GPU 查询 - 若用
Hi-Z降采样纹理做 CPU 端近似测试,注意纹理同步开销(glReadPixels很慢)
真正难的是平衡:剔除精度越高,CPU/GPU 开销越大;越粗糙,Overdraw 节省越少。实际项目里,往往把 Occlusion Culling 当作最后一层优化,优先做好 LOD、frustum culling 和 instancing。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











