65.9K
CodeProject 正在变化。 阅读更多。
Home

在 OpenGL 中生成轮廓

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (12投票s)

2004年10月7日

5分钟阅读

viewsIcon

207643

downloadIcon

5062

使用多通道技术在 OpenGL 中生成轮廓。

引言

我最近需要编写一些代码来使用 OpenGL 为 3D 对象生成轮廓。我在互联网上查找了实现此目的的方法,最终发现了两种主要技术。第一种涉及使用多边形偏移,第二种涉及使用模板缓冲区。本文解释了这两种方法的工作原理,并更详细地介绍了模板缓冲区是什么。

背景

在 3D 图形中,经常需要为屏幕上的对象生成轮廓。这可以用于各种不同的原因。例如,CAD 系统可能会用红色轮廓显示一个对象,以表明它当前已被选中。此外,密集三角化的对象通常在线框渲染时效果不佳,而轮廓模式可以作为一种在不使线框渲染过于复杂的情况下向用户呈现对象形状的方法。最后的应用可能是在工程制图中,3D 模型的轮廓可用于自动生成模型的特定黑白投影。

Outlining a 3D object

轮廓的生成需要多次遍历场景几何体才能实现其目标。多通道渲染技术越来越普遍,并且经常用于游戏软件中以生成阴影等效果。阴影无法通过传统的 z 缓冲区技术创建,但可以通过多通道技术创建。我将在本文末尾简要解释如何做到这一点;尽管我不会展示任何关于阴影的代码。

我找到的第一种技术是快速而粗糙的。它涉及以线框模式绘制对象,并将 GL 线样式设置为非常粗的线条,然后使用背景色重绘图像。这会留下穿透对象的粗线框边缘可见,从而得到所需的轮廓。其中一个渲染通道必须进行偏移。我选择将表面向前拉出屏幕,从而确保多边形渲染在顶部。

第二种技术利用了图形卡的一项功能,称为模板缓冲区。模板缓冲区类似于 Z 缓冲区,但它允许您在渲染通道中进行每像素测试。在这种情况下,我以背景色绘制对象,每次成功将像素写入屏幕时,都会在模板缓冲区中写入一个值。然后,对象以线框模式重新绘制,使用粗线条,但仅在模板缓冲区为空时进行写入。

为了快速实现一些 OpenGL 代码,我使用了来自另一个 Code Project 文章的库和演示应用程序。OGLTools(由 Jonathan de Halleux 编写)提供了一个面向对象的框架,用于创建 GL 渲染上下文并处理所有特定于 Windows 的 OpenGL 内容。请注意,为了使用模板缓冲区,您必须确保在像素格式中获得模板缓冲区。我通过对 Jonathan 的项目进行一些小改动来实现这一点。

使用代码

演示应用程序基于 Jonathan de Halleux 的应用程序。我对绘制循环进行了编辑,使其能够以三种方式之一进行绘制。第一种使用多边形偏移,第二种使用模板缓冲区,第三种使用模板缓冲区以及显示列表。在第三种算法中,我将对象绘制成深蓝色而不是背景色,以便您可以看到由此产生的效果。您可以从应用程序中的标准菜单中选择模式。

Outlining a 3D object - rendering in blue

第一段代码展示了如何使用多边形偏移绘制对象

// Push the GL attribute bits so that we don't wreck any settings
glPushAttrib( GL_ALL_ATTRIB_BITS );
// Enable polygon offsets, and offset filled polygons forward by 2.5
glEnable( GL_POLYGON_OFFSET_FILL );
glPolygonOffset( -2.5f, -2.5f );
// Set the render mode to be line rendering with a thick line width
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
glLineWidth( 3.0f );
// Set the colour to be white
glColor3f( 1.0f, 1.0f, 1.0f );
// Render the object
RenderMesh3();
// Set the polygon mode to be filled triangles 
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glEnable( GL_LIGHTING );
// Set the colour to the background
glColor3f( 0.0f, 0.0f, 0.0f );
// Render the object
RenderMesh3();
// Pop the state changes off the attribute stack
// to set things back how they were
glPopAttrib();

第二段代码演示了模板缓冲区的用法

// Push the GL attribute bits so that we don't wreck any settings
glPushAttrib( GL_ALL_ATTRIB_BITS );
glEnable( GL_LIGHTING );
// Set the clear value for the stencil buffer, then clear it
glClearStencil(0);
glClear( GL_STENCIL_BUFFER_BIT );
glEnable( GL_STENCIL_TEST );
// Set the stencil buffer to write a 1 in every time
// a pixel is written to the screen
glStencilFunc( GL_ALWAYS, 1, 0xFFFF );
glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
// Render the object in black
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glColor3f( 0.0f, 0.0f, 0.0f );
RenderMesh3();
glDisable( GL_LIGHTING );
// Set the stencil buffer to only allow writing
// to the screen when the value of the
// stencil buffer is not 1
glStencilFunc( GL_NOTEQUAL, 1, 0xFFFF );
glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
// Draw the object with thick lines
glLineWidth( 3.0f );
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
glColor3f( 1.0f, 1.0f, 1.0f );
RenderMesh3();
// Pop the state changes off the attribute stack
// to set things back how they were
glPopAttrib();

更详细地解释关键的 GL 调用:glClearStencil 用于设置对 glClear( GL_STENCIL_BUFFER_BIT ) 的调用清除值。这允许您将模板缓冲区初始化为您想要的任何值。glEnable( GL_STENCIL_TEST ) 的含义很明显,它开启了模板缓冲区的用法。关键函数是 glStencilFuncglStencilOpglStencilFunc 允许您设置一个测试函数,指定一个参考值和一个在任何测试执行之前将应用于模板缓冲区和参考值的掩码。测试函数可以包含各种比较运算符,或通用的 GL_NEVERGL_ALWAYSglStencilOp 解释了在模板缓冲区测试失败、模板缓冲区测试通过但 z 缓冲区测试失败,以及模板缓冲区测试和 z 缓冲区测试都通过的情况下应采取的操作。这里的选项包括保留现有值、将缓冲区清零或替换、反转、增加或减少值。

关注点

最后,我想解释一种使用模板缓冲区和多通道渲染来生成阴影的众多方法之一。以下过程将从一个简单的对象在地面上生成一个阴影

  1. 将视图变换加载到模型视图矩阵中。这会将系统设置为从视点查看。
  2. 绘制对象和地面,启用照明。
  3. 使用 glPushMatrix() 推送模型视图矩阵。
  4. 构建一个光照视图矩阵并将其乘以模型视图矩阵。光照视图矩阵是使用地面方程和光源位置通过特定方式生成的。
    void shadowMatrix(Glfloat m[4][4],
    GLfloat plane[4],
    GLfloat light[4])
    {
    GLfloat dot = plane[0]*light[0] + plane[1]*light[1] +
    plane[2]*light[2] + plane[3]*light[3];
    m[0][0] = dot - light[0]*plane[0];
    m[1][0] = - light[0]*plane[1];
    m[2][0] = - light[0]*plane[2];
    m[3][0] = - light[0]*plane[3];
    m[0][1] = - light[1]*plane[0];
    m[1][1] = dot - light[1]*plane[1];
    m[2][1] = - light[1]*plane[2];
    m[3][1] = - light[1]*plane[3];
    m[0][2] = - light[2]*plane[0];
    m[1][2] = - light[2]*plane[1];
    m[2][2] = dot - light[2]*plane[2];
    m[3][2] = - light[2]*plane[3];
    m[0][3] = - light[3]*plane[0];
    m[1][3] = - light[3]*plane[1];
    m[2][3] = - light[3]*plane[2];
    m[3][3] = dot - light[3]*plane[3];
    }
  5. 设置多边形偏移,以便渲染的阴影多边形不会与地面重叠。
  6. 将地面渲染到模板缓冲区 - 每次写入地面时设置一个值。
  7. 禁用照明,将颜色设置为深灰色,然后再次渲染对象(不是地面)。仅当模板缓冲区已设置时才渲染对象点,并且每次渲染时,都将模板缓冲区的内容清零。这个稍微复杂的过程将确保阴影仅投射在地面上,并且阴影多边形仅渲染一次。
  8. 弹出矩阵并禁用多边形偏移以进行清理。

这项技术以及其他使用多通道渲染的技术,在 nVidia 的 Mark J Kilgard 的一篇文章中有更详细的解释。

历史

  • 版本 1 于 04 年 10 月 7 日发布。
© . All rights reserved.