深度缓冲与位置重建
全库目录221
本文目录12
把采样深度乘一个距离,屏幕效果可能贴不住物体,因为缓冲里保存的是投影比较值。先确认投影和深度区间,再还原视空间距离或位置。Reversed-Z改变映射、清除与比较规则;这些条件一致,远近精度和位置重建才可验证。
深度映射
透视投影通过齐次除法生成深度,常规深度缓冲因此通常与视空间轴向距离非线性对应。用它直接做世界距离会出错,需要通过投影参数重建。
深度精度受 Near Plane 影响很大。Near 设得过近,会浪费大量可用精度。
投影深度的推导
透视投影把视锥内的点压到标准立方体。屏幕位置由相似三角形直接给出,但两个视空间深度不同的点可能投影到同一屏幕位置,因此需要保留一个可比较的深度量。
为了让深度表达式与 、 形式一致、便于用齐次坐标和矩阵统一表示,深度取以 为分母的线性分式:
以 Direct3D 约定为例,近裁剪面 映射到 0,远裁剪面 映射到 1。代入两个边界条件解得:
深度非线性来自 。对 求导得到 :距离越远,同样的世界空间深度差对应的缓冲值差越小。固定精度缓冲因此更难区分远处表面。横向投影缩小和深度量化是两类问题,深度误差应由投影公式与存储格式计算。
OpenGL传统NDC深度区间为 ;OpenGL 4.5的glClipControl可选择零到一区间。Direct3D使用 。重建深度和写投影矩阵时必须确认当前 API 与引擎的约定。
Reversed-Z与浮点精度
浮点数在 0 附近的可表示值远比在 1 附近密集。常规映射把远处放在接近 1 的区间,恰好落在浮点精度最稀疏的地方,非线性压缩与浮点分布的稀疏区叠加,远处 Z-Fighting 因此格外严重。
Reversed-Z 把近平面映射到 1、远平面映射到 0。远处落进浮点密集区,能抵消大部分投影造成的精度衰减。以无限远投影 为例,在浮点正规数区间,同一指数段内的深度相对量化误差近似恒定,因此恢复出的距离也有近似稳定的相对精度;绝对误差仍随距离增长。定点格式(如 D24)的刻度均匀,仅反转映射并不改善量化精度。
启用时必须同步四处:投影矩阵、深度 Clear Value(清成 0 而非 1)、深度比较函数(Less 改为 Greater)、以及所有手工重建深度的代码。只把采样结果做 1-z 是错误的。
缓解深度冲突
- 收窄 Near 与 Far 的比值。Near 的影响远大于 Far,把 Near 从 0.01 提到 0.1 的收益通常比压缩 Far 大得多;
- 避免共面或极近的几何,这是美术侧能解决的绝大部分情况;
- 使用 Polygon Offset / Depth Bias 给特定图元一个可控偏移;
- 启用 Reversed-Z 配合浮点深度。
深度缓冲与深度纹理
这是两个容易混淆的概念,服务于不同使用者。
深度附件是光栅化进行深度测试和写入的资源。应用或引擎负责创建、绑定、清除及同步;交换链之外的离屏目标也能有深度附件。光栅器默认提供插值后的投影深度,片元着色器还可通过深度输出覆盖候选值,这会影响提前测试条件。
深度纹理强调资源供着色器读取的用途。许多 API 允许同一深度资源创建采样视图,但需要支持的格式、创建标志、布局与访问状态。引擎也可能选择深度拷贝或独立预通道,尤其涉及 MSAA、相机堆栈和时机约束时。是否需要拷贝以目标管线实际路径为准。
对象是否出现在深度纹理中,要沿生成路径检查写入状态。若纹理来自主通道深度附件的拷贝或直接采样,关闭该通道的 Depth Write 就不会写入这个对象的深度。若深度纹理由独立预通道生成,则检查对象是否参加预通道,以及该通道的深度写入与裁剪状态;主颜色通道的状态不能代替这项判断。
生成深度纹理的常见路径:
- 独立深度通道:用深度着色器绘制所需对象;顶点位移和透明裁剪必须与主通道一致,代价按参与对象与实例批次数量计算;
- 深度拷贝或解析:从已渲染附件生成采样资源,可能使用复制命令,也可能使用全屏绘制或计算通道;MSAA还需定义多个深度样本的归约规则;
- 直接采样附件:GBuffer或主通道已写好所需深度,并且格式、访问状态与采样时机允许复用;使用前仍须建立正确同步。
Unity URP的路径还受相机设置、渲染器特性、平台和包版本影响。记录目标包版本后,在Frame Debugger中确认深度来自预通道、主通道复制还是其他路径;MSAA开关本身不足以唯一确定路径。
从深度重建位置
屏幕空间效果经常需要由深度反推世界位置。两条常用路径:
逆变换法:由屏幕 UV 与深度组装 NDC 坐标,乘以逆 View-Projection 矩阵,再做齐次除法。
射线插值法:设视空间前方为正Z,当前像素射线写成 ,轴向深度为 ,则 。射线应按Z分量归一化。若使用单位长度射线 ,则应乘 ;直接乘z会把画面边缘的位置向相机拉近。角点射线与当前投影对应,正交投影另需逐像素起点。
两条路径的前提不同,必须逐项核对:NDC 的 Y 方向、深度区间是 还是 、是否启用 Reversed-Z、UV 原点在左上还是左下。任何一项与平台约定不符,结果都会整体偏移或翻转。
由深度重建世界位置
输入是屏幕 UV 与深度纹理采样值,输出世界空间位置。以 Direct3D 风格的 深度区间为例:
float3 ReconstructWorldPos(float2 uv, float rawDepth)
{
float2 ndcXY = uv * 2.0 - 1.0;
#if UNITY_UV_STARTS_AT_TOP
ndcXY.y = -ndcXY.y;
#endif
float4 ndc = float4(ndcXY, rawDepth, 1.0);
float4 world = mul(_InvViewProjMatrix, ndc);
return world.xyz / world.w;
}rawDepth 必须是未经线性化的原始深度,与写入时的约定一致;若已调用过线性化函数,再走逆 VP 矩阵会得到错误结果。启用 Reversed-Z 时投影矩阵已包含反转,此处不需额外处理。
验证方法是把重建结果对 1 米取模输出为颜色,画面应出现贴合场景表面的规则网格。网格在斜面上扭曲说明透视除法有误;随相机移动而滑动说明用错了矩阵或深度约定。
对一个离轴点也可检查射线尺度。设正Z视空间位置为(3,0,4),90°水平视场、宽高比1,NDC横坐标为3/4=0.75,左下UV为(0.875,0.5)。近面1、远面11时,设备深度为1.1−1.1/4=0.825。逆函数恢复z=4,Z归一化射线为(0.75,0,1),乘4恰好得到原位置。
单位长度射线却是(0.6,0,0.8),直接乘4得到(2.4,0,3.2),位置整体缩短;改乘4/0.8=5才正确。这个误差在画面中心不明显,在边缘更大,正好解释屏幕效果中心贴合、两侧滑动的现象。
轴向深度的往返检查
沿前述正Z约定,原始深度 的逆函数为 。取近面0.1米、远面1000米,0.1米映射到0、1000米映射到1;1米约为0.90009。反向深度采用 ,逆变换先恢复 。这是有限远平面模型,背景清除值与无穷远投影需要单独处理,避免除以零。
Depth Test 和 Depth Write
Depth Test 判断新样本能否通过。Depth Write 决定通过后是否更新深度。两者独立。
常见情况:
- 不透明物体:测试开,写入开;
- 半透明物体:测试开,写入通常关;
- 天空:常在背景深度位置绘制;
- UI 或叠加效果:根据需求关闭测试或使用特殊比较。
验证方法
- 对近、中、远三个距离做投影与重建往返。
- 开关反向深度时同步检查清除值和比较函数。
- 将重建位置按世界距离画条纹,确认条纹贴合场景。
相关主题
实现与验证:raster_models.py 与 test_raster.py检查近、中、远距离在常规与反向深度下的往返及单调性,运行 python -B 知识库/examples/revision/test_raster.py。HLSL重建片段依赖宿主逆VP矩阵和UV方向,未执行目标引擎画面或深度格式误差测量。
参考资料
- Nathan Reed,Visualizing Depth Precision:2026-10-22核读Why 1/z与Graphing depth maps,支持投影映射、反向浮点分布及OpenGL深度区间说明;本文有限远平面往返由CPU模型复算。
- Unity目标包的深度生成路径属于工程接入项,本文未把某个URP版本的拷贝条件作为通用结论。