一、深度图基础

各位同学好,我是老张。做图形学渲染这些年,我越来越觉得深度图这东西,就像渲染管线的"地基"。地基没打牢,后面做再多花哨的后处理都是白搭。今天咱们就来聊聊这个基础中的基础。

1.1 什么是深度图

深度图,说白了就是一张记录了"每个像素离相机有多远"的灰度图。嗯,你可以把它想象成一张"距离地图"——越近的地方越亮(白色),越远的地方越暗(黑色)。

我记得刚入行那会儿,有同事问我:"深度图不就是一张黑白图吗?"我当时笑了笑,心想:要是真这么简单,我们这些搞渲染的就不用天天跟精度问题死磕了。

实际上,深度图里存储的不是真实世界的物理距离,而是经过投影变换后的归一化深度值。这个值通常在 [0, 1] 范围内,0 代表近裁剪面,1 代表远裁剪面。

核心概念:深度图 = 每个像素对应的深度缓冲区值,用于表示场景中物体到相机的相对距离。

1.2 深度缓冲区的原理

深度缓冲区(Depth Buffer),也叫 Z-Buffer,是渲染管线里一个非常重要的"裁判"。它的工作很简单:判断哪个物体在前面,哪个在后面。

你想想看,当场景里有多个物体重叠时,GPU 怎么知道该显示哪个?靠的就是深度测试。具体流程是这样的:

  1. 每个像素在光栅化时,会计算出一个深度值
  2. 把这个值和深度缓冲区里已有的值做比较
  3. 如果新值更小(离相机更近),就更新颜色和深度
  4. 否则,直接丢弃这个片段

我曾经在一个项目里遇到过这样的坑:场景里有个半透明的玻璃窗,结果后面的物体死活显示不出来。排查了半天才发现,是深度测试的写入设置搞错了。透明物体不应该写入深度缓冲区,否则后面的不透明物体就会被"挡住"。

避坑指南:我曾经因为深度测试顺序搞反,导致整个场景的遮挡关系完全错乱。记住,默认情况下是 Less 或 LessEqual 模式,新值更小才通过。

1.3 深度图的数据格式与精度

深度图的数据格式,这玩意儿可讲究了。不同的精度,直接影响你的渲染效果和性能。

格式 精度 适用场景 我个人的建议
D16 16位浮点 简单场景、移动端 精度够用,但远处容易有锯齿
D24S8 24位深度 + 8位模板 大多数游戏场景 我最常用的格式,性价比高
D32F 32位浮点 高精度需求、大场景 精度无敌,但带宽消耗大

这里有个关键点:深度值的分布不是线性的。因为透视投影的关系,靠近相机的区域深度变化快,远处变化慢。这就导致了一个问题——近处精度高,远处精度低。

我建议你在做后处理时,如果需要从深度图还原世界坐标,一定要做逆投影变换。直接拿深度值当线性距离用,结果会非常离谱。

小技巧:在 Unity 里,可以用 LinearEyeDepth() 函数把非线性深度转成线性深度。在 Unreal 里,对应的函数是 GetWorldPosition() 配合深度值计算。

1.4 深度图在渲染管线中的位置

深度图在整个渲染管线里,扮演着"中间人"的角色。它既不是输入,也不是最终输出,而是在光栅化阶段生成,在后续阶段被反复使用。

下面这张图是我自己画的,能帮你快速理解深度图在管线里的位置:

深度图在渲染管线中的位置 顶点处理 Vertex Shader 光栅化 生成片段 + 深度 深度测试 Z-Test 输出 深度缓冲区 Depth Buffer 写入深度 读取深度 后处理阶段:读取深度图 → 实现景深、雾效、边缘检测、SSAO 等效果 后处理读取

从这张图你可以看到,深度缓冲区在光栅化阶段被写入,在深度测试阶段被读取,最后在后处理阶段被再次读取。也就是说,深度图贯穿了整个渲染流程的后半段。

我建议你在做后处理时,一定要搞清楚你的深度图是在哪个阶段生成的。比如在 Unity 的 URP 里,深度图默认是在不透明物体渲染完成后才生成的。如果你在透明物体阶段去读深度图,拿到的可能是不完整的数据。

关键总结:深度图不是最终输出,而是渲染管线中的"中间产物"。它服务于深度测试和后处理两个核心环节。

1.5 深度图的实际应用场景

说了这么多理论,咱们来看看深度图到底能干啥。我列几个常见的应用:

  • 景深效果:根据深度值模糊远处或近处的像素,模拟相机镜头效果
  • 雾效:用深度值计算雾的浓度,实现距离雾
  • 边缘检测:通过深度值的突变来检测物体边缘
  • SSAO:环境光遮蔽,利用深度值判断遮挡关系
  • 阴影映射:从光源视角生成深度图,用于阴影计算

我记得有一次做景深效果,发现近处的物体模糊得不对劲。排查了半天,原来是深度图的精度不够,导致模糊半径计算错误。后来换成 D32F 格式,问题就解决了。所以说,选对深度格式真的很重要。

实用建议:如果你只是做简单的雾效或边缘检测,D16 格式就够了。但如果你要做高质量的景深或 SSAO,建议用 D32F 或者至少 D24S8。

好了,深度图的基础知识就聊到这儿。下一章咱们会深入讲讲如何在实际项目中获取和操作深度图,包括在 Unity 和 Unreal 里的具体实现方式。到时候我会分享一些我在项目中踩过的坑和总结的技巧。


专注资料整理