Rewrite 04-01
@@ -3,197 +3,198 @@
|
||||
原文 | [Depth testing](http://learnopengl.com/#!Advanced-OpenGL/Depth-testing)
|
||||
---|---
|
||||
作者 | JoeyDeVries
|
||||
翻译 | [Django](http://bullteacher.com/)
|
||||
校对 | [Geequlim](http://geequlim.com)
|
||||
翻译 | Meow J
|
||||
校对 | 暂未校对
|
||||
|
||||
在[坐标系的教程](../01 Getting started/08 Coordinate Systems.md)中我们呈现了一个3D容器,使用**深度缓冲(Depth Buffer)**,以防止被其他面遮挡的面渲染到前面。在本教程中我们将细致地讨论被深度缓冲(或z-buffer)所存储的**深度值**以及它是如何确定一个片段是否被其他片段遮挡。
|
||||
在[坐标系统](../01 Getting started/08 Coordinate Systems.md)小节中,我们渲染了一个3D箱子,并且运用了<def>深度缓冲</def>(Depth Buffer)来防止被阻挡的面渲染到其它面的前面。在这一节中,我们将会更加深入地讨论这些储存在深度缓冲(或z缓冲(z-buffer))中的<def>深度值</def>(Depth Value),以及它们是如何确定一个片段是处于其它片段后方的。
|
||||
|
||||
**深度缓冲**就像**颜色缓冲(Color Buffer)**(存储所有的片段颜色:视觉输出)那样存储每个片段的信息,(通常) 和颜色缓冲区有相同的宽度和高度。深度缓冲由窗口系统自动创建并将其深度值存储为 16、 24 或 32 位浮点数。在大多数系统中深度缓冲区为24位。
|
||||
深度缓冲就像<def>颜色缓冲</def>(Color Buffer)(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,并且(通常)和颜色缓冲有着一样的宽度和高度。深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的。
|
||||
|
||||
当深度测试启用的时候, OpenGL 测试深度缓冲区内的深度值。OpenGL 执行深度测试的时候,如果此测试通过,深度缓冲内的值将被设为新的深度值。如果深度测试失败,则丢弃该片段。
|
||||
当深度测试(Depth Testing)被启用的时候,OpenGL会将一个片段的的深度值与深度缓冲的内容进行对比。OpenGL会执行一个深度测试,如果这个测试通过了的话,深度缓冲将会更新为新的深度值。如果深度测试失败了,片段将会被丢弃。
|
||||
|
||||
深度测试在片段着色器运行之后(并且模板测试运行之后,我们将在[接下来](http://www.learnopengl.com/#!Advanced-OpenGL/Stencil-testing)的教程中讨论)在屏幕空间中执行的。屏幕空间坐标直接有关的视区,由OpenGL的`glViewport`函数给定,并且可以通过GLSL的片段着色器中内置的 `gl_FragCoord`变量访问。`gl_FragCoord` 的 X 和 y 表示该片段的屏幕空间坐标 ((0,0) 在左下角)。`gl_FragCoord` 还包含一个 z 坐标,它包含了片段的实际深度值。此 z 坐标值是与深度缓冲区的内容进行比较的值。
|
||||
深度缓冲是在片段着色器运行之后(以及模板测试(Stencil Testing)运行之后,我们将在[下一节](02 Stencil testing.md)中讨论)在屏幕空间中运行的。屏幕空间坐标与通过OpenGL的<fun>glViewport</fun>所定义的视口密切相关,并且可以直接使用GLSL内建变量<var>gl_FragCoord</var>从片段着色器中直接访问。<var>gl_FragCoord</var>的x和y分量代表了片段的屏幕空间坐标(其中(0, 0)位于左下角)。<var>gl_FragCoord</var>中也包含了一个z分量,它包含了片段真正的深度值。z值就是需要与深度缓冲内容所对比的那个值。
|
||||
|
||||
!!! Important
|
||||
|
||||
现在大多数 GPU 都支持一种称为提前深度测试(Early depth testing)的硬件功能。提前深度测试允许深度测试在片段着色器之前运行。明确一个片段永远不会可见的 (它是其它物体的后面) 我们可以更早地放弃该片段。
|
||||
现在大部分的GPU都提供一个叫做提前深度测试(Early Depth Testing)的硬件特性。提前深度测试允许深度测试在片段着色器之前运行。只要我们清楚一个片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段。
|
||||
|
||||
片段着色器通常是相当费时的所以我们应该尽量避免运行它们。对片段着色器提前深度测试一个限制是,你不应该写入片段的深度值。如果片段着色器将写入其深度值,提前深度测试是不可能的,OpenGL不能事先知道深度值。
|
||||
片段着色器通常开销都是很大的,所以我们应该尽可能避免运行它们。当使用提前深度测试时,片段着色器的一个限制是你不能写入片段的深度值。如果一个片段着色器对它的深度值进行了写入,提前深度测试是不可能的。OpenGL不能提前知道深度值。
|
||||
|
||||
深度测试默认是关闭的,要启用深度测试的话,我们需要用`GL_DEPTH_TEST`选项来打开它:
|
||||
深度测试默认是禁用的,所以如果要启用深度测试的话,我们需要用<var>GL_DEPTH_TEST</var>选项来启用它:
|
||||
|
||||
```c++
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
```
|
||||
|
||||
一旦启用深度测试,如果片段通过深度测试,OpenGL自动在深度缓冲区存储片段的 z 值,如果深度测试失败,那么相应地丢弃该片段。如果启用深度测试,那么在每个渲染之前还应使用`GL_DEPTH_BUFFER_BIT`清除深度缓冲区,否则深度缓冲区将保留上一次进行深度测试时所写的深度值
|
||||
当它启用的时候,如果一个片段通过了深度测试的话,OpenGL会在深度缓冲中储存该片段的z值;如果没有通过深度缓冲,则会丢弃该片段。如果你启用了深度缓冲,你还应该在每个渲染迭代之前使用<var>GL_DEPTH_BUFFER_BIT</var>来清除深度缓冲,否则你会仍在使用上一次渲染迭代中的写入的深度值:
|
||||
|
||||
```c++
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
```
|
||||
|
||||
在某些情况下我们需要进行深度测试并相应地丢弃片段,但我们不希望更新深度缓冲区,基本上,可以使用一个只读的深度缓冲区;OpenGL允许我们通过将其深度掩码设置为`GL_FALSE`禁用深度缓冲区写入:
|
||||
可以想象,在某些情况下你会需要对所有片段都执行深度测试并丢弃相应的片段,但**不**希望更新深度缓冲。基本上来说,你在使用一个<def>只读的</def>(Read-only)深度缓冲。OpenGL允许我们禁用深度缓冲的写入,只需要设置它的深度遮罩(Depth Mask)设置为`GL_FALSE`就可以了:
|
||||
|
||||
```c++
|
||||
glDepthMask(GL_FALSE);
|
||||
```
|
||||
|
||||
注意这只在深度测试被启用的时候有效。
|
||||
注意这只在深度测试被启用的时候才有效果。
|
||||
|
||||
## 深度测试函数
|
||||
|
||||
OpenGL 允许我们修改它深度测试使用的比较运算符(comparison operators)。这样我们能够控制OpenGL通过或丢弃碎片和如何更新深度缓冲区。我们可以通过调用`glDepthFunc`来设置比较运算符 (或叫做深度函数(depth function)):
|
||||
OpenGL允许我们修改深度测试中使用的比较运算符。这允许我们来控制OpenGL什么时候该通过或丢弃一个片段,什么时候去更新深度缓冲。我们可以调用<fun>glDepthFunc</fun>函数来设置比较运算符(或者说深度函数(Depth Function)):
|
||||
|
||||
```c++
|
||||
glDepthFunc(GL_LESS);
|
||||
```
|
||||
|
||||
该函数接受在下表中列出的几个比较运算符:
|
||||
这个函数接受下面表格中的比较运算符:
|
||||
|
||||
运算符|描述
|
||||
函数|描述
|
||||
----------|------------------
|
||||
GL_ALWAYS |永远通过测试
|
||||
GL_NEVER |永远不通过测试
|
||||
GL_LESS |在片段深度值小于缓冲区的深度时通过测试
|
||||
GL_EQUAL |在片段深度值等于缓冲区的深度时通过测试
|
||||
GL_LEQUAL |在片段深度值小于等于缓冲区的深度时通过测试
|
||||
GL_GREATER |在片段深度值大于缓冲区的深度时通过测试
|
||||
GL_NOTEQUAL|在片段深度值不等于缓冲区的深度时通过测试
|
||||
GL_GEQUAL |在片段深度值大于等于缓冲区的深度时通过测试
|
||||
GL_ALWAYS | 永远通过深度测试
|
||||
GL_NEVER | 永远不通过深度测试
|
||||
GL_LESS | 在片段深度值小于缓冲的深度值时通过测试
|
||||
GL_EQUAL | 在片段深度值等于缓冲区的深度值时通过测试
|
||||
GL_LEQUAL | 在片段深度值小于等于缓冲区的深度值时通过测试
|
||||
GL_GREATER | 在片段深度值大于缓冲区的深度值时通过测试
|
||||
GL_NOTEQUAL| 在片段深度值不等于缓冲区的深度值时通过测试
|
||||
GL_GEQUAL | 在片段深度值大于等于缓冲区的深度值时通过测试
|
||||
|
||||
默认情况下使用`GL_LESS`,这将丢弃深度值高于或等于当前深度缓冲区的值的片段。
|
||||
默认情况下使用的深度函数是<var>GL_LESS</var>,它将会丢弃深度值大于等于当前深度缓冲值的所有片段。
|
||||
|
||||
让我们看看改变深度函数对输出的影响。我们将使用新的代码显示一个由两个带纹理的立方体和带纹理的地板组成的没有光照的基本场景。你可以在这里找到[源代码](http://learnopengl.com/code_viewer.php?code=advanced/depth_testing_func)和其[着色器](http://learnopengl.com/code_viewer.php?code=advanced/depth_testing_func_shaders)代码。
|
||||
让我们看看改变深度函数会对视觉输出有什么影响。我们将使用一个新的代码配置,它会显示一个没有光照的基本场景,里面有两个有纹理的立方体,放置在一个有纹理的地板上。你可以在[这里](https://learnopengl.com/code_viewer_gh.php?code=src/4.advanced_opengl/1.1.depth_testing/depth_testing.cpp)找到源代码。
|
||||
|
||||
代码中我们将深度函数设为`GL_ALWAYS`:
|
||||
在源代码中,我们将深度函数改为<var>GL_ALWAYS</var>:
|
||||
|
||||
```c++
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_ALWAYS);
|
||||
```
|
||||
|
||||
这和我们没有启用深度测试得到了相同的行为。深度测试只是简单地通过,所以这样最后绘制的片段就会呈现在之前绘制的片段前面,即使他们应该在前面。由于我们最后绘制地板平面,那么平面的片段会覆盖每个容器的片段:
|
||||
这将会模拟我们没有启用深度测试时所得到的结果。深度测试将会永远通过,所以最后绘制的片段将会总是会渲染在之前绘制片段的上面,即使之前绘制的片段本就应该渲染在最前面。因为我们是最后渲染地板的,它会覆盖所有的箱子片段:
|
||||
|
||||

|
||||
|
||||
重新设置到`GL_LESS`给了我们曾经的场景:
|
||||
将它重新设置为<var>GL_LESS</var>,这会将场景还原为原有的样子:
|
||||
|
||||

|
||||
|
||||
## 深度值精度
|
||||
|
||||
在深度缓冲区中包含深度值介于`0.0`和`1.0`之间,从观察者看到其内容与场景中的所有对象的 z 值进行了比较。这些视图空间中的 z 值可以在投影平头截体的近平面和远平面之间的任何值。我们因此需要一些方法来转换这些视图空间 z 值到 [0,1] 的范围内,方法之一就是线性将它们转换为 [0,1] 范围内。下面的 (线性) 方程把 z 值转换为 0.0 和 1.0 之间的值 :
|
||||
深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。观察空间的z值可能是投影平截头体的**近平面**(Near)和**远平面**(Far)之间的任何值。我们需要一种方式来将这些观察空间的z值变换到[0, 1]范围之间,其中的一种方式就是将它们线性变换到[0, 1]范围之间。下面这个(线性)方程将z值变换到了0.0到1.0之间的深度值:
|
||||
|
||||
$$
|
||||
\begin{equation} F_{depth} = \frac{z - near}{far - near} \end{equation}
|
||||
$$
|
||||
|
||||
这里far和near是我们用来提供到投影矩阵设置可见视图截锥的远近值 (见[坐标系](../01 Getting started/08 Coordinate Systems.md))。方程带内锥截体的深度值 z,并将其转换到 [0,1] 范围。在下面的图给出 z 值和其相应的深度值的关系:
|
||||
这里的\(near\)和\(far\)值是我们之前提供给投影矩阵设置可视平截头体的(见[坐标系统](../01 Getting started/08 Coordinate Systems.md))那个 *near* 和 *far* 值。这个方程需要平截头体中的一个z值,并将它变换到了[0, 1]的范围中。z值和对应的深度值之间的关系可以在下图中看到:
|
||||
|
||||

|
||||
|
||||
!!! Important
|
||||
|
||||
注意在物体接近近平面的时候,方程给出的深度值接近0.0,物体接近远平面时,方程给出的深度接近1.0。
|
||||
注意所有的方程都会将非常近的物体的深度值设置为接近0.0的值,而当物体非常接近远平面的时候,它的深度值会非常接近1.0。
|
||||
|
||||
然而,在实践中是几乎从来不使用这样的线性深度缓冲区。正确的投影特性的非线性深度方程是和1/z成正比的 。这样基本上做的是在z很近是的高精度和 z 很远的时候的低精度。用几秒钟想一想: 我们真的需要让1000单位远的物体和只有1单位远的物体的深度值有相同的精度吗?线性方程没有考虑这一点。
|
||||
然而,在实践中是几乎永远不会使用这样的<def>线性深度缓冲</def>(Linear Depth Buffer)的。要想有正确的投影性质,需要使用一个非线性的深度方程,它是与 1/z 成正比的。它做的就是在z值很小的时候提供非常高的精度,而在z值很远的时候提供更少的精度。花时间想想这个:我们真的需要对1000单位远的深度值和只有1单位远的充满细节的物体使用相同的精度吗?线性方程并不会考虑这一点。
|
||||
|
||||
由于非线性函数是和 1/z 成正比,例如1.0 和 2.0 之间的 z 值,将变为 1.0 到 0.5之间, 这样在z非常小的时候给了我们很高的精度。50.0 和 100.0 之间的 Z 值将只占 2%的浮点数的精度,这正是我们想要的。这类方程,也需要近和远距离考虑,下面给出:
|
||||
由于非线性方程与 1/z 成正比,在1.0和2.0之间的z值将会变换至1.0到0.5之间的深度值,这就是一个float提供给我们的一半精度了,这在z值很小的情况下提供了非常大的精度。在50.0和100.0之间的z值将会只占2%的float精度,这正是我们所需要的。这样的一个考虑了远近距离的方程是这样的:
|
||||
|
||||
$$
|
||||
\begin{equation} F_{depth} = \frac{1/z - 1/near}{1/far - 1/near} \end{equation}
|
||||
$$
|
||||
|
||||
如果你不知道这个方程到底怎么回事也不必担心。要记住的重要一点是在深度缓冲区的值不是线性的屏幕空间 (它们在视图空间投影矩阵应用之前是线性)。值为 0.5 在深度缓冲区并不意味着该对象的 z 值是投影平头截体的中间;顶点的 z 值是实际上相当接近近平面!你可以看到 z 值和产生深度缓冲区的值在下列图中的非线性关系:
|
||||
如果你不知道这个方程是怎么回事也不用担心。重要的是要记住深度缓冲中的值在屏幕空间中不是线性的(在透视矩阵应用之前在观察空间中是线性的)。深度缓冲中0.5的值并不代表着物体的z值是位于平截头体的中间了,这个顶点的z值实际上非常接近近平面!你可以在下图中看到z值和最终的深度缓冲值之间的非线性关系:
|
||||
|
||||

|
||||
|
||||
正如你所看到,一个附近的物体的小的 z 值因此给了我们很高的深度精度。变换 (从观察者的角度) 的 z 值的方程式被嵌入在投影矩阵,所以当我们变换顶点坐标从视图到裁剪,然后到非线性方程应用了的屏幕空间中。如果你好奇的投影矩阵究竟做了什么我建议阅读[这个文章](http://www.songho.ca/opengl/gl_projectionmatrix.html)。
|
||||
可以看到,深度值很大一部分是由很小的z值所决定的,这给了近处的物体很大的深度精度。这个(从观察者的视角)变换z值的方程是嵌入在投影矩阵中的,所以当我们想将一个顶点坐标从观察空间至裁剪空间的时候这个非线性方程就被应用了。如果你想深度了解投影矩阵究竟做了什么,我建议阅读[这篇文章](http://www.songho.ca/opengl/gl_projectionmatrix.html)。
|
||||
|
||||
接下来我们看看这个非线性的深度值。
|
||||
如果我们想要可视化深度缓冲的话,非线性方程的效果很快就会变得很清楚。
|
||||
|
||||
## 深度缓冲区的可视化
|
||||
## 深度缓冲的可视化
|
||||
|
||||
我们知道在片段渲染器的内置`gl_FragCoord`向量的 z 值包含那个片段的深度值。如果我们要吧深度值作为颜色输出,那么我们可以在场景中显示的所有片段的深度值。我们可以返回基于片段的深度值的颜色向量:
|
||||
我们知道片段着色器中,内建<var>gl_FragCoord</var>向量的z值包含了那个特定片段的深度值。如果我们将这个深度值输出为颜色,我们可以显示场景中所有片段的深度值。我们可以根据片段的深度值返回一个颜色向量来完成这一工作:
|
||||
|
||||
```c++
|
||||
void main()
|
||||
{
|
||||
color = vec4(vec3(gl_FragCoord.z), 1.0f);
|
||||
}
|
||||
FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
如果再次运行同一程序你可能会发现一切都是白的,它看起来像我们的深度值都是最大值 1.0。那么为什么没有深度值接近 0.0而发暗?
|
||||
如果你再次运行程序的话,你可能会注意到所有东西都是白色的,看起来就想我们所有的深度值都是最大的1.0。所以为什么没有靠近0.0(即变暗)的深度值呢?
|
||||
|
||||
你可能还记得从上一节中的屏幕空间的深度值是非线性如他们在z很小的时候有很高的精度,,较大的 z 值有较低的精度。该片段的深度值会迅速增加,所以几乎所有顶点的深度值接近 1.0。如果我们小心的靠近物体,你最终可能会看到的色彩越来越暗,意味着它们的 z 值越来越小:
|
||||
你可能还记得在上一部分中说到,屏幕空间中的深度值是非线性的,即它在z值很小的时候有很高的精度,而z值很大的时候有较低的精度。片段的深度值会随着距离迅速增加,所以几乎所有的顶点的深度值都是接近于1.0的。如果我们小心地靠近物体,你可能会最终注意到颜色会渐渐变暗,显示它们的z值在逐渐变小:
|
||||
|
||||

|
||||
|
||||
这清楚地表明深度值的非线性特性。近的物体相对远的物体对的深度值比对象较大的影响。只移动几英寸就能让暗色完全变亮。
|
||||
这很清楚地展示了深度值的非线性性质。近处的物体比起远处的物体对深度值有着更大的影响。只需要移动几厘米就能让颜色从暗完全变白。
|
||||
|
||||
但是我们可以让深度值变换回线性。要实现这一目标我们需要让点应用投影变换逆的逆变换,成为单独的深度值的过程。这意味着我们必须首先重新变换范围 [0,1] 中的深度值为单位化的设备坐标(normalized device coordinates)范围内 [-1,1] (裁剪空间(clip space))。然后,我们想要反转非线性方程 (等式2) 就像在投影矩阵做的那样并将此反转方程应用于所得到的深度值。然后,结果是一个线性的深度值。听起来能行对吗?
|
||||
然而,我们也可以让片段非线性的深度值变换为线性的。要实现这个,我们需要仅仅反转深度值的投影变换。这也就意味着我们需要首先将深度值从[0, 1]范围重新变换到[-1, 1]范围的标准化设备坐标(裁剪空间)。接下来我们需要像投影矩阵那样反转这个非线性方程(方程2),并将这个反转的方程应用到最终的深度值上。最终的结果就是一个线性的深度值了。听起来是可行的,对吧?
|
||||
|
||||
首先,我们需要并不太难的 NDC 深度值转换:
|
||||
首先我们将深度值变换为NDC,不是非常困难:
|
||||
|
||||
```c++
|
||||
float z = depth * 2.0 - 1.0;
|
||||
```
|
||||
|
||||
然后把我们所得到的 z 值应用逆转换来检索的线性深度值:
|
||||
接下来使用获取到的z值,应用逆变换来获取线性的深度值:
|
||||
|
||||
```c++
|
||||
float linearDepth = (2.0 * near) / (far + near - z * (far - near));
|
||||
float linearDepth = (2.0 * near * far) / (far + near - z * (far - near));
|
||||
```
|
||||
|
||||
注意此方程不是方程 2 的精确的逆方程。这个方程从投影矩阵中导出,可以从新使用等式2将他转换为非线性深度值。这个方程也会考虑使用[0,1] 而不是 [near,far]范围内的 z 值 。[math-heavy](http://www.songho.ca/opengl/gl_projectionmatrix.html)为感兴趣的读者阐述了大量详细的投影矩阵的知识;它还表明了方程是从哪里来的。
|
||||
这个方程是用投影矩阵推导得出的,它使用了方程2来非线性化深度值,返回一个<var>near</var>与<var>far</var>之间的深度值。这篇注重数学的[文章](http://www.songho.ca/opengl/gl_projectionmatrix.html)为感兴趣的读者详细解释了投影矩阵,它也展示了这些方程是怎么来的。
|
||||
|
||||
这不是从投影矩阵推导出的准确公式;这个方程是除以far的结果。深度值的范围一直到far,这作为一个介于 0.0 和 1.0 之间的颜色值并不合适。除以far的值把深度值映射到介于 0.0 和 1.0,更适合用于演示目的。
|
||||
|
||||
这个能够将屏幕空间的非线性深度值转变为线性深度值的完整的片段着色器如下所示:
|
||||
将屏幕空间中非线性的深度值变换至线性深度值的完整片段着色器如下:
|
||||
|
||||
```c++
|
||||
#version 330 core
|
||||
out vec4 FragColor;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
float LinearizeDepth(float depth)
|
||||
float near = 0.1;
|
||||
float far = 100.0;
|
||||
|
||||
float LinearizeDepth(float depth)
|
||||
{
|
||||
float near = 0.1;
|
||||
float far = 100.0;
|
||||
float z = depth * 2.0 - 1.0; // Back to NDC
|
||||
return (2.0 * near) / (far + near - z * (far - near));
|
||||
float z = depth * 2.0 - 1.0; // back to NDC
|
||||
return (2.0 * near * far) / (far + near - z * (far - near));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
float depth = LinearizeDepth(gl_FragCoord.z) / far; // 为了演示除以far
|
||||
color = vec4(vec3(depth), 1.0f);
|
||||
{
|
||||
float depth = LinearizeDepth(gl_FragCoord.z) / far; // 为了演示除以 far
|
||||
FragColor = vec4(vec3(depth), 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
如果现在运行该应用程序,我们得到在距离实际上线性的深度值。尝试移动现场周围看到深度值线性变化
|
||||
由于线性化的深度值处于<var>near</var>与<var>far</var>之间,它的大部分值都会大于1.0并显示为完全的白色。通过在<fun>main</fun>函数中将线性深度值除以<var>far</var>,我们近似地将线性深度值转化到[0, 1]的范围之间。这样子我们就能逐渐看到一个片段越接近投影平截头体的远平面,它就会变得越亮,更适用于展示目的。
|
||||
|
||||
如果我们现在运行程序,我们就能看见深度值随着距离增大是线性的了。尝试在场景中移动,看看深度值是怎样以线性变化的。
|
||||
|
||||
。
|
||||
|
||||
颜色主要是黑色的因为深度值线性范围从 0.1 的近平面到 100 的远平面,那里离我们很远。其结果是,我们相对靠近近平面,从而得到较低 (较暗) 的深度值。
|
||||
颜色大部分都是黑色,因为深度值的范围是0.1的**近**平面到100的**远**平面,它离我们还是非常远的。结果就是,我们相对靠近近平面,所以会得到更低的(更暗的)深度值。
|
||||
|
||||
## 深度冲突
|
||||
|
||||
两个平面或三角形如此紧密相互平行深度缓冲区不具有足够的精度以至于无法得到哪一个靠前。结果是,这两个形状不断似乎切换顺序导致怪异出问题。这被称为**深度冲突(Z-fighting)**,因为它看上去像形状争夺顶靠前的位置。
|
||||
一个很常见的视觉错误会在两个平面或者三角形非常紧密地平行排列在一起时会发生,深度缓冲没有足够的精度来决定两个形状哪个在前面。结果就是这两个形状不断地在切换前后顺序,这会导致很奇怪的花纹。这个现象叫做<def>深度冲突</def>(Z-fighting),因为它看起来像是这两个形状在争夺(Fight)谁该处于顶端。
|
||||
|
||||
我们到目前为止一直在使用的场景中有几个地方深度冲突很显眼。容器被置于确切高度地板被安置这意味着容器的底平面与地板平面共面。两个平面的深度值是相同的,因此深度测试也没有办法找出哪个是正确。
|
||||
在我们一直使用的场景中,有几个地方的深度冲突还是非常明显的。箱子被放置在地板的同一高度上,这也就意味着箱子的底面和地板是共面的(Coplanar)。这两个面的深度值都是一样的,所以深度测试没有办法决定应该显示哪一个。
|
||||
|
||||
如果您移动摄像机到容器的里面,那么这个影响清晰可,容器的底部不断切换容器的平面和地板的平面:
|
||||
如果你将摄像机移动到其中一个箱子的内部,你就能清楚地看到这个效果的,箱子的底部不断地在箱子底面与地板之间切换,形成一个锯齿的花纹:
|
||||
|
||||

|
||||
|
||||
深度冲突是深度缓冲区的普遍问题,当对象的距离越远一般越强(因为深度缓冲区在z值非常大的时候没有很高的精度)。深度冲突还无法完全避免,但有一般的几个技巧,将有助于减轻或完全防止深度冲突在你的场景中的出现:
|
||||
深度冲突是深度缓冲的一个常见问题,当物体在远处时效果会更明显(因为深度缓冲在z值比较大的时候有着更小的精度)。深度冲突不能够被完全避免,但一般会有一些技巧有助于在你的场景中减轻或者完全避免深度冲突、
|
||||
|
||||
### 防止深度冲突
|
||||
|
||||
第一个也是最重要的技巧是让物体之间不要离得太近,以至于他们的三角形重叠。通过在物体之间制造一点用户无法察觉到的偏移,可以完全解决深度冲突。在容器和平面的条件下,我们可以把容器像+y方向上略微移动。这微小的改变可能完全不被注意但是可以有效地减少或者完全解决深度冲突。然而这需要人工的干预每个物体,并进行彻底地测试,以确保这个场景的物体之间没有深度冲突。
|
||||
第一个也是最重要的技巧是**永远不要把多个物体摆得太靠近,以至于它们的一些三角形会重叠**。通过在两个物体之间设置一个用户无法注意到的偏移值,你可以完全避免这两个物体之间的深度冲突。在箱子和地板的例子中,我们可以将箱子沿着正y轴稍微移动一点。箱子位置的这点微小改变将不太可能被注意到,但它能够完全减少深度冲突的发生。然而,这需要对每个物体都手动调整,并且需要进行彻底的测试来保证场景中没有物体会产生深度冲突。
|
||||
|
||||
另一个技巧是尽可能把近平面设置得远一些。前面我们讨论过越靠近近平面的位置精度越高。所以我们移动近平面远离观察者,我们可以在椎体内很有效的提高精度。然而把近平面移动的太远会导致近处的物体被裁剪掉。所以不断调整测试近平面的值,为你的场景找出最好的近平面的距离。
|
||||
第二个技巧是**尽可能将近平面设置远一些**。在前面我们提到了精度在靠近**近**平面时是非常高的,所以如果我们将**近**平面远离观察者,我们将会对整个平截头体有着更大的精度。然而,将近平面设置太远将会导致近处的物体被裁剪掉,所以这通常需要实验和微调来决定最适合你的场景的**近**平面距离。
|
||||
|
||||
另外一个技巧是放弃一些性能来得到更高的深度值的精度。大多数的深度缓冲区都是24位。但现在显卡支持32位深度值,这让深度缓冲区的精度提高了一大节。所以牺牲一些性能你会得到更精确的深度测试,减少深度冲突。
|
||||
另外一个很好的技巧是牺牲一些性能,**使用更高精度的深度缓冲**。大部分深度缓冲的精度都是24位的,但现在大部分的显卡都支持32位的深度缓冲,这将会极大地提高精度。所以,牺牲掉一些性能,你就能获得更高精度的深度测试,减少深度冲突。
|
||||
|
||||
我们已经讨论过的 3 个技术是最常见和容易实现消除深度冲突的技术。还有一些其他技术需要更多的工作,仍然不会完全消除深度冲突。深度冲突是一个常见的问题,但如果你将列举的技术适当结合你可能不会真的需要处理深度冲突。
|
||||
我们上面讨论的三个技术是最普遍也是很容易实现的抗深度冲突技术了。还有一些更复杂的技术,但它们依然不能完全消除深度冲突。深度冲突是一个常见的问题,但如果你组合使用了上面列举出来的技术,你可能不会再需要处理深度冲突了。
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
20
glossary.md
@@ -269,6 +269,26 @@
|
||||
- Crysis:孤岛危机
|
||||
- Nanosuit:纳米装
|
||||
|
||||
## 04-01
|
||||
|
||||
- Depth Buffer:深度缓冲
|
||||
- Depth Value:深度值
|
||||
- z-buffer:z缓冲
|
||||
- Stencil Testing:模板测试
|
||||
- Early Depth Testing:提前深度测试
|
||||
- Discard:丢弃
|
||||
- Read-only:只读的
|
||||
- Enable:启用
|
||||
- Disable:禁用
|
||||
- Depth Mask:深度遮罩
|
||||
- Depth Function:深度函数
|
||||
- Near(平截头体):近平面
|
||||
- Far(平截头体):远平面
|
||||
- Linear Depth Buffer:线性深度缓冲
|
||||
- Z-fighting:深度冲突
|
||||
- Coplanar:共面
|
||||
- Anti z-fighting:抗深度冲突
|
||||
|
||||
## 06-01
|
||||
|
||||
- Debugging:调试
|
||||
|
BIN
old/img/02/05/attenuation.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
old/img/04/01/depth_linear_graph.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
old/img/04/01/depth_non_linear_graph.png
Normal file
After Width: | Height: | Size: 30 KiB |
@@ -15,6 +15,7 @@
|
||||
"""
|
||||
|
||||
from PIL import Image
|
||||
from os.path import splitext
|
||||
|
||||
file_name = input("Filename: ")
|
||||
r, g, b, a = map(int, input("Color(R G B A): ").split())
|
||||
@@ -32,4 +33,4 @@ for x in range(0, img.size[0]):
|
||||
if pixel[x, y] == (r, g, b, 255):
|
||||
img.putpixel((x, y), (0, 0, 0, 0))
|
||||
|
||||
img.save('noBG.png')
|
||||
img.save(splitext(file_name)[0] + '_noBG.png')
|
||||
|