OpenGL的镶嵌
镶嵌(tessellation)是将凹边形分割或者是凸边形相交边组成的多边形。因为OpenGL只接受凸多边形的渲染,这些非凸多边形必须在绘制前进行镶嵌。

上图分别为凹四边形、中间有洞及自交的多边形。
下载:tessellation.zip,stencilTess.zip
简介
镶嵌的基本过程是将非凸多边形的所有顶点发送至镶嵌器中而不是直接发送至OpenGL渲染管线。然后通过镶嵌器(tessellator)镶嵌多边形。最后,当镶嵌完成,镶嵌器会通过用户定义的回调函例程(callback routine)用OpenGL命令渲染镶嵌多边形。
OpenGL提供大量例程将凹多边形处理为凸多边形:
GLUtessellator* gluNewTess()
void gluDeleteTess(GLUtessellator *tess)
gluNewTess() 创建镶嵌器对象(tesselator object),gluDeleteTess()删除镶嵌器对象。如果创建失败将返回NULL指针。
void gluTessBeginPolygon(GLUtessellator *tess, void *userData)
void gluTessEndPolygon(GLUtessellator *tess)
除了用glBegin()和glEnd()程序块描述多边形的顶点,还可以使用tessellator-specific block, gluTessBeginPolygon() 和 gluTessEndPolygon()。但必须在这个块中描述非凸多边形。
void gluTessBeginContour(GLUtessellator *tess)
void gluTessEndContour(GLUtessellator *tess)
一个多边形可能有超过一个封闭的轮廓(closed contour)(闭环(closed loop)),例如一个多边形有个洞以及2个轮廓,分别为内轮廓和外轮廓。每个轮廓都必须包含在块gluTessBeginContour() 和 gluTessEndContour()中。它是嵌套在gluTessBeginPolygon() 和 gluTessEndPolygon()块中。
void gluTessVertex(GLUtessellator *tess, GLdouble cords[3], void *vertexData)
gluTessVertex()指定轮廓顶点。镶嵌器利用顶点坐标完成镶嵌。所有顶点都大致在相同的平面上。第二个参数是需要镶嵌的顶点坐标,第三个参数用来渲染的数据。它不仅包括顶点坐标,还包括颜色、法线和纹理坐标。
void gluTessCallback(GLUtessellator *tess, GLUenum type, void (*fn)())
在镶嵌步骤中,镶嵌器在准备绘制镶嵌多边形时将会调用一系列的回调函数。必须提供适当的回调函数(callback function),包含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);
示例

有三个镶嵌的例子:4个顶点的简单凹边形、含洞多边形、自交多边形(星形图)
简单凹边形

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();
注意这个多边形时逆时针旋转的,也就是说多边形的平面法向量是指向平面外的。如果是顺时针,则法向量是相反方向,你看到的是反面而不是正面。你可以通过使用gluTessNormal()指定平面法线。
void gluTessNormal(GLUtessellator *tess, GLdouble x, GLdouble y, GLdouble z)
如果指定法线向量为(0,0,1),即使是顺时针旋转,你看到的仍然是正面(假设相机观察的是默认方向,-Z)。默认的法线的值是(0,0,0),这意味着镶嵌器将会计算给定坐标和旋绕方向的法线。
含洞多边形

第二个例子是有2个内外都为逆时针的回路。它被定义在tessellate2()中。镶嵌器产生相当于OpenGL命令:1个GL_TRIANGLE_STRIP,1个GL_TRIANGLES。
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是怎么知道中间的区域是洞(未填充)。答案是下面的缠绕规律和绕数。 tessellator分配绕数的区域由多个轮廓分割。在这种情况下,有2个独立的区域:内部区域绕数是2,外部区域绕数是1。根据给定的默认绕数规则,GLU_TESS_WINDING_ODD,标有奇数的会被填充,偶数的不填充。
如果内部顺时针,则内部区域绕数为0,外部绕数仍为1。因此,内部区域是个洞(不填充),因为内部区域绕数是0不为奇数。
自交多边形

最后一个例子是星形图,自交多边形,定义在tessellator3()中。注意镶嵌器增加了两两边线相交的5个顶点,分别是v5,v6,v7,v8和v9。当镶嵌算法检测到相交时,然后提供GLU_TESS_COMBINE回调函数创建新的顶点数据,以便我们以后绘制它。
void combineCB(GLdouble newVert[3], GLdouble *neighbourVert[4],
GLfloat neighborWeight[4], void **outData);
当两条线相交时,回调函数创建新的顶点数据。它包括四个参数:
newVert[3] :类型为GLdouble的x,y,z坐标数组。它存储的是镶嵌器创建的相交顶点的位置信息。第二个参数是4个相邻顶点数组指针,即相交两条线。第三个参数是4个相邻的4个权重值的数组。权重是用来计算交点的颜色、法线和纹理坐标插补。最后一个参数是指向输出顶点数据的指针。输出数据可能不仅包括顶点坐标,还包括颜色、法线和纹理坐标。镶嵌器将会根据绘制的顶点,把输出数据传送到GLU_TESS_VERTEX 回调例程中。
下面是镶嵌器阐述的OpenGL命令;2个三角扇形和1个三角形。
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_NONZERO,允许填充所有非零区域。(GLU_TESS_WINDING_POSITIVE也可以。)
镶嵌器提供gluTessProperty() 来改变旋绕规则和其它属性。例如GLU_TESS_BOUNDARY_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);
旋绕规则和绕数
假设多个轮廓(contours)相互重叠或者嵌套,将平面分为多个区域。旋绕规则确定这些区域是在内部还是外部。所以,内部的需要填充,外部的不需要填充。

多个轮廓将封闭区域划分开来,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_GEQ_TWO: 绝对值大于等于2,则填充
下面的图片显示了基于不同旋转规则的不同内部区域(阴影),如果设置GLU_TESS_WINDING_ABS_GEQ_TWO,将不被绘制(每个区域向外的)。

参考资料
OpenGL Tessellation:http://www.songho.ca/opengl/gltessellation.html#windingrules
OpenGL的镶嵌的更多相关文章
- 【转】OPENGL基本API概述
本文信息资源来源于网络,欢迎转载!转载时请保留本文链接(http://www.shopliyang.com.cn/)! OpenGL中的gl库是核心库,glu是实用库,glut是实用工具库. gl是核 ...
- 【计算机图形学】openGL常用函数
OpenGL常用函数 glAccum 操作累加缓冲区 glAddSwapHintRectWIN 定义一组被 SwapBuffers拷贝的三角形 glAlphaFunc允许设置alpha检测 ...
- opengl常用函数
glAccum 操作累加缓冲区 glAddSwapHintRectWIN 定义一组被 SwapBuffers拷贝的三角形 glAlphaFunc允许设置alpha检测功能 glAreTex ...
- OpenGL的API函数使用手册
(一)OpenGL函数库 格式: <库前缀><根命令><可选的参数个数><可选的参数类型> 库前缀有 gl.glu.aux.glut.wgl.glx.a ...
- 【转】OpenGL相关函数库介绍
原文:http://blog.chinaunix.net/uid-20638550-id-1909182.html OpenGL 函数库相关的API有核心库(gl).实用库(glu).辅助库(aux) ...
- opengl 函数
( 7 )光栅化.象素操作函数. 像素位置 glRasterPos*() .线型宽度 glLineWidth() .多边形绘制模式 glPolygonMode() ,读取象素 glReadPixel( ...
- 【OpenGL游戏开发之二】OpenGL常用API
OpenGL常用API 开发基于OpenGL的应用程序,必须先了解OpenGL的库函数.它采用C语言风格,提供大量的函数来进行图形的处理和显示.OpenGL库函数的命名方式非常有规律.所有OpenGL ...
- 【OpenGL游戏开发之三】OpenGl核心函数库汇总
OpenGl核心函数库 glAccum 操作累加缓冲区 glAddSwapHintRectWIN 定义一组被SwapBuffers拷贝的三角形 glAlphaFunc允许设置alpha检测功能 glA ...
- opengl 实体和网格绘图函数(基础)(转)
http://blog.csdn.net/he_wen_jian/article/details/8594880 GLUT工具箱提供几种图形3维图形的函数: void glutWireSphere(G ...
随机推荐
- js验证表单大全3
2 >表单提交验证类 2.1 表单项不能为空 <scriptlanguage="javascript"> <!-- function CheckForm( ...
- SpringBoot + Thymeleaf + Validate验证
在开发业务时,不可避免的需要处理一些校验, 如果是写 if-else 这种代码去校验, 那会有一大段这样的代码.不过还好有个校验插件: javax.validation.validation-api ...
- centos添加定时任务
安装crontab: yum install crontabs 查看crontab服务状态:service crond status 手动启动crontab服务:service crond start ...
- nodejs中全栈开发框架meteor的文档
http://wiki.jikexueyuan.com/project/discover-meteor/routing.html, 这本书的源码地址: https://github.com/Dis ...
- Spark架构解析(转)
Application: Application是创建了SparkContext实例对象的Spark用户,包含了Driver程序, Spark-shell是一个应用程序,因为spark-shell在启 ...
- mysql数据库补充知识4 数据备份和pymysql模块
一 介绍 #注意: 批量加注释:ctrl+?键 批量去注释:ctrl+shift+?键 二 MySQL数据备份 #1. 物理备份: 直接复制数据库文件,适用于大型数据库环境.但不能恢复到异构系统中如W ...
- 构建Ruby开发环境(Windows+Eclipse+Aptana Plugin)
1.安装Ruby ①.从http://rubyinstaller.org/downloads/下载安装包:rubyinstaller-2.2.5-x64.exe,直接安装.(so easy) 2.安装 ...
- [转]c#中从string数组转换到int数组
string[] input = { "1", "2", "3", "4", "5", " ...
- ajax使用formdata 提交excel文件表单到rails解析
.modal-body .container-fluid .row .col-md-12 1.下载模板文件 = link_to '模板文件' .row .col-md-12 = form_tag '' ...
- predis操作大全
predis是php连接redis的操作库,由于它完全使用php编写,大量使用命名空间以及闭包等功能,只支持php5.3以上版本,故实测性能一般,每秒25000次读写,相信改换c语言编写的php扩展后 ...