Unity UI深度遮挡 UIDepthOccluder
本文最后更新于 2026年7月10日 下午
https://github.com/NicoIer/UnityToolkit/blob/main/Runtime/Renderer/UIDepthOccluder.cs
https://github.com/NicoIer/UnityToolkit/blob/main/Runtime/Renderer/UIDepthOccluderFeature.cs
UIDepthOccluder
大块不透明 UI 最后肯定会盖住场景,但 UI 绘制之前,它背后的 Opaque 物体还是会正常经过光栅化和片元着色。
这里的方案是在 BeforeRenderingOpaques 阶段,先按 UI 的屏幕范围写一块近裁剪面深度。后面的 Opaque 像素会尽量在深度测试阶段被挡掉。
它不会减少 CPU 提交、DrawCall 或顶点处理,主要省的是被 UI 覆盖区域的片元开销,而且后续 Shader 必须正常使用深度测试。UI 是半透明的、面积很小或者背后的 Shader 很便宜时,多画一次深度可能反而更亏。
流程
UIDepthOccluder收集当前完全不透明的 UI- 把
RectTransform转成 viewport 范围 UIDepthOccluderFeature在 Opaque 前插入 RenderPass- Shader 只写深度,不写颜色
UIDepthOccluder 挂在 UI Image 上,Renderer Feature 挂在 URP Renderer Data 上。
Shader 使用:
1 | |
默认用一个矩形 Quad。如果是圆形头像或异形边框,可以传自定义 occluderMesh。Mesh 顶点使用 normalized [0,1] 坐标,(0,0) 是左下角,(1,1) 是右上角。
收集遮挡体
组件启用时检查 Image 的继承 alpha,完全不透明才注册:
1 | |
淡入淡出过程中 alpha 会变化,所以 LateUpdate 还要继续同步注册状态:
1 | |
alpha 小于 1 时不能写这块深度,否则 UI 还没完全显示,后面的场景已经被挡掉了。
转换到 Viewport
默认 Mesh 在 [0,1] 范围内,绘制前用 RectTransform 的世界角点算出 viewport 范围。
下面截取的是 ScreenSpaceOverlay 分支,世界角点可以直接除以屏幕尺寸:
1 | |
然后构造 localToViewport:
1 | |
其他 Canvas 模式会先取 canvas.worldCamera 或当前相机,再用 WorldToScreenPoint 转到屏幕坐标。最后得到的矩阵会直接传给 cmd.DrawMesh。
当前算法只取左下角和右上角,适合不旋转的矩形 UI。UI 有旋转时范围会不准。
在 Opaque 前写深度
Pass 的执行时机:
1 | |
RenderGraph 路径直接写当前激活的深度纹理:
1 | |
每个遮挡体是一次 DrawMesh。没有配置 Mesh 时使用默认 Quad:
1 | |
当前 Feature 只处理带 MainCamera tag 的相机:
1 | |
项目里的主相机没有这个 tag 时,Pass 不会执行。
Shader
关键状态只有几个:
1 | |
顶点阶段把 viewport 坐标转成 clip space,并把深度放到近裁剪面:
1 | |
ColorMask 0 不写颜色,ZTest Always 保证这块区域的深度被覆盖。后续落在这里的 Opaque 像素会被深度测试剔除。
异形 Mesh
矩形 UI 直接用默认 Quad。
如果要按 Sprite Alpha 生成异形遮挡,可以在 Editor 中离线处理:
- 采样 Sprite alpha,得到 bool 网格
- 合并连续区域
- 输出 normalized
[0,1]空间的 Mesh
这个 Mesh 只描述遮挡范围。UI 本身仍然由原来的 Image 绘制。
限制
- Pass 在
BeforeRenderingOpaques,主要优化 Opaque;透明物体是否被挡取决于它自己的深度状态 - 只支持继承 alpha 为 1 的 Image
- 当前 viewport 矩阵不支持旋转矩形
- 每个遮挡体都会增加一次深度绘制
- 只处理
MainCamera
兼容模式还有一个命名问题。分支里读取的是:
1 | |
运行时代码实际字段是:
1 | |
启用 URP_COMPATIBILITY_MODE 前需要把名字统一。
没有效果时
- 检查相机的
MainCameratag - 检查 Renderer Data 是否添加
UIDepthOccluderFeature - 检查 Shader 是否为
Hidden/UnityToolkit/UIDepthOccluder - 检查 Image 的继承 alpha
- 用 Frame Debugger 确认 Pass 在 Opaque 前执行
- 临时去掉
ColorMask 0,观察遮挡 Mesh 的位置
先确认 Pass 有没有执行,再查矩阵和深度。