跳转到内容

OpenGL 编程/模板缓冲区

来自 Wikibooks,开放世界中的开放书籍

当您使用 OpenGL 绘制内容时,您会在屏幕上看到颜色,但请记住,除了颜色缓冲区之外还有其他缓冲区。您已经熟悉深度缓冲区,它可以防止背景像素在存在更近像素的情况下显示。现在是介绍模板缓冲区的时候了。

模板缓冲区是另一个供您自定义使用的缓冲区:您可以在其中存储每个像素的信息,并指示 OpenGL 根据这些信息采取不同的操作。

要正确操作模板缓冲区,需要理解一些要点,所以让我们花点时间学习一下。

位平面

[编辑 | 编辑源代码]

这主要是一个词汇问题:每个像素的信息都是一些位平面 - 即位。

例如,通常有 8 个位平面,这意味着屏幕上的每个像素都关联一个字节,您可以存储最多 个不同的值。您可以使用 glGetIntegerv(GL_STENCIL_BITS, &i) 检查位平面的数量。

测试和操作

[编辑 | 编辑源代码]

可以使用 glStencilFuncglStencilOp 操作模板缓冲区: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

请注意,使用 sfaildpfail 时,颜色缓冲区保持不变。

如果您绘制一个体积形状,请记住,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_circle2D 模式 绘制一个圆圈。我们不会讨论这些函数,因为它们与模板功能无关,但您可以查看源代码(请参阅页面底部的链接)。

请注意,无需使用单独的程序在模板缓冲区中绘制。在这种情况下是有意义的,因为我们绘制了 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 中的条件并使用 sfailsfail 在测试失败时不会尝试计算像素片段,因此速度更快。

示例

  • 首先使用
    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_STENCIL8DEPTH24_STENCIL8,只支持 DEPTH_COMPONENT16STENCIL_INDEX8)。

如果您需要结合使用 后期处理 和模板化,则需要避免使用帧缓冲区对象,而应使用 glCopyTexSubImage2D 技术。

参考文献

[编辑 | 编辑源代码]
  1. "OpenGL 图形系统:规范 - 版本 4.2(核心配置文件)" (PDF). Khronos.org. 2011-08-22. 检索于 2011-10-05.

< OpenGL 编程

浏览并下载 完整代码
华夏公益教科书