本文正在参加「金石计划」
** 什么是模板测试**
模板测试和深度测试作用类似,模板测试主要是通过对比模板缓冲区来决定是否需要对片段进行丢弃。
在渲染时更新模板缓冲区可以获取很多有趣的效果。
一个模板缓冲区通常使用8位来表示,因此一个像素可以有256中表示方法。
下面是一个简单的模板缓冲区图示(来源):
上面图示中:先使用0来清空模板缓冲区,然后再绘制一个新的长方形,此时我们将1写入模板缓冲区,绘制出来的图形只会显示模板缓冲区为1的像素图形。
模板缓冲区允许我们使用指定的值去设置缓冲区,通过改变模板缓冲区的值实现一些特殊场景,这个一般步骤如下:
- 1.先开启模板测试
- 2.绘制源物体并改变模板缓冲区的值
- 3.禁止模板缓冲区写入。
- 4.绘制其他物体,此时可以使用对应的策略:如绘制的时候,丢弃和源物体重合的部分。
模板测试api
OpenGL相关的模板测试api包括以下几种:
- 1.启动模板测试:
glEnable(GL_STENCIL_TEST);
启动模板测试之后,记得清空模板缓冲区。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
- 2.开启/禁止模板缓冲区写入
glStencilMask(0xFF); // each bit is written to the stencil buffer as is
glStencilMask(0x00); // each bit ends up as 0 in the stencil buffer (disabling writes)
将模板缓冲区掩码设置为0之后,表示当前模板缓冲区不可写,其他的值表示可写、
- 3.模板测试策略函数
和深度测试类似,模板测试也有对应的api函数来设置模板测试的策略,如测试失败或者成功的情况下如何处理模板缓冲区。
模板测试提供了两个函数:glStencilFunc and glStencilOp.
glStencilFunc函数原型:
The glStencilFunc(GLenum func, GLint ref, GLuint mask)
三个参数意义:
-
func:设置模板测试成功与否的策略。包括下面几种情况:
func策略 描述 GL_NEVER 每次模板测试都失败 GL_LESS 比参考值更小就通过测试 GL_LEQUAL 小余或等于参考值就通过测试 GL_GREATER 比参考值更大就通过测试 GL_GEQUAL 大于或等于参考值就通过测试 GL_EQUAL 缓冲区的值和参数值ref相等 GL_NOTEQUAL 缓冲区的值和参数值ref不相等 GL_ALWAYS 每次模板测试都成功 -
ref:参考值,这个值会用来和模板缓冲区中的值进行比较,来判断是否丢弃片段。
-
mask:设置一个掩码,在模板缓冲区和ref参考值进行比较值钱,会使用mask做一个按位与的操作,初始化当前值,一般是在为0xFF即可,保留原值。
glStencilOp函数原型:
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
- sfail:表示模板测试失败如何更新缓冲区的值
| func策略 | 描述 |
|---|---|
| GL_KEEP | 保持现有的模板值 |
| GL_ZERO | 将模板值置为 0 |
| GL_REPLACE | 将模板值设置为用 glStencilFunc 函数设置的ref值 |
| GL_INCR | 如果模板值不是最大值就将模板值 +1 |
| GL_INCR_WRAP | 与 GL_INCR 一样将模板值 +1 ,如果模板值已经是最大值则设为 0 |
| GL_DECR | 如果模板值不是最小值就将模板值 -1 |
| GL_DECR_WRAP | 与 GL_DECR 一样将模板值 -1 ,如果模板值已经是最小值则设为最大值 |
| GL_INVERT | 按位反转当前模板缓冲区的值 |
- dpfail:表示模板测试成功,但是深度测试失败如何更新模板缓冲区。
- dppass:表示模板测试和深度测试都成功如何更新模板缓冲区的值。
下面是对应的所有的策略:
| func策略 | 描述 |
|---|---|
| GL_KEEP | 保持现有的模板值 |
| GL_ZERO | 将模板值置为 0 |
| GL_REPLACE | 将模板值设置为用 glStencilFunc 函数设置的ref值 |
| GL_INCR | 如果模板值不是最大值就将模板值 +1 |
| GL_INCR_WRAP | 与 GL_INCR 一样将模板值 +1 ,如果模板值已经是最大值则设为 0 |
| GL_DECR | 如果模板值不是最小值就将模板值 -1 |
| GL_DECR_WRAP | 与 GL_DECR 一样将模板值 -1 ,如果模板值已经是最小值则设为最大值 |
| GL_INVERT | 按位反转当前模板缓冲区的值 |
如设置:
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
则表示模板测试失败时,保持原有模板值,模板测试成功但是深度测试失败时保持原有模板值,两种都成功,则更新模板值为参考值。
下面我们使用一个案例来分析模板测试
物体轮廓绘制
如果你对上面的基础知识都已经掌握的差不多了,下面的demo对我们应该不会太难:
绘制物体轮廓的一般步骤如下:
- 1.首先开启模板测试,清空模板缓冲区,开启模板缓冲区写入
- 2.在绘制源物体之前设置模板测试操作为GL_ALWAYS,设置参考值为1,掩码为0xFF,这样可以在每次绘制的时候都可以将模板缓冲区写入1。
- 3.绘制源物体
- 4.禁止模板缓冲区写入并禁止深度测试。
- 5.将源物体放大一点。
- 6.使用一个纯颜色的片段着色器对源物体进行绘制。
- 7.绘制源物体,但是此时使用GL_NOTEQUAL 的方式进行模板缓冲区绘制策略
- 8.重新开启深度缓冲区和模板可写入操作。
代码如下:
void MyGLRenderContext::OnDrawFrame() {
isDrawing = true;
beforeDraw();
if(programObj == 0){
return;
}
//清除buffer
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glStencilFunc(GL_ALWAYS,1,0xff);
glStencilMask(0xff);
//使用程序着色器对象
glUseProgram(programObj);
//绑定VAO
glBindVertexArray(VAO);
//观察者位置
setCordder(model);
//开始绘制
glDrawArrays(GL_TRIANGLES,0,36);
//解绑VAO
glBindVertexArray(GL_NONE);
glStencilFunc(GL_NOTEQUAL,1,0xff);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);
glUseProgram(singleColorShader);
glBindVertexArray(VAO);
GLfloat scale = 1.1f;
if(isFirstDraw){
modelScale = glm::scale(modelScale, glm::vec3(scale, scale, scale));
isFirstDraw = false;
}
setCordder(modelScale);
glDrawArrays(GL_TRIANGLES,0,36);
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 0, 0xFF);
glEnable(GL_DEPTH_TEST);
glBindVertexArray(GL_NONE);
//解绑程序着色器对象
glUseProgram(GL_NONE);
isDrawing = false;
}
轮廓的片段着色器代码:
"#version 300 es \n"
"precision mediump float;\n"
"out vec4 outColor;\n"
"void main()"
"{ \n"
" outColor = vec4(0.23f, 0.35f, 0.2f, 1.0f); \n"
"} \n";
效果如下: