learnOpenGL 4.2 模板测试

169 阅读3分钟

除了深度测试,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);

注意:

  • 边框箱子在绘制时应该先移动后放大,如果次序不对可能导致位置错误
  • 最后要开启深度测试,开启模板写入并通过所有模板测试
    ,否则会影响画面中其他部分的绘制