首页 > 代码库 > 栅格化

栅格化

OpenGL 栅格化

栅格化是将凹多边形或自相交多边形分割成凸多边形的过程。由于OpenGL只接受渲染凸多边形,那些非凸多边形在渲染之前必须栅格化。


左边为凹四边形,中间为有洞多边形,右边为自相交图形

概述

栅格化的基本过程是将非凸多边形的所有顶点发送到栅格器而不是直接发送到OpenGL渲染管线,然后由栅格器对多边形栅格化。最后,当栅格过程结束,栅格器将调用用户定义回调函数中的实际OpenGL命令渲染这些栅格化的多边形。

OpenGL提供一组用于将凹多边形转化为凸多边形的函数:

GLUtessellator* gluNewTess()void gluDeleteTess(GLUtessellator *tess)

gluNewTess()创建栅格器对象,gluDeleteTess()删除该栅格器对象。如果创建失败,它返回NULL指针。

void gluTessBeginPolygon(GLUtessellator *tess, void *userData)void gluTessEndPolygon(GLUtessellator *tess)

需要使用栅格器指定的块结构gluTessBeginPolygon()与gluTessEndPolygon()描述多边形的所有定点,而不是使用glBegin()与glEnd()块。你必须在该块结构中描述非凸多边形。

void gluTessBeginContour(GLUtessellator *tess)void gluTessEndContour(GLUtessellator *tess)

多边形可以具有多个封闭轮廓(封闭环),如含洞的多边形具有2个轮廓:内轮廓与外轮廓。每个轮廓必须在该块结构中组织。gluTessBeginPolygon()与gluTessEndPolygon()块可以进行嵌套。

void gluTessVertex(GLUtessellator *tess, GLdouble cords[3], void *vertexData)

gluTessVertex()指定轮廓定点。栅格器使用这些定点坐标执行栅格过程。所有定点需要近似在一个平面。第二个参数为栅格过程的定点坐标,第三个参数为实际渲染用到的数据。它不仅仅可以是定点坐标,也可以为颜色、法向与纹理坐标。

void gluTessCallback(GLUtessellator *tess, GLUenum type, void (*fn)())

栅格化过程中,栅格器在渲染栅格多边形时将调用一系列回调函数。你必须提供合适的包含用于渲染多边形的实际OpenGL命令的回调函数,如glBegin()、glEnd()、glVertex*()等。

下列代码段为通用实例。

// 创建栅格器GLUtesselator *tess = gluNewTess();// 注册回调函数gluTessCallback(tess, GLU_TESS_BEGIN,   beginCB);gluTessCallback(tess, GLU_TESS_END,     endCB);gluTessCallback(tess, GLU_TESS_VERTEX,  vertexCB);gluTessCallback(tess, GLU_TESS_COMBINE, combineCB);gluTessCallback(tess, GLU_TESS_ERROR,   errorCB);// 描述非凸多边形gluTessBeginPolygon(tess, user_data);    // 第一个轮廓    gluTessBeginContour(tess);        gluTessVertex(tess, coords[0], vertex_data);        ...    gluTessEndContour(tess);    // 第二个轮廓    gluTessBeginContour(tess);        gluTessVertex(tess, coords[5], vertex_data);        ...    gluTessEndContour(tess);    ...gluTessEndPolygon(tess);// 处理完后删除栅格器gluDeleteTess(tess);

实例

这是3个栅格化实例:左边的为具有四个顶点的简单凹多边形,中间的为含洞多边形,右边的为自相交多边形(星形)。

下载源代码与二进制文件:tessellation.zip。

简单凹多边形

该图形的栅格化过程在tessellate1()中描述。OpenGL栅格器在执行栅格过程的时候选择最有效的基础类型:GL_TRIANGLE、GL_TRIANGLE_FAN、GL_TRIANGLE_STRIP与GL_LINE_LOOP。此种情况,栅格器使用GL_TRIANGLE_FAN。

在回调函数中,你需要记录实际的OpenGL命令,它们在栅格过程中执行。下列代码由上个器生成并在回调函数中记录。参考源文件中每个回调函数,查看它如何记录。

glBegin(GL_TRIANGLE_FAN);    glVertex3dv(v3);    glVertex3dv(v0);    glVertex3dv(v1);    glVertex3dv(v2);glEnd();

注意,该多边形的环绕方向为逆时针方向(CCW),表明多边形的面法线朝向平面外面。如果环绕方向为顺时针方向(CW),法向量为相反方向,因此你将看到背面而不是正面。你可以通过使用gluTessNormal()显示定义面法线。

void gluTessNormal(GLUtessellator *tess, GLdouble x, GLdouble y, GLdouble z)

如果你指定法向量为(0,0,1),你将总是看到正面,即使环绕方向为顺时针防线(假定相机朝向默认方向-Z)。默认法向量值为(0,0,0),表示栅格器将从给定顶点与环绕方向计算法向。

含洞多边形

第二个示例具有2个逆时针(CCW)的内环与外环,在tessellate2()中指定。栅格器生成如下的OpenGL命令:1个三角形条与1个三角形。

glBegin(GL_TRIANGLE_STRIP);    glVertex3dv(v1);    glVertex3dv(v5);    glVertex3dv(v0);    glVertex3dv(v4);    glVertex3dv(v3);    glVertex3dv(v7);    glVertex3dv(v2);    glVertex3dv(v6);    glVertex3dv(v5);glEnd();glBegin(GL_TRIANGLES);    glVertex3dv(v5); glVertex3dv(v1);    glVertex3dv(v2);glEnd();

你或许疑惑OpenGL栅格器如何知道中间的区域为洞(非填充)。答案为环绕规则与环绕数量。栅格器给由多个轮廓分割的区域赋值环绕数。这个例子,有2个独立区域:内部区域的环绕书为2,外部区域的环绕书为1。假定默认环绕规则GLU_TESS_WINDING_ODD,奇数标示的区域为填充区域,偶数标示的为非填充区域。

如果内轮廓为顺时针旋转,内部区域的环绕数为0,外区域的环绕数还是1.因此,由于内部区域的环绕数非奇(0),内部区域依旧是洞(非填充)。环绕规则在下一节介绍。

自相交多边形

最后一个实例为星形,一个自相交多边形,在tessellator3()中指定。注意,栅格器添加5个额外的顶点,其中2个边线相交,v5、v6、v7、v8、v9。当栅格器算法检测到一个相交时,为了创建新顶点数据,必须提供GLU_TESS_COMBINE_CALLBACK回调函数,因此可以在后面绘制它。

void combineCB(GLdouble newVert[3], GLdouble *neighbourVert[4],               GLfloat neighborWeight[4], void **outData);

combine回调函数,用于当2条边相交时创建新顶点数据。它拥有4个参数:newVert[3]为GLdouble类型的x、y、z坐标数组。它表示栅格器找寻的新相交顶点位置。第二个参数为四个已存邻接顶点指针数组,它们构成2相交边。第三个参数为四个已存邻接顶点的权重因子。该权重用于计算相交点的颜色、法线、纹理坐标差值。最后一个参数为输出顶点数据指针。输出数据不仅包含顶点坐标,还有颜色、法线与纹理坐标。为了绘制相交顶点,栅格器将输出数据传递到GLU_TESS_VERTEX回调函数中。

下述OpenGL命令由栅格器生成:2个三角形条与一个三角形。

glBegin(GL_TRIANGLE_FAN);    glVertex3dv(v9);    glVertex3dv(v0);    glVertex3dv(v5);    glVertex3dv(v7);    glVertex3dv(v8);    glVertex3dv(v2);glEnd();glBegin(GL_TRIANGLE_FAN);    glVertex3dv(v6);    glVertex3dv(v1);    glVertex3dv(v7);    glVertex3dv(v5);    glVertex3dv(v3);glEnd();glBegin(GL_TRIANGLES);    glVertex3dv(v4);    glVertex3dv(v8);    glVertex3dv(v7);glEnd();

考虑到该多边形的环绕规则与环绕数。多边形分为6个独立区域,环绕数如图所示。

对于GLU_TESS_WINDING_ODD规则,由于环绕数为偶数,中间区域不会被填充。我们需要一个不同的环绕规则,因此我们能够填充奇数与偶数区域。此种情况,环绕规则为GLU_TESS_WINDING_NONEZERO,它准许填充非零区域。(GLU_TESS_WINDING_POSITIVE也可以实现)。

栅格器提供gluTessProperty()以改变环绕规则以及其他属性,如GLU_TESS_BOUNDING_ONLY与GLU_TESS_TOLERANCE。关于环绕规则的更详尽信息参考下一节。

void gluTessProperty(GLUtesselator *tess, GLenum property, GLdouble *value)// 示例gluTessProperty(tess, GLU_TESS_BOUNDARY_ONLY, GL_TRUE);gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);

环绕规则与环绕数

想象一下多个轮廓线,它们相互重叠或嵌套,将平面分成多个区域。环绕规则决定哪些区域为内部区域哪些为外部区域。因此,内部区域为填充区域,外部区域为非填充区域。

对于每一个由多个轮廓线分割的封闭区域,OpenGL栅格器都予以赋值。从区域中引一条任意方向的射线,从0开始,每遇到一个逆时针轮廓线(从右到左)就加1,每遇到一个顺时针轮廓线(从左到右)就减1。统计完所有相交区域之后,环绕数代表该区域。

若环绕规则为GLU_TESS_WINDING_ODD,奇数区域为内部区域且被填充,偶数区域为外部区域非填充。因此,区域1与-1被填充,区域0为洞。

可能的环绕规则为:

GLU_TESS_WINDING_ODD:填充奇数(默认设置)

GLU_TESS_WINDING_NONZERO:填充非零数

GLU_TESS_WINDING_POSITIVE:填充正数

GLU_TESS_WINDING_NEGATIVE:填充负

GLU_TESS_WINDING_ABS_GEO_TWO:填充绝对值大于等于2的数

下图显示不同环绕规则的不同内部区域(阴影)。如果设置GLU_TESS_WINDING_ABS_GEQ_TWO,所有都不绘制(所有区域都是外部区域)。