LearnOpenGL-深度测试与模板测试
深度测试 Depth test
深度缓冲像颜色缓冲一样,在每个片段中存储了信息,并且有一样的宽度高度,都是由窗口自动创建的,在大部分系统中,深度缓冲的精度都是24位。
深度缓冲是在片元着色器之后(以及模板测试之后),在屏幕空间中执行的。屏幕空间可以直接由GLSL内建 变量gl_FragCoord 获得,x和y分量代表了片段的屏幕空间坐标,z分量就是深度测试要比较的值。
- Early-Z 提前深度测试允许深度测试在片段着色器之前运行,就对于那些完全肯定看不见的片元不再渲染出来后再剔除,节省性能。
- 不能用情况:1.alpha test、2.alpha blend、3.关闭深度测试、4.开启多重采样、5.其他任何导致需要颜色混合的操作
深度测试默认是禁用的,需要用glEnable(GL_DEPTH_TEST);
启用。当它启用的时候,如果一个片段通过了深度测试的话,OpenGL会在深度缓冲中储存该片段的z值;如果没有通过深度缓冲,则会丢弃该片段。
如果启用了深度缓冲,还应该在每个渲染迭代之前使用GL_DEPTH_BUFFER_BIT glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
来清除深度缓冲,否则会仍在使用上一次渲染迭代中的写入的深度值:
在某些情况下(透明度混合)你会需要对所有片段都执行深度测试并丢弃相应的片段,但不希望更新深度缓冲 —— 只读的深度缓冲(禁用深度写入),需要设置它的深度掩码(Depth Mask)设置为GL_FALSE就可以了glDepthMask(GL_FALSE);
一、深度测试函数
OpenGL允许我们修改深度测试中使用的比较运算符,即什么情况下可以通过深度测试,调用 glDepthFunc函数 来设置比较运算符glDepthFunc(GL_LESS);
(深度值大的被丢弃)
二、深度值精度
深度缓冲包含了一个介于0.0和1.0之间的深度值,观察空间的z值可能是投影平截头体的近平面(Near)和远平面(Far)之间的任何值。我们需要一种方式来将这些观察空间的z值变换到[0, 1]范围之间
- 其中的一种方式就是将它们线性变换到[0, 1]范围之间,在实践中是几乎永远不会使用这样的线性深度缓冲(Linear Depth Buffer)的
- 需要使用一个非线性的深度方程,它是与 1/z 成正比的,在z非常小的时候提供非常高的精度,z很远时提供更少的精度。这个(从观察者的视角)变换z值的方程是嵌入在投影矩阵中的,在从观察空间变换到裁剪空间时,这个方程就被应用了。
三、深度缓冲可视化
因为内建gl_FragCoord向量的z值包含了那个特定片段的深度值,我们可以直接将其作为颜色输出
void main()
{
FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
}
因为是非线性的,只有近处物体的精度较高,所以变暗的程度不大,远处物体精度很低,稍微移动一点可能就变的特别白
四、深度冲突
当两个平面或者三角形非常紧密地平行排列在一起时会发生,因为深度缓冲没有足够的精度来判断哪个在前面
防止深度冲突
- 1.不要把多个物体摆得特别近或者重叠
- 2.尽可能将近平面设置远一些,然而,将近平面设置太远将会导致近处的物体被裁剪掉,所以这通常需要实验和微调来决定最适合你的场景的近平面距离。
- 3.牺牲性能,使用更高精度的深度缓冲
模板测试 Stencil test
在片元处理器执行结束后就会进行模板检测,和深度测试一样,也会根据一定标准来剔除片段。是根据 模板缓冲 来进行的。
在模板缓冲中,每个模板值是8位的,所以每个像素/片元一共能有256中不同的模板值。模板缓冲操作允许我们在渲染片段时将模板缓冲设定为一个特定的值。通过在渲染时修改模板缓冲的内容,我们写入了模板缓冲。在同一个(或者接下来的)渲染迭代中,我们可以读取这些值,来决定丢弃还是保留某个片段。
- 启用模板缓冲写入
- 渲染物体,更新模板缓冲内容
- 禁用模板缓冲的写入
- 渲染其他物体,这次根据模板缓冲的内容丢弃特定的片段。
启用GL_STENCIL_TEST来启用模板测试glEnable(GL_STENCIL_TEST);
,在这一行代码之后,所有的渲染调用都会以某种方式影响着模板缓冲。
需要在每次迭代之前清除模板缓冲glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glStencilMask
允许我们设置一个位掩码(Bitmask),它会与将要写入缓冲的模板值进行与(AND)运算。默认情况下设置的位掩码0xFF所有位都为1,不影响输出,但如果我们将它设置为0x00,写入缓冲的所有模板值最后都会变成0.这与深度测试中的glDepthMask(GL_FALSE)是等价的。
glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
一、模板函数
模板缓冲应该通过还是失败,以及它应该如何影响模板缓冲,也是有一定控制的。一共有两个函数能够用来配置模板测试:glStencilFunc和glStencilOp
- glStencilFunc(GLenum func, GLint ref, GLuint mask):描述了OpenGL应该对模板缓冲内容做什么
- func:设置模板测试函数,GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS(与深度测试差不多)
- ref:设置了模板测试的参考值(Reference Value)。模板缓冲的内容将会与这个值进行比较。
- mask:设置一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。
glStencilFunc(GL_EQUAL, 1, 0xFF) 只要一个片段的模板值等于(GL_EQUAL)参考值1, 片段将会通过测试并被绘制,否则会被丢弃。
- glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass):应该如何更新缓冲
- sfail:模板测试失败时采取的行为
- dpfail:模板测试通过,但深度测试失败的行为
- dppass:模板测试和深度测试都通过时采取的行为
- 每一个选项都可以选用以下行为,默认是设置为(GL_KEEP, GL_KEEP, GL_KEEP)的,不会更新模板缓冲。
二、物体轮廓
为物体创建轮廓的步骤如下:
- 1.在绘制物体之前,将模板函数设置为GL_ALWAYS,每当物体渲染时,模板缓冲更新为1
- 2.渲染物体
- 3.禁用模板写入以及深度测试
- 4.将每个物体缩放一点点
- 5.使用不同的片元着色器,输出一个单独的边框颜色
- 6.再次绘制物体时,只在它们片元的模板值不等于1时才绘制
- 7.再次启用模板写入和深度测试
先为边框颜色创建一个片元着色器 shaderSingleColor
void main()
{
FragColor = vec4(0.04, 0.28, 0.26, 1.0);
}
在渲染开始前,启用模板测试和深度测试,并清空缓冲
glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
先绘制地板,不更新模板缓冲
glStencilMask(0x00); // 记得保证我们在绘制地板的时候不会更新模板缓冲
normalShader.use();
DrawFloor()
再绘制两个箱子,启用模板缓冲,将箱子绘制的地方的模板缓冲值设置为1
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
DrawTwoContainers();
再绘制放大版的箱子(即轮廓),此时关闭模板缓冲写入和深度测试,在模板缓冲不为1的地方绘制(即箱子外部)
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);
shaderSingleColor.use();
DrawTwoScaledUpContainers();
再开启模板缓冲写入和深度缓冲
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);