一,前言
这是对官网的一部分补充,看之前需要看一下官网的文章。
了解一下小小的知识点。
1,模板测试和深度测试的顺序
片段着色器------->模板测试-------->深度测试
2,什么叫模板缓冲?
就是记录了每一个像素点的模板值。(不理解也没关系,看看后面怎么用的就了解了)
如果熟悉RGBA的Alpha通道,可能会将模板缓冲理解为Alpha通道,两者很相似,但是模板缓冲比Alpha通道多了一个比较的环节,也就是glStencilFunc函数。
二,使用方法
直接上结果:
可以看到笑脸箱子的周围包了一圈红色的框,这个框就是使用模板测试实现的。
我们直接从代码着手,通过分析代码的作用来解释模板测试的相关函数的作用。
1,启动模板测试
既然要使用模板测试,那么就需要开启模板测试(深度测试是上一节的内容),直接上代码:
/*启用深度测试*/
glEnable(GL_DEPTH_TEST);
//在片段深度值小于缓冲的深度值时通过测试
glDepthFunc(GL_LESS);
//启用GL_STENCIL_TEST来启用模板测试
glEnable(GL_STENCIL_TEST);
2,设置模板测试的参数
//( ref & mask ) != ( stencil & mask )则通过。
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
函数原型是:
glStencilFunc(GLenum func, GLint ref, GLuint mask);
其中这三个参数表示:
func
:设置模板测试函数(Stencil Test Function)。这个测试函数将会应用到已储存的模板值上和glStencilFunc函数的ref
值上。
ref
:设置了模板测试的参考值(Reference Value)。模板缓冲的内容将会与这个值进行比较。
mask
:设置一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。初始情况下所有位都为1。
下面就是func的值和使用方法:
func接受以下值:
GL_NEVER
总是失败。
GL_LESS
( ref & mask ) < ( stencil & mask ) 则通过。
GL_LEQUAL
( ref & mask ) <= ( stencil & mask )则通过。
GL_GREATER
( ref & mask ) > ( stencil & mask )则通过。
GL_GEQUAL
( ref & mask ) >= ( stencil & mask )则通过。
GL_EQUAL
( ref & mask ) = ( stencil & mask )则通过。
GL_NOTEQUAL
( ref & mask ) != ( stencil & mask )则通过。
GL_ALWAYS
总是通过。
但是glStencilFunc仅仅描述了OpenGL应该对模板缓冲内容做什么,而不是我们应该如何更新缓冲。这就需要glStencilOp这个函数了。
/*
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)一共包含三个选项,我们能够设定每个选项应该采取的行为:
sfail:模板测试失败时采取的行为。
dpfail:模板测试通过,但深度测试失败时采取的行为。
dppass:模板测试和深度测试都通过时采取的行为。
*/
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)一共包含三个选项,我们能够设定每个选项应该采取的行为:
sfail
:模板测试失败时采取的行为。dpfail
:模板测试通过,但深度测试失败时采取的行为。dppass
:模板测试和深度测试都通过时采取的行为。
每个选项都可以选用以下的其中一种行为:
行为 | 描述 |
---|---|
GL_KEEP | 保持当前储存的模板值 |
GL_ZERO | 将模板值设置为0 |
GL_REPLACE | 将模板值设置为glStencilFunc函数设置的ref 值 |
GL_INCR | 如果模板值小于最大值则将模板值加1 |
GL_INCR_WRAP | 与GL_INCR一样,但如果模板值超过了最大值则归零 |
GL_DECR | 如果模板值大于最小值则将模板值减1 |
GL_DECR_WRAP | 与GL_DECR一样,但如果模板值小于0则将其设置为最大值 |
GL_INVERT | 按位翻转当前的模板缓冲值 |
在模板测试和深度测试都通过时设置的参数为GL_REPLACE,也就是说,我们可以通过设置glStencilFunc函数的ref值来设置模板缓冲的值,也就是把下图中的1设置成别的参数。
3,绘制
回顾一下之前的结果图,我们是希望绘制箱子和箱子外轮廓,那么如何绘制箱子的外轮廓呢?
我们可以将外轮廓想象一个更大的箱子,所以我们绘制一个比箱子a更大的箱子A,但是,在绘制了箱子A之后,由于深度测试,箱子A会覆盖箱子a,那么我们就希望,在绘制箱子A的时候,不要绘制箱子a存在的片段。
(如果在绘制的时候不开启深度测试,这样只需要先绘制箱子A,在绘制箱子a就行,但是这样有一个问题就是,当箱子一部分埋在地板中的时候,由于没有深度测试,箱子整个会被绘制,显示在窗口上)
模板测试就可以做到这一点,流程如下:
- 运行一下函数: glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
- 在绘制(需要添加轮廓的)物体(箱子a)之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1。
- 渲染物体。
- 禁用模板写入以及深度测试。
- 将每个物体缩放一点点(箱子A)。
- 使用一个不同的片段着色器,输出一个单独的(边框)颜色。
- 再次绘制物体,但只在它们片段的模板值不等于1时才绘制。
- 再次启用模板写入和深度测试。
我们一步一步来解释:
第一步的作用是在模板测试失败时采取 和 模板测试通过,但深度测试失败时采取时保持模板缓冲内部的值不变。模板测试和深度测试都通过时更新模板缓冲的对应模板值为1(glStencilFunc由这个函数设置模板值为多少)。
第二步就是将绘制箱子a那部分模板值设置为1.因为箱子a可以通过模板测试和深度测试。
代码如下:
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
// cubes
glBindVertexArray(VAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, cubeTexture);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
第四,五,六,七步的作用:禁止写入模板测试,也就是说在绘制箱子A的时候,模板值不变,而在绘制箱子A和箱子a相互重叠的部分的时候,箱子A会进行模板测试,在模板缓冲为1的时候,箱子A无法通过模板测试,不进行绘制。
代码如下:
glStencilFunc(GL_NOTEQUAL, 2, 0xFF);
glStencilMask(0x00);
//glDisable(GL_DEPTH_TEST);
shaderSingleColor.use();
float scale = 1.1f;
// cubes
glBindVertexArray(VAO);
glBindTexture(GL_TEXTURE_2D, cubeTexture);
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
model = glm::scale(model, glm::vec3(scale, scale, scale));
shaderSingleColor.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
model = glm::scale(model, glm::vec3(scale, scale, scale));
shaderSingleColor.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 0, 0xFF);
glEnable(GL_DEPTH_TEST);
那么有个问题,如果在绘制箱子A的时候,不关闭深度测试会怎么样?结果如下:
放大的箱子A,即边框,会被地板所覆盖。
4,完整代码:
Code Viewer. Source code: src/4.advanced_opengl/2.stencil_testing/stencil_testing.cpp