PISCOnoob

导航

深度测试浅述

写在前面:

本文章为个人学习笔记,方便以后自己复习,也希望能帮助到他人。

由于本人水平有限难免出现错误,还请评论区指出,多多指教。

部分图元和素材来源于网络,如有侵权请联系本人删除。

参考资料与链接会在文章末尾贴出。

=======================================================================

在基础图形学我们谈到如何将一个三角形“画”在屏幕上,将一个三角形打碎成像素后再呈现在屏幕上。似乎这个“画”是个非常简单的过程,但是当场景中的物体多了起来,三角形多了起来,位置关系复杂了起来开始相互遮挡覆盖时,情况就变得复杂起来了。

1.1 画家算法

当场景中有多个物体并出现互相遮挡时,对计算机来说怎么绘制就困难了一些。有人从油画的绘制中获得了灵感,想到了画家算法。假设我们现在在野外写生,我们先画远处的天空,山峰,再画近处的森林池塘等,即将我们所看到的景象根据离我们的距离分成不同的层级,由远到近一层一层地覆盖上去。我们在计算机上也做一样的操作来绘制出这幅画,先把最远层的结果放到framebuffer中然后一层层地覆盖重写framebuffer的结果。我们能够得出远近排序正确的结果,但是这么做却会出现性能问题。

如下图,被树覆盖的后面显然我们是看不到的,被山挡住的地方我们也是看不到的(可能后面还有一座山),但是我们却仍然计算,渲染,保存下来,这无疑是一种性能浪费,也就是overdraw问题。在一个极端的例子下,假如我们面前有一列物体,第一个是最大的,挡住了后面所有物体,如此一来根据画家算法从最后面的物体开始渲染直到第一个,实际上除了第一个外我们都看不到,性能被浪费掉了。

此外,我们上面假设是把物体提前排好顺序,因此可以决定先画哪个物体,然而当物体形态变得复杂,如下图三个plane两两覆盖,我们就很难去为物体排先后顺序。

1.2 z-buffer算法

既然为物体排序(计算深度)是很难的,我们或许可以为像素排序,即计算每一个像素的深度。于是人们提出z-buffer算法,用一个深度缓冲区(z-buffer)把场景中每个像素深度保存下来,之后还涉及深度测试(z-test)和深度写入(z-write),我们用这算法能够比较好地处理物体遮挡关系。

我们这里假设我们摄像机在原点看向z正方向,即离我们越远z越大,深度越大,离我们越近z越小深度越小。

简单来说,我们初始化深度缓存为无限大,每个像素只记录离该像素最小的深度,如果新插入的像素(物体)深度比目前小(深度测试),则更新该值(深度写入),并将新的像素值写入颜色缓冲区。

深度缓冲就像颜色缓冲(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,并且(通常)和颜色缓冲有着一样的宽度和高度。深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的

实际上,深度测试和深度写入可做的事情还很多,它是可配置的。

首先深度测试分为通过和不通过两种情况,深度写入分为开启和不开启两种,那么就一共有是四种情况:

  1. 深度测试通过,深度写入开启→更新深度缓冲区,更新颜色缓冲区
  2. 深度测试不通过,深度写入开启→不更新深度缓冲区,不更新颜色缓冲区
  3. 深度测试通过,深度写入不开启→不更新深度缓冲区,更新颜色缓冲区
  4. 深度测试不通过,深度写入不开启→不更新深度缓冲区,不更新颜色缓冲区

而新物体的深度值,跟深度缓冲区对应像素值中的深度值该如何比较也是可以自由配置的:

如下图左上角这种情况就符合我们正常观察到的景象:

我们来看下下面这个例子:

首先我们初始化深度缓冲区把每个像素值设为无限大,然后我们要在左上角渲染一个三角形每个像素深度值都是5,都比原本缓冲区的深度值要小,因此深度测试通过,更新深度缓冲区,更新颜色缓冲区。

接下来我们在左下角渲染一个物体与原本的三角形有部分重叠,于是便跟重叠部分的深度值作比较,比5小的像素才会通过测试,而没有重叠的部分深度值跟无限大作比较自然是可以顺利通过测试,更新相应部分深度值,更新相应颜色缓冲,得到最终结果。

最终我们得到正确的渲染结果的同时也得到正确的深度图(正确的遮挡关系),如下图例子:

我们先看左图右上角是地板位置离我们摄像机最远,深度值(z值)也最大,到深度图上表现为最白最亮;

这个模型的右上方的角离我们摄像机最近,深度值就最小,到深度图上表现为最黑最暗。

 

2.1 半透明与非透明物体的渲染

虽然z-buffer算法原理看上去很简单,但是涉及到渲染场景中不同属性的物体时,还需要做出更多的考量与设置。如何渲染场景中多个物体,给他们如何排序这个问题非常重要。在unity中默认有几种渲染队列,也可以自行定义,比如2001就是在Geometry后,AlphaTest之前。

在unity中,先渲染非透明物体再渲染半透明物体,为什么要这么做呢?

我们先考虑下二者的特性:非透明物体能够遮挡其他物体;而我们能够透过半透明物体看到后面的物体。

在渲染不透明物体时,我们要开启深度测试(zTest),并开启深度写入(zWrite on),确保场景中的不透明物体间有正确的深度关系(正确排序)。如此根据zbuffer算法排序,无论是由近到远还是由远到近渲染不透明物体,最终结果都会是正确的,当然由近到远会更好,因为只要是被近处物体遮挡(即深度小于当前缓冲区的值,没能通过深度测试)的物体就不用渲染了,节省性能。

进行半透明渲染也需要开启深度测试,因为如果半透明物体被不透明物体挡住了,那么对应像素(或fragment)是不需要写入到帧缓冲的,也就是说开启深度测试是为了保证不透明物体和半透明物体之间正常的遮挡关系;然后需要关闭深度写入,因为开启深度写入后我们会得到这样一种错误的结果,那就是近处的半透明fragments居然会挡住远处的半透明fragments,这和我们前面说到的我们可以透过半透明物体看到物体后面的东西是矛盾的!

而且,对于半透明物体来说渲染顺序十分重要,渲染顺序正确与否影响着最终结果:

通过上面的简单分析我们会发现半透明物体的渲染其实比不透明物体渲染复杂很多,我们在渲染半透明物体时需要遵循画家算法由远及近进行绘制,最终效果才会比较正确。

 

参考资料:

1.现代计算机图形学入门--闫令琪

2.Unity shader入门精要–冯乐乐

3.https://space.bilibili.com/7398208/channel/seriesdetail?sid=1067039-TA百人计划

posted on 2022-11-01 10:40  PISCOnoob  阅读(264)  评论(0编辑  收藏  举报