1. 深度缓冲
之前我们并没有提到过渲染顺序的问题,也就是当场景中有多个物体时,需要先渲染哪个后渲染哪个。实际上,对于不透明物体,先渲染谁后渲染谁都是一样的,都可以得到正确的渲染结果,这是由于深度缓冲(Z-Buffer)的存在。
在实时渲染中,深度缓冲用于解决可见性的问题,它可以决定哪个物体的哪些部分会被渲染在前面,哪些部分会被其他物体挡住。
其基本思想是,根据深度缓冲中的值来判断片元距离摄像机的距离。当渲染一个片元时,将它的深度值与深度缓冲中存在的值进行比较(当开启了深度测试时),如果片元的深度值大于深度缓冲中的值,代表当前片元距离摄像机更远,会被深度缓冲中记录的物体挡住,此时该片元不应该被渲染到屏幕上,于是该片元被丢弃。如果片元的深度值小于深度缓冲中的值,代表当前片元距离摄像机更近,应该被渲染到屏幕上,并且,如果开启了深度写入,则需要将当前片元的深度值写入深度缓冲,更新深度缓冲中的深度值。
因此,对于不透明的两个物体A和B,由于深度缓冲的存在,先渲染A和先渲染B的结果是一样的,并且这个过程要依赖深度缓冲中值的正确性,或者说,要依赖深度写入的正确性。
对于半透物体,通常需要关闭深度写入,这是因为从正确的渲染效果来看,我们可以透过半透物体看到后边被其遮挡的物体,如果先渲染了某个半透物体A,并且该物体开启了深度写入,其深度值就会被写入深度缓冲中,此时再对其后的某个物体B进行渲染时,B会无法通过深度测试,导致物体B被遮挡无法显示出来。
2. 渲染顺序对结果的影响
即使关闭了深度写入,渲染顺序对于半透物体的渲染结果依然有很重要的影响。
2.1 不透明物体与半透物体
图中A为半透物体,B为不透明物体。
当先渲染B后渲染A时:
- B先进行深度测试,此时深度缓冲中没有值
- B通过深度测试,将深度值写入深度缓冲
- 将B的颜色值写入颜色缓冲
- A再进行深度测试,此时深度缓冲中记录的是B的深度值,该值大于A的深度值
- A通过深度测试,由于半透物体A关闭了深度写入,不更新深度缓冲,此时深度缓冲中记录的仍为B的深度值
- 此时颜色缓冲中的值为B的颜色,将A的颜色值与颜色缓冲中的值进行混合,将结果写入颜色缓冲中
- 最终渲染结果正确
当先渲染A后渲染B:
- A先进行深度测试,此时深度缓冲中没有值
- A通过深度测试,由于半透物体A关闭了深度写入,不更新深度缓冲,此时深度缓冲中仍然没有值
- 将A的颜色值与颜色缓冲中的颜色值进行混合,此时颜色缓冲中没有值,于是将A的值写入颜色缓冲
- B再进行深度测试,此时深度缓冲中仍然没有值
- B通过深度测试,将B的深度写入深度缓冲
- B为不透明物体,B的颜色值会直接覆盖颜色缓冲中的值,于是颜色缓冲中的值变为B的颜色值
- 最终渲染结果变成B遮挡A,渲染错误
2.2 半透物体与半透物体
同理,对于两个均为半透的物体A和B:
- 当先渲染B后渲染A时,B的颜色值会先写入颜色缓冲,最终为用A的颜色值与B的颜色值进行混合,效果为透过A看到B,渲染结果正确。
- 当先渲染A后渲染B时,A的颜色值会先写入颜色缓冲,最终为用B的颜色值与A的颜色值进行混合,效果为透过B看到A,渲染结果错误。
基于以上两点,引擎在渲染时通常会:
- 将物体进行排序
- 先渲染不透明物体,所有不透明物体开启深度测试和深度写入,从近到远进行渲染
- 后渲染半透明物体,开启深度测试,并关闭深度写入,从远到近进行渲染
3. 渲染队列
Unity提供了渲染队列(Render Queue)这一解决方案。渲染队列决定了物体渲染的先后顺序,我们在SubShader的 Tags{} 中设置的 “Queue” 标签就是为物体指定其所属的渲染队列。
Unity定义了5个渲染队列:
Queue | 队列索引 | 描述 |
---|---|---|
Background | 1000 | 最先进行渲染的队列,可以用来渲染那些完全作为背景的物体(比如只会远远做展示用,不会参与到正常的游戏交互中的物体) |
Geometry | 2000 | 第二个进行渲染的队列,不透明物体队列,由于游戏中大部分物体都为正常的不透明物体,因此该队列也是默认的渲染队列 |
AlphaTest | 2500 | 需要进行透明度测试的物体的渲染队列,将透明度测试的物体单独拆分成一个渲染队列,可以提高渲染效率 |
Transparent | 3000 | 半透物体的渲染队列,也就是需要进行透明度混合的队列 |
Overlay | 4000 | 最后一个进行渲染的队列,用于实现一些叠加的表现效果 |