一、什么叫分格化?
分格化就是把复杂多边形(非凸多边形,如下图)分割成凸多边形的过程。典型的复杂多变性主要有三类:凹多边形、中间有洞的多边形以及自相交多边形。
二、为什么要进行分格化?
因为OpenGL只能够保证正确渲染凸多边形,而不能保证渲染复杂多边行结果的正确性(当然有时也正确)。至于OpenGL为什么不提供对复杂多边形的正确性支持,主要是由于渲染性能方面的考虑。
三、如何分格化?
glu(OpenGL Utility Library,OpenGL实用库)提供一系列的分格化函数供我们调用。在分格化一个复杂多边形时,需要执行以下几个典型步骤:
1. 用gluNewTess()函数创建一个分格化对象;
2. 调用gluTessCallback()函数注册相关回调函数,这些回调函数在分格化执行时调用。其中,最为复杂的情况是当分格化算法检测到多边形存在相交并且必须调用在GLU_TESS_COMBINE回调函数中所注册的函数的时候;
3. 调用gluTessProperty()函数制定分格化属性。其中最重要的属性是环绕规则,它确定了多边形的哪些区域应该填充,哪些区域不应该着色;
4.
通过制定一个或多个闭合多边形的轮廓来渲染经过分格化的多边形。如果物体的数据时静态的,可以把经过分格化的多边形放在显示列表中(如果不需要反复重新进行分格化计算,使用显示列表可以提高效率);
5. 如果需要对其他物体进行分格化,可以复用原来的分格化队形。如果完成对分格化对象的操作,可以使用gluDeleteTess()函数删除该分格化对象。
分格化实际上是在把顶点传入OpenGL管线之前,先将其传到分格化对象(tessellator),分格化对象处理完后,tessellator再把处理后的顶点传入到OpenGL管线。
四、详解
1. 分格化对象
创建分格化对象,需要调用:
GLUtesselator* gluNewTess(void);
此函数创建一个新的分格化对象,并返回一个指向它的指针。如果创建失败,该函数返回NULL指针。可以在所有的分格化任务中复用同一个分格化对象。(分格化的单词为tessellate,此处应该是拼写错误!)
2. 回调函数
为什么要引入回调函数?有两方面的意义:一是给用户查看信息的机会;二是给用户修改信息的机会。回调函数的注册是调用如下函数:
void gluTessCallback(GLUtesselator* tessobj, GLenum type, void (*fn)());
此函数将回调函数fn与分格化对象tessobj相关联。type指示回调函数的类型,type的不同,函数参数也不同。值得注意的是,此注册函数并未向fn传递参数,fn的参数是由OpenGL在分格化过程中调用时指定的。OpenGL的
分格化回调函数主要有6大类,12种:
(1)GLU_TESS_BEGIN:void begin(GLenum type);
在每个分格化对象被创建之后,该回调函数就会被调用。type的类型主要有四种:GL_TRIANGLE_FAN、GL_TRIANGLE_STRIP、GL_TRIANGES或GL_LINE_LOOP。当分格化对象对多边形进行分解时,分格化算法决定了使用哪种类型的三角形图元的效率最高(如果启用GLU_TESS_BOUNDARY_ONLY属性,就使用GL_LINE_LOOP进行渲染)。
(2)GLU_TESS_EDGE_FLAG:void edgeFlag(GLboolean flag);
边界标志对
GL_TRIANGLE_FAN与
GL_TRIANGLE_STRIP没有意义,因此如果有一个与
GLU_TESS_EDGE_FLAG相关联的毁掉函数启用了边界标志,则GL_TESS_BEGIN回调函数只能用于GL_TRIANGLES。
(3)GLU_TESS_VERTEX:void vertex(void* vertex_data);
vertex_data返回分格化后的顶点数据。该回调函数在GLU_TESS_BEGIN与GLU_TESS_END之间会被频繁调用。
(4)GL_TESS_END:void end(void);
此回调函数在分格化结束之后调用。
(5)GL_TESS_COMBINE:void combine(GLdouble coords[3], void* vertex_data[4], GLfloat weight[4], void** outData);
当分格化算法检查输入轮廓线的橡胶情况,以决定是否创建新顶点时,这个回调函数也会被调用;当分格化对象决定把两个非常接近的顶点合并为一个顶点时,这个回调函数也会被调用。新创建的顶点最多可达4个原有顶点的线性组合(vertex_data[4]),weight[4]为这4个顶点的权重,coords[3]为新创建的顶点,outData为返回的顶点坐标。
该注册的回调函数必须为另一个顶点分配内存,使用vertex_data与weight数组中的数据进行加权差值,并在outData中返回这个新顶点的指针。
(6)GL_TESS_ERROR:void error(GLenum errno);
如果在分格化过程中出现错误,该毁掉函数就会传递一个GLU错误号。可以使用gluErrorString()函数获取一个描述错误信息的字符串。
余下6种为用户指定数据版本,分别与上面6中一一对应,只是每一种回调函数都多了一个参数(user_data),可以传递用户自定义的数据信息,如类名:
(7)GLU_TESS_BEGIN_DATA:void begin(GLenum type, void* user_data);
(8)GLU_TESS_EDGE_FLAG
_DATA
:void edgeFlag(GLboolean flag
, void* user_data
);
(9)GLU_TESS_VERTEX
_DATA
:void vertex(void* vertex_data
, void* user_data
);
(10)GL_TESS_END
_DATA
:void end(
, void* user_data
);
(11)GL_TESS_COMBINE
_DATA
:void combine(GLdouble coords[3], void* vertex_data[4], GLfloat weight[4], void** outData
, void* user_data
);
(12)GL_TESS_ERROR
_DATA
:void error(GLenum errno
, void* user_data
);
注意,如果指定一个特定回调函数的全部两个版本,具有user_data的那个版本就会被实际使用,另一个责备忽略。所以,在实际能够同时使用的回调函数最大数量为6。
3. 分格化属性
在进行分格化之前,设置分格化属性,可以对分格化算法进行控制。调用函数:
void gluTessProperty(GLUtesselator* tessobj, GLenum property, GLdouble value);
tessobj为分格化对象,property为属性类型,主要有:
GLU_TESS_BOUNDARY_ONLY:value值为GL_TRUE或GL_FALSE,当为GL_TRUE时,多边形不再分格化为填充多边形,而是用线框来绘制多边形的轮廓,区分多边形的内部和外部。
GLU_TESS_TOLERANCE:value表示一个距离的公差值,表示两个顶点足够靠近时,可以由GLU_TESS_COMBINE回调函数进行合并。默认0。
GLU_TESS_WINDING_RULE:决定多边形的那部分位于内部,哪部分位于外部(不进行填充)。value的值为:
GLU_TESS_WINDING_ODD( 默认),渲染环绕数为奇数的区域
GLU_TESS_WINDING_NONZERO:渲染环绕数不为0的区域
GLU_TESS_WINDING_POSITIVE:渲染环绕数为正的区域
GLU_TESS_WINDING_NEGATIVE:
GLU_TESS_WINDING_ABS_GEQ_TWO:
4. 环绕数与环绕规则
环绕数的计算规则:对于每个区域内的点的环绕数就是环绕这个点的所有轮廓线的代数和,逆时针为正,顺时针为负。如下图:
五、实例
#include
#include
#include "gl/glut.h"
#ifndef CALLBACK
#define CALLBACK
#endif
const char* szCaption = "Tess.cpp";
GLint nProperty=0; //环绕规则索引
GLdouble TessProperty[5] = { //环绕规则
GLU_TESS_WINDING_ODD, //环绕数为奇数
GLU_TESS_WINDING_NONZERO, //环绕数为非0
GLU_TESS_WINDING_POSITIVE, //环绕数为正数
GLU_TESS_WINDING_NEGATIVE, //环绕数为负数
GLU_TESS_WINDING_ABS_GEQ_TWO //环绕数绝对值大于等于2
};
GLint nTessInsectCount = 0; // 焦点个数
GLdouble dTessInsectPnt[100][6]; // 焦点,最大100个焦点
GLdouble graphics0[3][4][3] = { //左上角图形
{{10.0, 10.0, 0.0}, {-10.0, 10.0, 0.0}, {-10.0, -10.0, 0.0}, {10.0, -10.0, 0.0}},
{{7.0, 7.0, 0.0}, {-7.0, 7.0, 0.0}, {-7.0, -7.0, 0.0}, {7.0, -7.0, 0.0}},
{{4.0, 4.0, 0.0}, {-4.0, 4.0, 0.0}, {-4.0, -4.0, 0.0}, {4.0, -4.0, 0.0}}
};
GLdouble graphics1[3][4][3] = { //右上角图形
{{10.0, 10.0, 0.0}, {-10.0, 10.0, 0.0}, {-10.0, -10.0, 0.0}, {10.0, -10.0, 0.0}},
{{7.0, 7.0, 0.0}, {7.0, -7.0, 0.0}, {-7.0, -7.0, 0.0}, {-7.0, 7.0, 0.0}},
{{4.0, 4.0, 0.0}, {4.0, -4.0, 0.0}, {-4.0, -4.0, 0.0}, {-4.0, 4.0, 0.0}}
};
GLdouble graphics2[11][3] = { //左下角图形
{10.0, 10.0, 0.0}, {-10.0, 10.0, 0.0}, {-10.0, -10.0, 0.0}, {10.0, -10.0, 0.0},
{13.0, 2.0, 0.0}, {-13.0, 2.0, 0.0}, {-13.0, -2.0, 0.0}, {13.0, -2.0, 0.0},
{3.0, 6.0, 0.0}, {-3.0, 6.0, 0.0}, {0.0, -15.0, 0.0}
};
GLdouble graphics3[16][3] = { //右下角图形
{7.0, 10.0, 0.0}, {-10.0, 10.0, 0.0}, {-10.0, -10.0, 0.0}, {10, -10.0, 0.0},
{10.0, 1.0, 0.0}, {-1.0, 1.0, 0.0}, {-1.0, -1.0, 0.0}, {1.0, -1.0, 0.0},
{1.0, 4.0, 0.0}, {-4.0, 4.0, 0.0}, {-4.0, -4.0, 0.0}, {4.0, -4.0, 0.0},
{4.0, 7.0, 0.0}, {-7.0, 7.0, 0.0}, {-7.0, -7.0, 0.0}, {7.0, -7.0, 0.0}
};
//顶点的回调函数
void CALLBACK vertexCallback(GLvoid* vertex)
{
GLdouble* pt;
GLubyte red, green, blue;
int numb;
pt = (GLdouble*)vertex;
numb = rand();
red = (GLubyte)rand()&0xff; //(numb>>16) & 0xff;
green = (GLubyte)rand()&0xff; //(numb>>8) & 0xff;
blue = (GLubyte)rand()&0xff; //numb & 0xff;
glColor3ub(red, green, blue);
glVertex3dv(pt);
}
void CALLBACK beginCallback(GLenum type)
{
glBegin(type);
}
void CALLBACK endCallback()
{
glEnd();
}
void CALLBACK errorCallback(GLenum errorCode)
{
const GLubyte * estring;
//打印错误类型
estring = gluErrorString(errorCode);
fprintf(stderr, "Tessellation Error: %s/n", estring);
exit(0);
}
//用于处理检测轮廓线交点,并决定是否合并顶点,
//新创建的顶点最多可以是4个已有顶点线性组合,这些定点坐标存储在data中
//其中weight为权重,weight[i]的总合为1.0
void CALLBACK combineCallback(GLdouble coords[3],
GLdouble* data[4],
GLfloat weight[4], GLdouble ** dataout)
{
// GLdouble *vertex;
int i;
//vertex = (GLdouble*)malloc(6*sizeof(GLdouble));
dTessInsectPnt[nTessInsectCount][0] = coords[0];
dTessInsectPnt[nTessInsectCount][1] = coords[1];
dTessInsectPnt[nTessInsectCount][2] = coords[2];
for(i = 3; i < 6; i++) //新顶点的颜色为4个顶点的线型组合
{
dTessInsectPnt[nTessInsectCount][i] = weight[0]*data[0][i]+weight[1]*data[1][i]
+weight[2]*data[2][i]+weight[3]*data[3][i];
}
*dataout = dTessInsectPnt[nTessInsectCount];
++nTessInsectCount;
}
void init()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_SMOOTH);
}
void drawGraphics(GLint ngraphic, GLint nproperty)
{
int i;
GLUtesselator * tessobj;
tessobj = gluNewTess();
//注册回调函数
gluTessCallback(tessobj, GLU_TESS_VERTEX, (void (CALLBACK *)())vertexCallback);
gluTessCallback(tessobj, GLU_TESS_BEGIN, (void (CALLBACK *)())beginCallback);
gluTessCallback(tessobj, GLU_TESS_END, (void (CALLBACK *)())endCallback);
gluTessCallback(tessobj, GLU_TESS_ERROR, (void (CALLBACK *)())errorCallback);
//设置环绕规则
gluTessProperty(tessobj, GLU_TESS_WINDING_RULE, TessProperty[nproperty]);
if(2==ngraphic || 3==ngraphic)
gluTessCallback(tessobj, GLU_TESS_COMBINE, (void (CALLBACK *)())combineCallback);//多边型边自相交的情况下回调用回调函数
gluTessBeginPolygon(tessobj, NULL);
switch(ngraphic) {
case 0:
gluTessBeginContour(tessobj); //定义轮廓线1 逆时针矩形
gluTessVertex(tessobj, graphics0[0][0], graphics0[0][0]);
gluTessVertex(tessobj, graphics0[0][1], graphics0[0][1]);
gluTessVertex(tessobj, graphics0[0][2], graphics0[0][2]);
gluTessVertex(tessobj, graphics0[0][3], graphics0[0][3]);
gluTessEndContour(tessobj);
gluTessBeginContour(tessobj); //定义轮廓线2 逆时针矩形
gluTessVertex(tessobj, graphics0[1][0], graphics0[1][0]);
gluTessVertex(tessobj, graphics0[1][1], graphics0[1][1]);
gluTessVertex(tessobj, graphics0[1][2], graphics0[1][2]);
gluTessVertex(tessobj, graphics0[1][3], graphics0[1][3]);
gluTessEndContour(tessobj);
gluTessBeginContour(tessobj); //定义轮廓线3 逆时针矩形
gluTessVertex(tessobj, graphics0[2][0], graphics0[2][0]);
gluTessVertex(tessobj, graphics0[2][1], graphics0[2][1]);
gluTessVertex(tessobj, graphics0[2][2], graphics0[2][2]);
gluTessVertex(tessobj, graphics0[2][3], graphics0[2][3]);
gluTessEndContour(tessobj);
break;
case 1:
gluTessBeginContour(tessobj); //定义轮廓线1 逆时针矩形
gluTessVertex(tessobj, graphics1[0][0], graphics1[0][0]);
gluTessVertex(tessobj, graphics1[0][1], graphics1[0][1]);
gluTessVertex(tessobj, graphics1[0][2], graphics1[0][2]);
gluTessVertex(tessobj, graphics1[0][3], graphics1[0][3]);
gluTessEndContour(tessobj);
gluTessBeginContour(tessobj); //定义轮廓线2 顺时针矩形
gluTessVertex(tessobj, graphics1[1][0], graphics1[1][0]);
gluTessVertex(tessobj, graphics1[1][1], graphics1[1][1]);
gluTessVertex(tessobj, graphics1[1][2], graphics1[1][2]);
gluTessVertex(tessobj, graphics1[1][3], graphics1[1][3]);
gluTessEndContour(tessobj);
gluTessBeginContour(tessobj); //定义轮廓线3 顺时针矩形
gluTessVertex(tessobj, graphics1[2][0], graphics1[2][0]);
gluTessVertex(tessobj, graphics1[2][1], graphics1[2][1]);
gluTessVertex(tessobj, graphics1[2][2], graphics1[2][2]);
gluTessVertex(tessobj, graphics1[2][3], graphics1[2][3]);
gluTessEndContour(tessobj);
break;
case 2:
gluTessBeginContour(tessobj); //定义轮廓线1 逆时针矩形
gluTessVertex(tessobj, graphics2[0], graphics2[0]);
gluTessVertex(tessobj, graphics2[1], graphics2[1]);
gluTessVertex(tessobj, graphics2[2], graphics2[2]);
gluTessVertex(tessobj, graphics2[3], graphics2[3]);
gluTessEndContour(tessobj);
gluTessBeginContour(tessobj); //定义轮廓线2 逆时针矩形
gluTessVertex(tessobj, graphics2[4], graphics2[4]);
gluTessVertex(tessobj, graphics2[5], graphics2[5]);
gluTessVertex(tessobj, graphics2[6], graphics2[6]);
gluTessVertex(tessobj, graphics2[7], graphics2[7]);
gluTessEndContour(tessobj);
gluTessBeginContour(tessobj); //定义轮廓线3 逆时针三角形
gluTessVertex(tessobj, graphics2[8], graphics2[8]);
gluTessVertex(tessobj, graphics2[9], graphics2[9]);
gluTessVertex(tessobj, graphics2[10], graphics2[10]);
gluTessEndContour(tessobj);
break;
case 3:
gluTessBeginContour(tessobj);
for(i=0; i<16; i++)
gluTessVertex(tessobj, graphics3[i], graphics3[i]);
gluTessEndContour(tessobj);
break;
default:
break;
}
gluTessEndPolygon(tessobj);
}
void reshape(GLsizei w, GLsizei h)
{
GLfloat fovy = 45;
GLfloat aspect = 1;
if (0 == h) { h = 1; }
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
fovy = 45;
aspect = (GLfloat)w/h;
if (w < h)
{
fovy = fovy * (GLfloat)h / w;
}
gluPerspective(fovy, aspect, 0.1, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glTranslated(-16.0, 16.0, -90.0);
drawGraphics(0, nProperty);
glPopMatrix();
glPushMatrix();
glTranslated(16.0, 16.0, -90.0);
drawGraphics(1, nProperty);
glPopMatrix();
glPushMatrix();
glTranslated(-16.0, -16.0, -90.0);
drawGraphics(2, nProperty);
glPopMatrix();
glPushMatrix();
glTranslated(16.0, -16.0, -90.0);
drawGraphics(3, nProperty);
glPopMatrix();
glFlush();
}
void keyboard(GLubyte key, GLint x, GLint y)
{
switch(key) {
case 'p':
case 'P':
nProperty++;
nProperty = nProperty%5;
nTessInsectCount = 0;
glutPostRedisplay();
break;
case 27:
exit(0);
break;
default:
break;
}
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);
glutInitWindowSize(500, 500);
glutInitWindowPosition(0, 0);
glutCreateWindow(szCaption);
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}