当片段着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行,会丢弃片段,被保留的片段会进入深度测试。
即,模板测试是在深度测试之前对片段(三角形各个点)的丢弃操作。
模板缓冲(Stencil Buffer)
模板测试是根据模板缓冲来进行的,(通常)每个模板值(Stencil Value)是8位的。
所以每个像素/片段一共能有256种不同的模板值。
模板缓冲首先会被清除为0,之后在模板缓冲中使用1填充了一个空心矩形。场景中的片段将会只在片段的模板值为1的时候会被渲染(其它的都被丢弃了)。
使用模板缓冲的时候你可以尽情发挥,但大体的步骤如下:
- 启用模板缓冲的写入。
- 渲染物体,更新模板缓冲的内容。
- 禁用模板缓冲的写入。
- 渲染(其它)物体,这次根据模板缓冲的内容丢弃特定的片段。
模板测试
启动模板测试
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
做与运算和1
与0xFF
做与运算的值进行比较,比较方式为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)。
到现在我们还不知道这可以做什么,下面我们看看模板测试使用的简单样例。
物体轮廓
为物体创建轮廓的步骤如下:
- 在绘制(需要添加轮廓的)物体之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1。
- 渲染物体。
- 禁用模板写入以及深度测试。
- 将每个物体缩放一点点。
- 使用一个不同的片段着色器,输出一个单独的(边框)颜色。
- 再次绘制物体,但只在它们片段的模板值不等于1时才绘制。
- 再次启用模板写入和深度测试。
完整代码步骤:
场景中物体轮廓的完整步骤会看起来像这样:
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中:
- 更新缓存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
- 禁用模板
glStencilMask(0x00);
glDrawArrays(GL_TRIANGLES, 0, 6);
//绘制地板- 在绘制两个正方形的时候,将正方形能改变的模板值全部设为1.
shader.use();
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
glDrawArrays(GL_TRIANGLES, 0, 36); - 当模板值不为1时通过,禁用模板写入
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST); - 使用边框渲染器,放大正方形大小,绘制(将在模板值通过时绘制)。
shaderSingleColor.use();
float scale = 1.1;
model = glm::scale(model, glm::vec3(scale, scale, scale));
glDrawArrays(GL_TRIANGLES, 0, 36); - 还原默认值,防止对后面渲染有影响
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 0, 0xFF);
glEnable(GL_DEPTH_TEST);
其实理解了也没啥难的,我个人的理解是,模板测试相当于是对三维模型转换为二维后可以进行的一些操作。
这也在交互中有很大作用。
暂无评论内容