【计算机图形学基础】8数据结构
该系列为阅读书籍Fundamentals Of Computer Graphics所做的笔记。
本篇对应书中第十二章 Data Structures for Graphics。
主要讲几种比较实用的数据结构:网格、空间数据、场景图形。
三角网格
每个顶点的数据通常要包含:位置、颜色、法向量、反射率等。
网格拓扑
The idea that meshes are surface-like can be formalized as constraints on the mesh topology—the way the triangles connect together, without regard for the vertex positions.
喵喵喵?
首先要通俗地讲一下啥是流形(manifold)。
quora上有解释如下:
A manifold in a three dimensional space can be imagined as a surface. More generally, a manifold embedded in $n -1$ dimensional euclidean space locally looks like a $n−1$-dimensional vector space. For example Earth (a big sphere) is a big manifold embedded in 3-dimensional space. But, we as tiny entities living on its surface can only see flat 2-dimensional land. So locally at every point on a sphere, it looks like a 2-dimensional plane.
Curves in 3-dimensional space are also manifolds. But, they locally look like 1-dimensional vector space(a line). To be a little more precise, a manifold embedded in n-dimensional euclidean space locally looks like k-dimensional vector space $(k \lt n)$ at every point on it.
A manifold has a dimension.
A 2-dimensional manifold is a surface. It could also be a union of several surfaces, too. But I’ll assume the manifolds we’re talking about are connected for the rest of this explanation.
A 1-dimensional manifold is a curve.
A 0-dimensional manifold is a point.
很多算法都假设网格是流形的。为了避免程序崩溃,程序会检查以下两点来确保网格是流形:
- 每条边只被两个三角形共享
- 每个顶点周边都环绕有一连串不间断的三角形(一个接一个,不间断)
由于有时候我们游戏中的表面并不是闭合的,也就是相当于表面是有边缘的,这并不符合上面的规定,所以我们把这规定松动一下:
- 每条边被一个或者两个三角形使用
- 每个顶点都被连接到一个边练边的三角形集合(Every vertex connects to a singl edge-connected set of triangles.)
最后一点要注意的是三角形的正反面,或者叫内外侧,由顶点的顺序决定。逆时针方向(counterclockwise order)为正面,反之为反面(可能某些另外的系统定义会反过来)。一个连接网格(connected mesh)要保证上面所有相邻的三角形都是一个转向的。在两个转向一致的共享同一条边的三角形中,构成这条边的两个点在两个三角形中出现的次序是相反的。
Indexed Mesh Storage
为了省空间,一般采用上图中右边那种方法:每个三角形只储存顶点的索引,或者说指针。
代码可以是这么写:
1 | class Vertex { |
或者可以这么定义一个网格:
1 | class IndexedMesh { |
Triangle Strips and Fans
讲两种组织方式,在OpenGL编程里可以看到。
使用strips和fans两种组织方法可以更有效率。
fan方法以顶点0为共用顶点。
strip通常用来组成带♂状♂物。顺序是(0, 1, 2)(2, 1, 3)(2, 3, 4)(4, 3, 5)。总之就是对这个带状物上面一条边的顶点遍历,对找没绘制过的从这个点开始的逆时针构成的三角形。
Data Structures for Mesh Connectivity
索引网格、带、扇都是挺不错的紧凑网格结构。但是这些结构很难更改网格。为了能够高效地更改网格数据,需要用更复杂的结构。目的是解决这些问题:
- 给出一个三角形,它的三个相邻的三角形是哪三个
- 给出一个边,哪两个三角形在用它
- 给出一个顶点,哪些面在用它
- 给出一个顶点,哪些边在用它
最简单粗暴的方法就是直接定义三角形、边、顶点。
1 | class Triangle{ |
The Triangle-Neighbor Structure
三角形包含它的顶点以及它相邻三个三角形的指针。顶点包含使用它的任意一个三角形。
1 | class Triangle { |
在数组Triangle.neighbor中,第k个三角形与本三角形共享由顶点数组中的第k和第k+1个顶点所构成的边。这样构造出来的网格的结构为:
1 | class Mesh{ |
如果$v$是三角形$t$的第$k$个顶点,那么$v$沿着逆时针方向的下一个毗邻三角形就是t.tNbr[k]。
对于给出的一个顶点v,可以这么顺时针遍历出它所有的毗邻三角形:
1 | function TrianglesOfVertex(v): |
思路是先找到信息中的一个毗邻三角形,再从这个三角形的毗邻中寻找未被遍历过的、公用定点v的三角形性。由于判断了符合条件的毗邻三角形是否已经被选入结果集,所以这个遍历算法是维持着同一方向的(顺时针或逆时针)。
边翼结构
把关系信息储存在边上而不是三角形面上。
后面各种讲网格储存方式的,由于暂时在开发中用不上,所以先不看,待以后有机会涉及到光栅化了,再来看也不迟。
Scene Graphs
大多数场景组织物体都是通过使用scene graph,采用继承(hierachical)的方式。
假设有这么一个铰链钟摆,分为上下两个部分。现在要求它们转过一定角度后的位置。首先设钟摆的上半部分转交为$\theta$,并产生了位移。假设钟摆上下两部分的衔接点为b。
则上半部分可以表示为:
$M_1 = \text{rotate}(\theta)$
$M_2 = \text{transform(p)}$
$M_3 = M_2 M_1$
把$M_3$应用到上半部分的所有点上。
考虑下半部分的时候,由于有衔接点b,所以可以认为下半部分继承于上半部分。因故下半部分的运动可以考虑为自身的运动和上半部分运动的复合。
现在假设下半部分运动角度$\Phi$。
那么下半部分:
$M_a = \text{rotate}(\Phi)$
$M_b = \text{translate}\text{(b)}$
$M_c = M_b M_a$
$M_d = M_3 M_d$
应用$M_d$到下半部分钟摆的所有点。
transform中,步骤先的,做乘法的时候其矩阵反而在后面
结论是,子项的运动可以通过和父项的运动的矩阵相乘,来得到结果。
假设有一艘渡船,上面有一辆可以自由行驶的车。假设渡船相对于世界的运动矩阵(船的本地运动矩阵)为$M_0$,车相对于船的运动(车的本地运动矩阵)为$M_1$。那么车相对于世界的运动矩阵为$M_1 M_0$。