除了深度测试,OpenGL还有一种较为自由的测试,称为模板测试stencil。
模板函数
首先,和深度测试一样,我们要先启用它,同时需要在渲染循环中清除它的缓存
glEnable(GL_STENCIL_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
除此以外,我们还可以使用以下函数开启和禁用模板值的写入
glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
深度测试默认取深度浅的,因为这符合我们的常识。模板测试中,哪些值能通过,必须通过函数设定好。
举例如下,第一个参数是测试模式,可用的选项有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它们的语义和深度缓冲的函数类似。
第二个参数是参考值。用来进行对比。
第三个参数是一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。初始情况下所有位都为1。
glStencilFunc(GL_EQUAL, 1, 0xFF)
以上这个函数的意义就是告诉OpenGL,只要一个片段的模板值等于(GL_EQUAL)参考值1,片段将会通过测试并被绘制,否则会被丢弃。
除了怎么测试,我们还要设置如何更改模板的值
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 按位翻转当前的模板缓冲值
简单应用
我们可以用它来做一个物体边框的效果
具体步骤如下:
- 开启深度测试
- 渲染地面
- 开启模板测试,启用写入,将之后绘制的部分模板值设为一
- 渲染两个放在地板上的箱子
- 关闭深度测试
- 禁用模板值写入,将模板测试设为只通过模板值不等于1的部分
- 在相同的地点绘制两个略放大的纯色的箱子
- 开启深度测试,开启模板写入并通过所有模板测试
下面是主体代码
shader.use();
glm::mat4 model(1.0f);
shader.setMat4("model", model);
glm::mat4 view = camera.GetViewMatrix();
shader.setMat4("view", view);
glm::mat4 projection = glm::perspective(glm::radians(45.0f), 1920.0f / 1080.0f, 0.1f, 100.0f);
shader.setMat4("projection", projection);
glStencilMask(0x00);
glBindVertexArray(planeVAO);
glBindTexture(GL_TEXTURE_2D, planetexture);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);//地面绘制
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xff);
glBindVertexArray(cubeVAO);
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, 1.0f));
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);//箱子绘制
frameshader.use();
frameshader.setMat4("view", view);
frameshader.setMat4("projection", projection);
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);
glBindVertexArray(cubeVAO);
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
model = glm::scale(model, glm::vec3(1.05f, 1.05f, 1.05f));
frameshader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 1.0f));
model = glm::scale(model, glm::vec3(1.05f, 1.05f, 1.05f));
frameshader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 0, 0xFF);
glEnable(GL_DEPTH_TEST);
注意:
- 边框箱子在绘制时应该先移动后放大,如果次序不对可能导致位置错误
- 最后要开启深度测试,开启模板写入并通过所有模板测试
,否则会影响画面中其他部分的绘制