第十四课,模板测试

当片段着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行,会丢弃片段,被保留的片段会进入深度测试。
即,模板测试是在深度测试之前对片段(三角形各个点)的丢弃操作。

模板缓冲(Stencil Buffer)

模板测试是根据模板缓冲来进行的,(通常)每个模板值(Stencil Value)是8位的。
所以每个像素/片段一共能有256种不同的模板值。
在这里插入图片描述

模板缓冲首先会被清除为0,之后在模板缓冲中使用1填充了一个空心矩形。场景中的片段将会只在片段的模板值为1的时候会被渲染(其它的都被丢弃了)。

使用模板缓冲的时候你可以尽情发挥,但大体的步骤如下:

  1. 启用模板缓冲的写入。
  2. 渲染物体,更新模板缓冲的内容。
  3. 禁用模板缓冲的写入。
  4. 渲染(其它)物体,这次根据模板缓冲的内容丢弃特定的片段。

模板测试

启动模板测试

glEnable(GL_STENCIL_TEST);

每次迭代之前清除模板缓冲。

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

设置一个位掩码(Bitmask)

glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)

一共有两个函数能够用来配置模板测试:glStencilFunc和glStencilOp。

glStencilFunc(仅仅描述了OpenGL应该对模板缓冲内容做什么)

glStencilFunc(GLenum func, GLint ref, GLuint mask)一共包含三个参数:

  • func:设置模板测试函数(Stencil TestFunction)。这个测试函数将会应用到已储存的模板值上和glStencilFunc函数的ref值上。
    可用的选项有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它们的语义和深度缓冲的函数类似。
  • ref:设置了模板测试的参考值(Reference Value)。模板缓冲的内容将会与这个值进行比较。
  • mask:设置一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。初始情况下所有位都为1。

使用一个简单例子:

glStencilFunc(GL_EQUAL, 1, 0xFF)

表示,将该位置的模板值与0xFF做与运算和10xFF做与运算的值进行比较,比较方式为GL_EQUAL。
即若模板值等于1时绘制,否则便丢弃。

glStencilOp

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 按位翻转当前的模板缓冲值

默认有glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)。

到现在我们还不知道这可以做什么,下面我们看看模板测试使用的简单样例。

物体轮廓

为物体创建轮廓的步骤如下:

  1. 在绘制(需要添加轮廓的)物体之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1。
  2. 渲染物体。
  3. 禁用模板写入以及深度测试。
  4. 将每个物体缩放一点点。
  5. 使用一个不同的片段着色器,输出一个单独的(边框)颜色。
  6. 再次绘制物体,但只在它们片段的模板值不等于1时才绘制。
  7. 再次启用模板写入和深度测试。

完整代码步骤:

场景中物体轮廓的完整步骤会看起来像这样:

glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);  //当模板测试和深度测试都通过时,用ref值替代模板值

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);//模板值不为1时,与掩码做与运算
glStencilMask(0x00); //关闭模板测试(不读入数据)
glDisable(GL_DEPTH_TEST);//关闭深度测试
shaderSingleColor.use(); 
DrawTwoScaledUpContainers();//绘制两个边框
glStencilMask(0xFF);//开启模板测试
glEnable(GL_DEPTH_TEST);  //开启深度测试

好吧,还是没看懂,我是懵了。
看看源代码:
https://learnopengl.com/code_viewer_gh.php?code=src/4.advanced_opengl/2.stencil_testing/stencil_testing.cpp

源码分析

在主函数内先设置

glEnable(GL_DEPTH_TEST);//开启深度测试
glDepthFunc(GL_LESS);//这句是废话,默认值就是这个。
glEnable(GL_STENCIL_TEST);//开启模板测试
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

shader

    Shader shader("2.stencil_testing.vs", "2.stencil_testing.fs");//图形绘制
    Shader shaderSingleColor("2.stencil_testing.vs", "2.stencil_single_color.fs");//边框绘制

在循环while中:

  1. 更新缓存
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 
  1. 禁用模板
glStencilMask(0x00);
  1. glDrawArrays(GL_TRIANGLES, 0, 6);//绘制地板
  2. 在绘制两个正方形的时候,将正方形能改变的模板值全部设为1.
    shader.use();
    glStencilFunc(GL_ALWAYS, 1, 0xFF);
    glStencilMask(0xFF);
    glDrawArrays(GL_TRIANGLES, 0, 36);
  3. 当模板值不为1时通过,禁用模板写入
    glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
    glStencilMask(0x00);
    glDisable(GL_DEPTH_TEST);
  4. 使用边框渲染器,放大正方形大小,绘制(将在模板值通过时绘制)。
    shaderSingleColor.use();
    float scale = 1.1;
    model = glm::scale(model, glm::vec3(scale, scale, scale));
    glDrawArrays(GL_TRIANGLES, 0, 36);
  5. 还原默认值,防止对后面渲染有影响
    glStencilMask(0xFF);
    glStencilFunc(GL_ALWAYS, 0, 0xFF);
    glEnable(GL_DEPTH_TEST);

其实理解了也没啥难的,我个人的理解是,模板测试相当于是对三维模型转换为二维后可以进行的一些操作。
这也在交互中有很大作用。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片