目录
在这部分中主要涉及了
- 深度测试:
- 鼠标拾取
- 模型控制
- 模板测试
- 混合
- 面剔除
- 帧缓冲
- 立方体贴图
- 高级数据
- 高级GLSL
- 几何着色器
本章源码: 源码
一 深度测试
1.1 深度缓冲
在了解openGL的深度测试之前我们需要先了解一下深度缓冲
- 深度缓冲是由窗口系统自动创建的,他会以float的形式储存它的深度值。
- 是一种用于确定哪些片段将被绘制到屏幕上的技术。深度缓冲(Depth Buffer)是一种用于存储每个像素的深度信息的缓冲区,其中深度值通常被映射到0到1之间。
- 值得注意的是他并不是简简单单的Z轴大小,而是存储在每个像素处的深度值,这些值表示了从相机(或观察者)到特定片段(屏幕像素点之间)的距离。深度缓冲的值与场景中物体在Z轴上的距离是相关的,但并不仅仅是Z轴的大小。在典型的情况下,深度缓冲的值被映射到一个在0到1之间的范围内,其中0代表最近处,1代表最远处。相当于之前坐标系统中的near和far。
理解: 我们可以这样理解,再打开的一个窗口中,被分成了很多个小格子,每个小格子在写入数据的时候都会记录下它的z缓冲值,当下一个位于同一个像素点(小格子)处要被绘制时,如果你开启了深度测试,那么将通过深度测试的值保留,未通过的值丢弃。当然深度测试的比较方法取决于你选择的深度测试方式.
- 默认情况下深度测试是禁用的:GL_DEPTH_TEST选项来启用它:
glEnable(GL_DEPTH_TEST);
- OpenGL会在深度缓冲中储存该片段的z值;如果没有通过深度缓冲,则会丢弃该片段。如果启用了深度测试,必须在每次重新绘制时使用GL_DEPTH_BUFFER_BIT来清除深度缓冲清理缓冲区,否则上一次的深度缓冲就会影响这一次的绘制。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
openGL是可以设置深度缓冲为只读属性的,也就是禁用深度缓冲的写入,但要值得注意的是,他不是完全意义的只读,而是在本次绘制过程中,只要有值被写入,那么这个片段的深度缓冲就会被设置成只读属性
glDepthMask(GL_FALSE);
1.2 深度测试函数
- 深度测试函数是告诉openGL什么时候应该去执行哪种规则的深度测试
函数 | 描述 |
---|---|
GL_ALWAYS | 永远通过深度测试 |
GL_NEVER | 永远不通过深度测试 |
GL_LESS | 在片段深度值小于缓冲的深度值时通过测试 |
GL_EQUAL | 在片段深度值等于缓冲区的深度值时通过测试 |
GL_LEQUAL | 在片段深度值小于等于缓冲区的深度值时通过测试 |
GL_GREATER | 在片段深度值大于缓冲区的深度值时通过测试 |
GL_NOTEQUAL | 在片段深度值不等于缓冲区的深度值时通过测试 |
GL_GEQUAL | 在片段深度值大于等于缓冲区的深度值时通过测试 |
运行时间: 深度缓冲是在片段着色器运行之后(以及模板测试运行之后)在屏幕空间中运行的。
但是这样就导致很多要被废弃的片段白白绘制了一次,所有现在大部分GPU都会提供一个深度提前测试的硬件特性。
1.3 测试Demo
我们首先在paintGL方法中,设置深度测试为 GL_ALWAYS ,也就是无论什么时候都会通过测试,也就是后来的片段深度永远会通过深度测试(覆盖前面片段深度值)
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);
// 当然不要忘记清空缓冲
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- 在本章的源码中我们先绘制了一个箱子,然后绘制了一个地板(其实也就是一个面),但由于深度测试是glDepthFunc(GL_ALWAYS);那么后面绘制的地板在相同的片段出按理来说就会覆盖箱子的片段。
- 然后我们将深度缓冲设置回去为默认的glDepthFunc(GL_LESS);
- 这时候我们就能看到正常的箱子了。
二 深度缓冲
- 深度缓冲是一个介于0.0-1.0之间的深度值,我们需要将近观察面(near) 和远观察面(Far)中观察到的所有物体的z值,变换到[0-1]范围之间,所以我们就需要一个公式来进行转化
2.1 线性深度缓冲
- 这个公式需要一个Z值来计算他的深度值,比如当我们设置near为1,far为50时对应的关系就应该如下图所示:
- 但这有个什么问题呢?其实我们更关心的离视口最近的片段,所以对于线性的计算来说很明显不否和我们实际的需求。
- 因为在实践中我们几乎不会使用线性的深度缓冲,而是应该使用一个非线性的深度方程
2.1非线性深度缓冲
- 非线性深度缓冲是于1/z成正比的,他会在z值很小的时候提供非常高的精度,而在Z值非常大的时候提供很小的精度
- 要值得注意的是,提供很大的精度的目的是,方式深度冲突,如果精度太小,可能会导致深度冲突
- 试想一下如果使用线性的方程,我们真的需要将1000单位远的深度值和1单位远的深度值设置的精度一样大吗?
- 这里的z值是我们观察空间中的near和far的值
- 重要的是要记住这种方式的深度缓冲中的值在屏幕空间中不是线性的
- 深度缓冲中0.5的值并不代表着物体的z值是位于平截头体的中间了,这个顶点的z值实际上非常接近近平面!你可以在下图中看到z值和最终的深度缓冲值之间的非线性关系:
深度值很大一部分是由很小的z值所决定的,这给了近处的物体很大的深度精度。这个(从观察者的视角)变换z值的方程是嵌入在投影矩阵中的,所以当我们想将一个顶点坐标从观察空间至裁剪空间的时候这个非线性方程就被应用了
三 深度冲突(Z-fighting)
3.1 原因
在绘制时如果深度缓冲没有足够的精度来决定到底应该绘制哪个平面在前面,他就会不断的在它无法确定精度的片段处,不断的切换前后顺序,导致很奇怪的形状,这也就是我们打游戏时,任务角色突然卡住,屏幕一直不断的切换几种不同的形状的原因。
深度冲突是深度缓冲的一个常见问题,当物体在远处时效果会更明显,因为非线性的深度缓冲在更远处的精度会更小)。深度冲突不能够被完全避免,但一般会有一些技巧有助于在你的场景中减轻或者完全避免深度冲突、
3.2 如何避免
对于深度冲突,几乎不可能彻底避免的,但如果灵活使用这三种方法,基本上是可以避免大多数情况的。
- 永远不要把多个物体摆得太靠近: 即使是一个箱子摆在桌子上,但是把他们的y轴设置的相差个0.01,几乎是不会被注意到的。
- 尽可能将近平面设置远一些: 这会让近远平面离得更近,所以精度值更大,但如果设置的太原,一些物体就会被裁剪出去,这是要值得注意的
- 使用更高精度的深度缓冲:比如使用32位的深度缓冲,这会极大的提高精度,但会牺牲性能,并且你的GPU要能够支持。
四 深度缓冲的可视化
4.1 非线性可视化
在GLSL语言中,内置的gl_FragCoord向量的z值会包含特定片段的深度值。如果将这个深度值设置为颜色,那么就可以显示出场景中所有片段的深度值。这样我们当我们按住W将摄像机一直往前移,就会看到越靠近物体,就会变得越黑,因为黑色的颜色是接近于0的,而越远处就是白色的.
- 当然我们也可以将深度值变为线性的,因为gl_FragCoord的z值保存的是非线性的。
4.2 线性可视化
要实现线性可视化,我们仅仅只需要反转深度值的投影变换。
- 首先我们应该讲深度值从[0,1]变为NDC(标准设备坐标)中的[-1,1]
float z = depth * 2.0 - 1.0;
- 接下来我们需要将获取到的z值(也就是非线性的深度缓冲),使用逆变来获取线性的深度值
float linearDepth = (2.0 * near * far) / (far + near - z * (far - near));
- 这个公式是使用用投影矩阵推导得出的,跟我们的非线性深度缓冲的公式并没有什么关系。它返回的是一个near与far之间的深度值
- 当然我们其实也不需要这么麻烦,使用之前的线性深度方程公式也是能获取到这个值的。
#version 330 core
float near = 0.1;
float far = 100.0;
float API_linearDepth(float depth)
{
float z = depth * 2.0 -1.0;
return (2.0 * near * far)/(far + near - z * (far - near));
}
out vec4 FragColor;
void main(){
// 计算出线性深度值来输出
float depth = API_linearDepth(gl_FragCoord.z) / (far - near);
FragColor = vec4(vec3(depth),1.0);
}
- 这里要注意的是这个计算结果,实际是从near-far(0.1-100)之间的值,所有要先给它除以far-near,将深度值转换到0-1之间
- 这样我们仔细观看,下面两张图,就会发现越近的地方就会越黑,越远的地方就会越亮,但是他的变换是很缓慢的。
五 深度提前测试:
-
提前深度测试允许深度测试在片段着色器之前运行。只要我们清楚一个片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段。
-
片段着色器通常开销都是很大的,所以我们应该尽可能避免运行它们。当使用提前深度测试时,片段着色器的一个限制是你不能写入片段的深度值。如果一个片段着色器对它的深度值进行了写入,提前深度测试是不可能的。OpenGL不能提前知道深度值。
-
上面的大概意思也就是说,如果你已经再片段着色器中对进行了操作,那么提前深度测试就已经没有必要了,当前目前我们也不会研究这个。
简单来说,就是在片段着色器运行之前,进行提前深度测试,如果通过了深度测试就会丢弃这个片段而不会进入片段着色器中进行计算了,当然也就不会绘制了,当然我们所学习的深度测试目前都是在片段着色器之后处理的。