镶嵌(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个顶点的简单凹边形、含洞多边形、自交多边形(星形图)

下载: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();

注意这个多边形时逆时针旋转的,也就是说多边形的平面法向量是指向平面外的。如果是顺时针,则法向量是相反方向,你看到的是反面而不是正面。你可以通过使用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_ONLYGLU_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的镶嵌的更多相关文章

  1. 【转】OPENGL基本API概述

    本文信息资源来源于网络,欢迎转载!转载时请保留本文链接(http://www.shopliyang.com.cn/)! OpenGL中的gl库是核心库,glu是实用库,glut是实用工具库. gl是核 ...

  2. 【计算机图形学】openGL常用函数

    OpenGL常用函数   glAccum 操作累加缓冲区   glAddSwapHintRectWIN 定义一组被 SwapBuffers拷贝的三角形   glAlphaFunc允许设置alpha检测 ...

  3. opengl常用函数

    glAccum 操作累加缓冲区   glAddSwapHintRectWIN 定义一组被 SwapBuffers拷贝的三角形   glAlphaFunc允许设置alpha检测功能   glAreTex ...

  4. OpenGL的API函数使用手册

    (一)OpenGL函数库 格式: <库前缀><根命令><可选的参数个数><可选的参数类型> 库前缀有 gl.glu.aux.glut.wgl.glx.a ...

  5. 【转】OpenGL相关函数库介绍

    原文:http://blog.chinaunix.net/uid-20638550-id-1909182.html OpenGL 函数库相关的API有核心库(gl).实用库(glu).辅助库(aux) ...

  6. opengl 函数

    ( 7 )光栅化.象素操作函数. 像素位置 glRasterPos*() .线型宽度 glLineWidth() .多边形绘制模式 glPolygonMode() ,读取象素 glReadPixel( ...

  7. 【OpenGL游戏开发之二】OpenGL常用API

    OpenGL常用API 开发基于OpenGL的应用程序,必须先了解OpenGL的库函数.它采用C语言风格,提供大量的函数来进行图形的处理和显示.OpenGL库函数的命名方式非常有规律.所有OpenGL ...

  8. 【OpenGL游戏开发之三】OpenGl核心函数库汇总

    OpenGl核心函数库 glAccum 操作累加缓冲区 glAddSwapHintRectWIN 定义一组被SwapBuffers拷贝的三角形 glAlphaFunc允许设置alpha检测功能 glA ...

  9. opengl 实体和网格绘图函数(基础)(转)

    http://blog.csdn.net/he_wen_jian/article/details/8594880 GLUT工具箱提供几种图形3维图形的函数: void glutWireSphere(G ...

随机推荐

  1. 小白学linux命令

    小白是景女神全栈开发股份有限公司的一名财务实习员工,经过3个月的实习期,小白是过五关斩六将啊!终于成为了公司的一名正式员工,而且收到了景总亲自发来贺喜的邮件:“欢迎你加入大家庭,公司也本着员工全面发展 ...

  2. 常用的JS代码块收集

    /**数组去重一*/ (function (arr) { arr = arr.sort(); for (var i = 0; arr[i]; i++) { if (arr[i] === arr[i + ...

  3. 使用selenium进行密码破解(绕过账号密码JS加密)

    经常碰到网站,账号密码通过js加密后进行提交.通过burp拦截抓到的账号密码是加密后的,所以无法通过burp instruder进行破解.只能模拟浏览器填写表单并点击登录按钮进行破解.于是想到了自动化 ...

  4. Scilab 的画图函数(2)

    一幅图是由很多元素组成的. 包含图标题.x轴标签.y轴标签,刻度线等.图1给出了各个元素的一个示意图. 这些全部的元素在scilab中都是能够用代码控制的. 标题 上个笔记上介绍了用xtitle()函 ...

  5. python ipython notebook或者 jupyter notebook 的安装

    IPython Notebook使用浏览器作为界面,向后台的IPython服务器发送请求,并显示结果.在浏览器的界面中使用单元(Cell)保存各种信息.Cell有多种类型,经常使用的有表示格式化文本的 ...

  6. (扫盲)WebSocket 教程

    原文地址:http://www.ruanyifeng.com/blog/2017/05/websocket.html WebSocket 是一种网络通信协议,很多高级功能都需要它. 本文介绍 WebS ...

  7. 剑指offer 面试58题

    面试58题: 题目:翻转字符串 题:牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上.同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意 ...

  8. 函数编程——匿名函数与lambda(一)

    python允许用lambda关键字创造匿名函数. 匿名函数是因为不需要以标准的方式来声明,比如说,使用def语句. 但是,作为函数,它们也能有参数. 一个完整的lambda“语句”代表了一个表达式, ...

  9. 学习小程序第三天 WXML语言特性

    WXML语言特性     1.数据绑定   Musstache 语法 获取json中指定键值:变量名加双括号的绑定语法 如下: (1)绑定文本 注意所有组件和属性 都要小写 (2)绑定属性     ( ...

  10. Python编程-常用模块及方法

    常用模块介绍 一.time模块 在Python中,通常有这几种方式来表示时间: 时间戳(timestamp):通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量.我们运行 ...