OpenGL 编程/模板缓冲区
当您使用 OpenGL 绘制内容时,您会在屏幕上看到颜色,但请记住,除了颜色缓冲区之外还有其他缓冲区。您已经熟悉深度缓冲区,它可以防止背景像素在存在更近像素的情况下显示。现在是介绍模板缓冲区的时候了。
模板缓冲区是另一个供您自定义使用的缓冲区:您可以在其中存储每个像素的信息,并指示 OpenGL 根据这些信息采取不同的操作。
要正确操作模板缓冲区,需要理解一些要点,所以让我们花点时间学习一下。
这主要是一个词汇问题:每个像素的信息都是一些位平面 - 即位。
例如,通常有 8 个位平面,这意味着屏幕上的每个像素都关联一个字节,您可以存储最多 个不同的值。您可以使用 glGetIntegerv(GL_STENCIL_BITS, &i)
检查位平面的数量。
可以使用 glStencilFunc
和 glStencilOp
操作模板缓冲区:glStencilFunc
指定要应用于模板缓冲区每个像素的测试,然后 glStencilOp
指定根据测试结果要执行的操作。
glStencilFunc
采用 3 个参数,构建以下测试 (ref & mask) OP (stencil & mask)
- OP:GL_NEVER、GL_ALWAYS、GL_EQUAL、GL_NOTEQUAL、GL_LESS、GL_LEQUAL、GL_GEQUAL、GL_GREATER 之一
- ref:比较中使用的固定整数
- mask:应用于 ref 和模板像素的掩码;如果使用 8 个位平面,可以使用 0xFF 禁用掩码
glStencilOp
采用 3 个参数
- sfail:来自
glStencilFunc
的测试失败 - dpfail:来自
glStencilFunc
的测试通过,但深度缓冲区测试失败 - dppass:来自
glStencilFunc
的测试通过,并且深度缓冲区通过或已禁用
这 3 个参数中的每一个都是要对模板缓冲区执行的操作,其中之一是 GL_KEEP、GL_ZERO、GL_REPLACE、GL_INCR、GL_INCR_WRAP、GL_DECR、GL_DECR_WRAP、GL_INVERT(默认值是 GL_KEEP)。
因此,我们看到两个不同的测试是链接的,导致了3 种情况
- 首先是
glStencilFunc
,如果它失败,则应用 sfail 并停止 - 然后是深度测试(如果深度缓冲区可用且已启用),如果它失败,则执行 dpfail 并停止
- 如果两个测试都通过,则在颜色缓冲区中评估片段着色器并应用 dppass
请注意,使用 sfail 和 dpfail 时,颜色缓冲区保持不变。
如果您绘制一个体积形状,请记住,OpenGL 可能会多次绘制同一个模板像素,具体取决于重叠三角形的顺序和/或深度缓冲区的可用性;在这种情况下,您的操作将被执行多次。
在我们的示例中,我们将绘制一个移动的圆圈,场景将被裁剪在圆圈内。
/* main */
glutInitDisplayMode(GLUT_RGBA|GLUT_ALPHA|GLUT_DOUBLE|GLUT_DEPTH|GLUT_STENCIL);
/* onDisplay */
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE);
glStencilFunc(GL_NEVER, 1, 0xFF);
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP); // draw 1s on test fail (always)
// draw stencil pattern
glStencilMask(0xFF);
glClear(GL_STENCIL_BUFFER_BIT); // needs mask=0xFF
draw_circle();
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
glStencilMask(0x00);
// draw where stencil's value is 0
glStencilFunc(GL_EQUAL, 0, 0xFF);
/* (nothing to draw) */
// draw only where stencil's value is 1
glStencilFunc(GL_EQUAL, 1, 0xFF);
draw_scene();
glDisable(GL_STENCIL_TEST);
draw_scene
从 教程 05 中绘制彩色立方体,而 draw_circle
以 2D 模式 绘制一个圆圈。我们不会讨论这些函数,因为它们与模板功能无关,但您可以查看源代码(请参阅页面底部的链接)。
请注意,无需使用单独的程序在模板缓冲区中绘制。在这种情况下是有意义的,因为我们绘制了 2D 模式,但完全可以在同一个场景中绘制 3D 模式,就像在 Mini-Portal 教程中一样。
在示例中,我们看到首先我们专门在模板缓冲区上绘制模板形状,然后专门在颜色缓冲区上使用它作为遮罩。
当您想修改模板缓冲区而不修改颜色缓冲区和/或深度缓冲区时,您可以屏蔽这些缓冲区
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE);
不要忘记设置它们。您可以通过这种方式保存以前的值
GLboolean save_color_mask[4];
GLboolean save_depth_mask;
glGetBooleanv(GL_COLOR_WRITEMASK, save_color_mask);
glGetBooleanv(GL_DEPTH_WRITEMASK, &save_depth_mask);
/* Do something */
glColorMask(save_color_mask[0], save_color_mask[1], save_color_mask[2], save_color_mask[3]);
glDepthMask(save_depth_mask);
如果您想在颜色缓冲区上应用更改而不修改模板缓冲区,您可以
- 设置
glStencilMask(0x00)
- 这意味着在任何情况下都不会写入任何内容 - 设置
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
- 这将对所有情况应用一个无操作
在操作模板缓冲区时很难理解问题所在,因为我们没有直接查看它的方法。
如果您使用的是非 ES OpenGL,您可以调用 glReadPixels
获取整个模板缓冲区并进行外部检查,但这很繁琐。
另一种方法是用两个三角形填充屏幕,并进行模板测试(例如,只匹配非零模板像素)。您可能需要在执行此操作时启用/禁用颜色缓冲区。
然后交换 OpenGL 颜色缓冲区并暂停,以便您可以直观地检查结果
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT);
glStencilMask(0x00);
glStencilFunc(GL_LEQUAL, 1, 0xFF);
fill_screen();
glutSwapBuffers();
cout << "swap" << endl;
sleep(1);
glStencilMask(0xFF);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
您的 fill_screen()
函数最好使用一个专用的程序,该程序不包含任何 MVP 矩阵,尽管您可以重用现有程序并传递 v=glm::mat4(1)
和 m=glm::inverse(projection)
。
我们看到 glStencilOp
中有三个可能的动作
- sfail
- dpfail
- dppass
使用 sfail 操作在模板缓冲区上表达您的操作确实违反直觉,因为存在双重否定(不是“当此条件满足时执行此操作”,而是“当此条件不满足时不要执行此操作”。
但是,如果您使用 dppass 来表达您的操作,请记住,OpenGL 会检查模板和深度测试,还会使用片段着色器计算像素值。如果您只是在模板缓冲区中绘制一个形状,这将是一个巨大的性能损失 - 因为您根本不需要调用片段着色器。
建议是首先使用 dppass 实现您的绘制算法以确保清晰,并在它正常工作后,反转 glStencilFunc
中的条件并使用 sfail。sfail 在测试失败时不会尝试计算像素片段,因此速度更快。
示例
- 首先使用
glStencilFunc(GL_NOTEQUAL, 0, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // +1 when current pixel != 0
- 然后优化
glStencilFunc(GL_EQUAL, 0, 0xFF);
glStencilOp(GL_INCR, GL_KEEP, GL_KEEP); // +1 when !(current pixel == 0)
OpenGL 4.2 核心配置文件规范提到
- 但是,当深度和模板附件都存在时,实现只需要支持两个附件都引用同一个图像的帧缓冲区对象。 [1]
这意味着您可能无法将两个渲染缓冲区附加到同一个帧缓冲区。
特别是在 OpenGL ES 2.0 中,组合格式在 glRenderbufferStorage
中不受支持 (GL_FRAMEBUFFER_UNSUPPORTED
)(通常不支持 DEPTH32F_STENCIL8
或 DEPTH24_STENCIL8
,只支持 DEPTH_COMPONENT16
和 STENCIL_INDEX8
)。
如果您需要结合使用 后期处理 和模板化,则需要避免使用帧缓冲区对象,而应使用 glCopyTexSubImage2D
技术。
- ↑ "OpenGL 图形系统:规范 - 版本 4.2(核心配置文件)" (PDF). Khronos.org. 2011-08-22. 检索于 2011-10-05.