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 ...
随机推荐
- 数据库时间类型和 util 包下时间类型转换
Java 中的类型 1. java.sql 包下给出三个数据库相关的日期时间类型,分别是 java.sql.Date, 表示日期,只有年月日,没有时分秒. java.sql.Time, 表示时间, 只 ...
- Qt 如何像 VS 一样创建项目模版?
qt 存储模版路径位置:Qt\Qt5.9.5\Tools\QtCreator\share\qtcreator\templates\wizards 在里面随意复制一个模版,修改三项即可在 qt 中显示该 ...
- C语言中的const,free使用方法具体解释
注意:C语言中的const和C++中的const是有区别的,并且在使用VS编译測试的时候. 假设是C的话.请一定要建立一个后缀为C的文件.不要是CPP的文件. 由于.两个编译器会有区别的. 一.C语言 ...
- C# 函数4
//数据库 public class GF_DA { /// <summary> /// 执行SQL语句 sConnStr 连接字符串,sq ...
- HBA卡 和 RAID卡
HBA卡: 只从HBA的英文解释HOST BUS ADAPTER(主机总线适配器)就能看出来,他肯定是给主机用的,一般HBA就是给主机插上后,给主机扩展出更多的接口,来连接外部的设备.大多数讲到HBA ...
- python中的关键字global和nonlocal
知识点: global将一个变量变为全局变量 nonlocal改变最近的变量,又不是全局作用. 1.global 在python中,当引用一个变量的时候,对这个变量的搜索按找本地作用域(Local). ...
- gzframework开发记录
修改窗体权限: 重写方法 修改操作按钮名称 全部自定义增加操作按钮: 插入控件顺序: InsertAfterButton插入到指定控件后面 InsertBeforeButton插入到指定控件前面 公共 ...
- Raspberry Pi开发之旅-实现云平台监控
一.基本设置 1 sudo raspi-config 移动到第五项“Enable Camera”,回车进入,按tab键切换到“Enable”回车确认.回到主菜单,tab键切换到“Finish”回车确认 ...
- 【Tech】SQL Server2008使用笔记
1.64位win7系统报错“未在本地计算机上注册 Microsoft.ACE.OLEDB.12.0 ”. 解决方法:从这个网址下载和安装一个驱动http://www.microsoft.com/zh- ...
- java 程序的使用
Java程序可以在任何安装有Java平台的系统上运行,运行的时候语法如下: java -jar <program.jar> -jar这个参数是必须有的,后面跟你的java程序,例如我们 ...