深度测试
深度缓冲用来防止被阻挡的面渲染到其他面的前面,深度缓冲就像颜色缓冲,在每个片段中储存了信息,
当深度测试(Depth Testing)被启用的时候,OpenGL会将一个片段的深度值与深度缓冲的内容进行对比。OpenGL会执行一个深度测试,如果这个测试通过了的话,深度缓冲将会更新为新的深度值。如果深度测试失败了,片段将会被丢弃。
深度缓冲运行在片段着色器之后(以及模板测试之后)
gl_TexCoord 是GLSL内建变量,它是一个vec3,x和y分量代表了片段的屏幕坐标(左下角为原点),z分量为片段的深度值。
提前深度测试(Early Depth Testing, Early-Z)允许深度测试在Fragment着色器之前运行。只要判断该片段在其他物体之后,便会将他提前剔除。
使用Early-Z的条件是,Fragment Shader里不能有写入深度值的操作。
深度测试一般是禁用的,需要开启
gl_Enable(GL_DEPTH_TEST)
在渲染迭代之前清除深度缓冲。否则会使用上一次渲染迭代中的写入的深度值
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
禁用深度缓冲的写入,但是可以进行深度测试:
glDepthMask(GL_FALSE);
深度测试函数
OpenGL允许我们修改深度测试中使用的比较运算符。这允许我们来控制OpenGL什么时候该通过或丢弃一个片段
glDepthFunc(GL_LESS); //默认情况
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);
想象一个地板上放着一个方块,如果我们先绘制方块之后绘制地板,那么地板就会覆盖方块
重新设置为 GL_LESS 之后
深度值精度
深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。观察空间的z值可能是投影平截头体的近平面(Near)和远平面(Far)之间的任何值。我们需要一种方式来将这些观察空间的z值变换到[0, 1]范围之间
这种线性深度缓冲实践中不会使用,一般会使用下面这种,更符合现实
深度值很大一部分是由很小的z值所决定的,这给了近处的物体很大的深度精度
非线性深度值转换方程被嵌入到了投影矩阵中,在观察空间->裁剪空间的转换过程中被应用。
这意味着,我们使用gl_FragCoord.z得到的值就是非线性深度值。
可以将非线性的深度值转换为线性的,它使用投影矩阵推到得到的,其中 z 是 NDC 坐标下的 z 值
float z = depth * 2.0 - 1.0;
float linearDepth = (2.0 * near * far) / (far + near - z * (far - near));
深度冲突
两个平面或者三角形非常紧密的排列在一起时,由于深度缓冲没有足够的精度来决定两个形状哪个在前面,就会发生 深度冲突,看起来像两个形状在争夺谁在前面
深度冲突是深度缓冲的一个常见问题,当物体在远处时效果会更明显(因为深度缓冲在z值比较大的时候有着更小的精度)。深度冲突不能够被完全避免
防止深度冲突
- 最重要的技巧是永远不要把多个物体摆得太靠近,以至于它们的一些三角形会重叠。
- 尽可能将近平面设置远一些。在前面我们提到了精度在靠近近平面时是非常高的,所以如果我们将近平面远离观察者,我们将会对整个平截头体有着更大的精度。
- 牺牲一些性能,使用更高精度的深度缓冲。
模板测试
当片段着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行,和深度测试一样,它也可能会丢弃片段
模板测试使用模板缓冲,模板缓冲类似于一个遮罩。一个模板缓冲中,(通常)每个模板值(Stencil Value)是8位的。所以每个像素/片段一共能有256种不同的模板值。我们可以将这些模板值设置为我们想要的值,然后当某一个片段有某一个模板值的时候,我们就可以选择丢弃或是保留这个片段了。
模板缓冲操作允许我们在渲染片段时将模板缓冲设定为一个特定的值。通过在渲染时修改模板缓冲的内容,我们写入了模板缓冲。在同一个(或者接下来的)渲染迭代中,我们可以读取这些值,来决定丢弃还是保留某个片段。
- 启用模板缓冲的写入。
- 渲染物体,更新模板缓冲的内容。
- 禁用模板缓冲的写入。
- 渲染(其它)物体,这次根据模板缓冲的内容丢弃特定的片段。
所以,通过使用模板缓冲,我们可以根据场景中已绘制的其它物体的片段,来决定是否丢弃特定的片段。
glEnable(GL_STENCIL_TEST); // 开始模板测试
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); //清除模板缓存
glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
glStencilMask 允许我们设置一个位掩码,它会将要写入缓冲的模板值进行与运算,默认情况下位掩码的所有位是1,不影响写入
模板函数
glStencilFunc(GLenum func,Glint ref,Gluint mask) // 描述了OpenGL应该对模板缓冲内容做什么
- func 设置模板测试函数(Stencil Test Function)。这个测试函数将会应用到已储存的模板值上和glStencilFunc 函数的 ref 值上。可用的选项有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它们的语义和深度缓冲的函数类似。
- ref: 设置了模板测试的参考值(Reference Value)。模板缓冲的内容将会与这个值进行比较。
- mask: 设置一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。初始情况下所有位都为1。
glStencilOp(Glenum sfail,Glenum dpfail,GLenum dppass) //我们应该如何更新缓冲
- sfail: 模板测试失败时采取的行为。
- dpfail: 模板测试通过,但深度测试失败时采取的行为。
- dppass: 模板测试和深度测试都通过时采取的行为。
每个选项都可以选用以下的其中一种行为:
默认情况下 glStencilOp 是设置为 (GL_KEEP,GL_KEEP,GL_KEEP) 的,所以不论任何测试的结果如何,模板缓冲都会保留它的值,默认的行为不会更新模板缓冲。
描边
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() glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
DrawTwoContainers();glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);
shaderSingleColor.use();
DrawTwoScaledUpContainers();
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);
为物体创建轮廓的步骤如下:
- 在绘制(需要添加轮廓的)物体之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1。
- 渲染物体。
- 禁用模板写入以及深度测试。
- 将每个物体缩放一点点。
- 使用一个不同的片段着色器,输出一个单独的(边框)颜色。
- 再次绘制物体,但只在它们片段的模板值不等于1时才绘制。
- 再次启用模板写入和深度测试。
参考:深度测试 - LearnOpenGL CN