网格化是将凹多边形或有边相交的多边形划分成凸多边形。由于openGL渲染时只接受凸多边形,这些非凸多边形在渲染之前必须先被网格化。

第一行中第一个图形是4条边的凹多边形,第二个图形中间有个洞,第三个图形有相交的边

下载: tessellation.zip, stencilTess.zip


概述

网格化基本的步骤是将所有非凸多边形的顶点坐标发送到网格器而不是直接发送到OpenGL渲染管线中,然后网格器将所有多边形网格化。最后,网格化工作完成以后,网格器使用用户定义的回调模式调用实际的OpenGL命令来渲染网格化后的多边形。

OpenGL提供了一系列的将凹多边形处理成凸多边形的模式。

[cpp] view plaincopy
  1. GLUtessellator* gluNewTess()
  2. void gluDeleteTess(GLUtessellator *tess)

gluNewTess()创建一个网格器对象,gluDeleteTess()删除指定的网格器对象。如果创建失败,将会返回NULL。

[cpp] view plaincopy
  1. void gluTessBeginPolygon(GLUtessellator *tess, void *userData)
  2. void gluTessEndPolygon(GLUtessellator *tess)

不像之前使用glBegin()和glEnd()块来描述多边形的顶点,你需要使用特殊的网格器块,gluTessBeginPolygon()和gluTessEndPolygon().你必须在这个块中描述非凸多边形。

[cpp] view plaincopy
  1. void gluTessBeginContour(GLUtessellator *tess)
  2. void gluTessEndContour(GLUtessellator *tess)

一个多边形可能有多个封闭的轮廓线(封闭的回路),例如,一个有洞的多边形有2条回路,里面一条外面一条。每一个回路必须被gluTessBeginContour()和gluTessEndContour()包含。这是在gluTessBeginPolygon()和gluTessEndPolygon()中内嵌的块。

[cpp] view plaincopy
  1. void gluTessVertex(GLUtessellator *tess, GLdouble cords[3], void *vertexData)

gluTessVertex()指定了回路的顶点。网格器使用这些顶点坐标执行网格化。所有的顶点需要位于同一个面中。第二个参数是网格化需要的顶点坐标,第三个参数是实际用来渲染的坐标,它可能不仅是顶点坐标,也可能是颜色坐标,法向量坐标,纹理坐标。

[cpp] view plaincopy
  1. void gluTessCallback(GLUtessellator *tess, GLUenum type, void (*fn)())

在网格化的过程中,当OpenGL准备渲染网格化后的多边形形时网格器会调用一系列的回调模式。你必须指定适当的回调函数,这些回调函数包括实际渲染多边形的OpenGL命令,如glBegin(),glEnd(),glVertex*()等。

下面一小段代码显示了网格化的使用方法:

[cpp] view plaincopy
  1. // 创建网格器
  2. GLUtesselator *tess = gluNewTess();
  3. // 注册回调函数
  4. gluTessCallback(tess, GLU_TESS_BEGIN,   beginCB);
  5. gluTessCallback(tess, GLU_TESS_END,     endCB);
  6. gluTessCallback(tess, GLU_TESS_VERTEX,  vertexCB);
  7. gluTessCallback(tess, GLU_TESS_COMBINE, combineCB);
  8. gluTessCallback(tess, GLU_TESS_ERROR,   errorCB);
  9. // 描述非凸多边形的顶点
  10. gluTessBeginPolygon(tess, user_data);
  11. // 第一条回路
  12. gluTessBeginContour(tess);
  13. gluTessVertex(tess, coords[0], vertex_data);
  14. ...
  15. gluTessEndContour(tess);
  16. // 第二条回路
  17. gluTessBeginContour(tess);
  18. gluTessVertex(tess, coords[5], vertex_data);
  19. ...
  20. gluTessEndContour(tess);
  21. ...
  22. gluTessEndPolygon(tess);
  23. // 处理完成后删除网格器
  24. gluDeleteTess(tess);


例子:

上图中有3个网格器的例子,左边是一个简单的有4个顶点的凹多边形,中间是一个有洞的多边形,右边是一个有相交边的多边形(星形)。

下载源文件和可执行文件:tessellation.zip


一个简单的凹多边形的例子

凹多边形的网格化

这个轮廓的网格化模式定义在tessellate1()函数中。OpenGL网格器定义和多种基本的图元类型来高效地执行网格化:GL_TRIANGLE, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP and GL_LINE_LOOP。本例中,网格器是使用GL_TRIANGLE_FAN将多边形划分成三角形。

你可以将实际的OpenGL记录在回调函数中,这些函数在网格化的过程中会被执行。下面的代码是网格器生成的并记录在回调函数中。在源文件中查看回调函数式如何记录的。

[cpp] view plaincopy
  1. glBegin(GL_TRIANGLE_FAN);
  2. glVertex3dv(v3);
  3. glVertex3dv(v0);
  4. glVertex3dv(v1);
  5. glVertex3dv(v2);
  6. glEnd();

注意多边形的环绕方式是逆时针的(CCW),这样多边形的表面法向量是指离多边形的。如果环绕方式是顺时钟的(CW),表面法向量则是指向多边形的,这样你看到的是多边形的背面而不是前面。你可以使用gluTessNormal()来明确地指明表面法向量。

[cpp] view plaincopy
  1. void gluTessNormal(GLUtessellator *tess, GLdouble x, GLdouble y, GLdouble z)

如果你用(0,0,1)指定了法向量,那么你将一直看到多边形的前面即使环绕方式是顺时针的(我假设照相机是指向默认的方向,即-Z轴)。默认的法向量的值是(0,0,0),这意味着网格器会更加给定的顶点和环绕方式计算法向量。


有洞的多边形

第二个例子有两个逆时针的回路,里面一个外面一个。它被定义在tessellate2()中。网格器一个三角形一个三角形地生成实际的OpenGL命令。

[cpp] view plaincopy
  1. glBegin(GL_TRIANGLE_STRIP);
  2. glVertex3dv(v1);
  3. glVertex3dv(v5);
  4. glVertex3dv(v0);
  5. glVertex3dv(v4);
  6. glVertex3dv(v3);
  7. glVertex3dv(v7);
  8. glVertex3dv(v2);
  9. glVertex3dv(v6);
  10. glVertex3dv(v5);
  11. glEnd();
  12. glBegin(GL_TRIANGLES);
  13. glVertex3dv(v5);
  14. glVertex3dv(v1);
  15. glVertex3dv(v2);
  16. glEnd();

你可能会疑惑OpenGL网格器是怎么知道中间的区域是洞的(不需要填充)。答案是环绕的规则和环绕的数字。网格器给被回路划分的多个区域分配了环绕的数字。在本例中,有2个单独的区域:里面区域被分配的环绕数字是2,外面区域是1。使用默认的环绕规则,GLU_TESS_WINDING_ODD,被奇数标记的区域会被填充,被偶数标记的区域则不会被填充。

如果内部的轮廓是顺时针方向的,那么现在内部区域的环绕数字是0,外面的是1。因此,内部的区域依然是一个洞(不填充)因为内部区域的环绕数字不是偶数,而是0。环绕的规则在下面会讲到。


有边相交的多边形

最后一个例子是一个星型,它有边相交并被定义在tessellator3()。注意网格器添加了5个额外的顶点并插入了2条边,v5,v6,v7,v8和v9。当网格器算法检测到插入边时。GLU_TESS_COMBINE回调函数必须被提供来创建新的顶点,我们稍后会绘制它。

[cpp] view plaincopy
  1. void combineCB(GLdouble newVert[3], GLdouble *neighbourVert[4],
  2. GLfloat neighborWeight[4], void **outData);

当两条边插入时回调函数用来创建新的顶点。它需要4个参数:newVert[3]是x,y,z坐标的一个数组,这是网格器检测到的新插入的顶点的坐标。第二个参数指向4个邻接顶点的坐标的指针。第3个参数是4个邻接顶点的权重因素。这个权重值会被用来计算插入点的颜色,法向量,或者纹理坐标。最后一个参数是指向输出顶点数据的指针。输出数据可能不止包含顶点坐标,也包含颜色,法向量,纹理坐标。玩个器将会将输出数据传送到GLU_TESS_VERTE回调模式中来绘制插入的顶点数据。

下面的OpenGL命令是使用网格器生成的:

[cpp] view plaincopy
  1. glBegin(GL_TRIANGLE_FAN);
  2. glVertex3dv(v9);
  3. glVertex3dv(v0);
  4. glVertex3dv(v5);
  5. glVertex3dv(v7);
  6. glVertex3dv(v8);
  7. glVertex3dv(v2);
  8. glEnd();
  9. glBegin(GL_TRIANGLE_FAN);
  10. glVertex3dv(v6);
  11. glVertex3dv(v1);
  12. glVertex3dv(v7);
  13. glVertex3dv(v5);
  14. glVertex3dv(v3);
  15. glEnd();
  16. glBegin(GL_TRIANGLES);
  17. glVertex3dv(v4);
  18. glVertex3dv(v8);
  19. glVertex3dv(v7);
  20. glEnd();

在这个多边形中考虑环绕规则和环绕数字。多边形被分成了6个单独的区域,环绕的数字如图所示:

使用GLU_TESS_WINDING_ODD规则,中间区域不会被填充因为它的环绕数字是偶数。我们需要另外一个环绕规则,这样我们可以填充环绕数字为奇数和偶数的区域。这种情况下则应该使用GLU_TESS_WINDING_NODZERO环绕规则,它会填充环绕数字非0的区域。(GLU_TESS_WINDING_POSITIVE同时也工作)

网格器提供gluTessProerty()函数改变环绕规则和其他属性,例如GLU_TESS_BOUNDARY_ONLY,glu_TESS_TOLERANCE。更多环绕规则的内容查看下面。


环绕规则和环绕数字

假设多个轮廓线,相互之间重叠或嵌套,将平面划分成了多个区域。环绕规则决定了哪些区域是里面还是外面。这样里面会被填充,外面不会被填充。

对每个被多条轮廓线封闭的区域,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系列教程之十一:OpenGL网格化相关推荐

  1. Java NIO系列教程(十一) Pipe

    转载自  Java NIO系列教程(十一) Pipe 原文链接     作者:Jakob Jenkov     译者:黄忠       校对:丁一 Java NIO 管道是2个线程之间的单向数据连接. ...

  2. docker 打包镜像_Spring Boot2 系列教程(四十一)部署 Spring Boot 到远程 Docker 容器

    不知道各位小伙伴在生产环境都是怎么部署 Spring Boot 的,打成 jar 直接一键运行?打成 war 扔到 Tomcat 容器中运行?不过据松哥了解,容器化部署应该是目前的主流方案. 不同于传 ...

  3. 织梦仿站系列教程第十一讲——幻灯片的制作

    织梦仿站系列教程第十一讲--幻灯片的制作 我们还是顺着做吧,这一讲说说幻灯片代码的制作. 首先打开index.htm文档,找到幻灯片部分的代码.dedecms教程就是下面这段代码: <DIV i ...

  4. 织梦仿站系列教程第二十一讲——封面页制作(四)

    织梦搜索提示关键词不少于2个字节 织梦仿站系列教程第二十一讲--封面页制作(四) 看拳击在线的代码,晕,最新新闻和热门新闻竟然是JS调用,我们只好找到这个JS文件,转换成HTML,然后修改. 将如下代 ...

  5. Provisioning Services 7.8 入门系列教程之十一 通过版本控制自动更新虚拟磁盘

    续Provisioning Services 7.8 入门系列教程之十 通过类自动更新虚拟磁盘 从前两的两种更新方式可以看出,它们有一个共同的特点,即需要产生(复制)完成的虚拟磁盘副本,然后进行相关的 ...

  6. 推荐系统系列教程之十一:那些在Netflix Prize中大放异彩的推荐算法

    编者按:之前推出了<推荐系统系列教程>,反响不错,前面已经推出了十期,今天按约推出第十一期:那些在Netflix Prize中大放异彩的推荐算法.希望朋友们多点"在看" ...

  7. 现代OpenGL系列教程(一)---旋转的三角形

    [写在前面] 本章主要内容: 1.基本的矩阵变换 2.基本的OpenGL Buffer Object 3.基本的GLSL(OpenGL着色语言)  [正文开始] 在正式开始学习之前,我必须要说明的是: ...

  8. Linux (x86) Exploit 开发系列教程之十一 Off-By-One 漏洞(基于堆)

    Off-By-One 漏洞(基于堆) 译者:飞龙 原文:Off-By-One Vulnerability (Heap Based) 预备条件: Off-By-One 漏洞(基于栈) 理解 glibc ...

  9. mybatis依赖_Spring Boot2 系列教程(二十一)整合 MyBatis

    前面两篇文章和读者聊了 Spring Boot 中最简单的数据持久化方案 JdbcTemplate,JdbcTemplate 虽然简单,但是用的并不多,因为它没有 MyBatis 方便,在 Sprin ...

最新文章

  1. Struts2+JSON特别让人恶心的一个问题
  2. 连续处理函数reduce
  3. tilemap 导入unity_教程|Unity中使用Tilemap快速创建2D游戏世界
  4. Hystrix配置参数查找方式
  5. poj-1659-Frogs Neighborhood-(图论-是否可图)
  6. 穷人最缺少的是什么?
  7. PyMC3和Lasagne构建神经网络(ANN)和卷积神经网络(CNN)
  8. 在emIDE中创建STM32项目
  9. Linux系统编程 --- 系统调用
  10. PHP工程师学习计划
  11. ssh: connect to host gitee.com port 22: Connection timed out fatal: Could not read from remote repos
  12. 矩阵乘法 c/c++代码
  13. promise语法与用法、this指向和this指向修改
  14. awflasher的Vplayer 2.1 ( FLV Player )
  15. 深夜加油站遇到苏格拉底
  16. 海通股票交易接口查询当日成交c++源码分享
  17. 用递归及非递归方式实现树状结构的遍历函数
  18. top.layer.open()是什么东西
  19. 写给不想做OJ题的C++学习者
  20. StackFrame

热门文章

  1. 【光学】基于matlab GUI光栅条纹投影生成【含Matlab源码 2118期】
  2. pytorch - K折交叉验证过程说明及实现
  3. 斯图金是如何发明电磁铁的?
  4. 无领导群面中,有哪些评分标准?(二)
  5. 北科大计算机实践报告,计算机应用实践报告北科大.doc
  6. 儿童场景英语品牌“麦禾教育”完成天使轮融资,清科资管领投
  7. 三字棋游戏的的设计和代码
  8. 组装电脑基础知识之电源
  9. 帝国の炮王的陨落-----张泽宇
  10. 简单总结Unity使用AssetDatabase编辑器资源管理