本文章描述了一种三维标量场等值面的多边形曲面的创建方法。对于这类问题的一个通常的名字叫做"marching cubes"算法。这种算法即简单又高速,因为它基本上完全在查找表(lookup tables)上工作。
对于这类技术有很多应用,两个非常常用的是:
1,从医学体积数据中重建表面。例如,MRI在规则的3D网格的顶点处产生一个3D体积的样本。
2,产生一个数学标量场的3D轮廓。在这种情况下,函数在任何地方都是已知的,但是在规则的3D网格的顶点处采样。
解决方法
基本问题是通过在矩形三维网格上采样的标量场来形成等值面的近似值。给定由其顶点和每个顶点上的标量值定义的网络单元格,就需要创建最能通过该网格单元格(grid cell)表示等值面的平面切面。 等值面可能不穿过单元格,它可能切断任意一个顶点,或者它可能以许多更复杂的方式中的一种通过。每一种可能性都将由高于或低于等值面的顶点的数量来表征。如果一个顶点在等值面上方,另一个顶点在等值面下方,那么我们就知道等值面切割两个顶底之间的边。它切割边的位置将会被线性插值,切点与两个顶点之间的长度之比将于等值面值与网格单元格顶点处的值之比相同。
算法中使用的顶点和边的索引约定如下所示:
举例来讲如果顶点3在等值面之下,并且其它所有顶点的值都在等值面之上,那么我们就会创建一个切割边2,3和11的三角形的面。三角形顶点的确切的位置将分别取决与等值面的值和顶点3-2,3-0和3-7的值的关系。
让这个算法变得困难的是大量(256种)可能的组合,并且需要为每个解决方案导出一致的构面组合,以便相邻网格的构面正确的连接到一起。
算法的第一部分是使用一个表(edgeTable),它将在等值面之下的点映射到相交的边。构建一个8bit的索引,其中每个bit对应一个顶点。
cubeindex = 0;if (grid.val[0] < isolevel) cubeindex |= 1;if (grid.val[1] < isolevel) cubeindex |= 2;if (grid.val[2] < isolevel) cubeindex |= 4;if (grid.val[3] < isolevel) cubeindex |= 8;if (grid.val[4] < isolevel) cubeindex |= 16;if (grid.val[5] < isolevel) cubeindex |= 32;if (grid.val[6] < isolevel) cubeindex |= 64;if (grid.val[7] < isolevel) cubeindex |= 128;
对edgeTable进行查找将会返回一个12bit的数字,每个bit对应一条边,0表示这条边没有等值面切割,1表示这条边被等值面切割。如果没有一条边被切割,那么table将返回0,这会发生在cubeindex是0(所有的顶点都在等值面下)或者0xff(所有的顶点都在等值面上)。
使用之前只有顶点3在等值面之下的例子,cubeindex将会是00001000(8),其中edgeTable[8]=1000 0000 1100 (在下面会给出表)。它的意思是边2,3和11与等值面进行相交。
相交的交点现在通过线性插值进行计算。如果P1和P2是一条切边,并且V1和V2是每个顶点的标量值,那么相交点P的计算公式为P=P1+(isovalue?V1)(P2?P1)/(V2?V1)P=P_1 + (isovalue-V_1)(P_2-P_1)/(V_2-V_1)P=P1?+(isovalue?V1?)(P2??P1?)/(V2??V1?)
算法的最后一部分涉及从等值面与网格单元的边相交的位置形成正确的面。同样,使用了一个表(triTable),这次允许使用相同的cubeindex,但允许查找顶点序列,因为要表示网格单元中的等值面需要尽可能多的三角形面。最多需要5个三角形面(4个?)。
回到我们的例子,在之前的步骤中我们计算了和边2,3,11的交点。在triTable中的第8个元素是{3,11,2,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1}\{3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1\}{ 3,11,2,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1}这是一个特别简单的例子,因为很多情况下,表中的构面的组合不是那么明显。
另一个例子
我们假设顶点3和0是在等值面之下的,cubeindex将会是00001001(9)。在edgeTable中的第9个元素是905hex905_{hex}905hex? = 1001 0000 0101,他表示边11,8,2和0被切割,然后我们可以算出等值面和这些边的交点。
接着,9在triTable中是0,11,2,8,11,0。它对应着两个三角形构面,其中一个是0,11和2边的交点构成,另一个是8,11和0的交点构成。
网格分辨率
将值已知或可以在空间中任意地方插值的区域多边形化时,我们通常要控制网格的分辨率。这允许根据所需的平滑度或可用于显示该表面的算力来生成等值线的粗略近似。下面是用不同网格大小生成的两个"bobby分子"。
源码
//http://paulbourke.net/geometry/polygonise/
有人建议,插值应按这里所示处理,这解决了一个等值面上小裂缝的问题。
总结
Marching cube 是从体数据(volumetric data)中渲染等值面的算法。基本概念是我们可以通过立方体的八个角上的标量值来定义体素(立方体)。如果一个或多个立方体顶点的值比用户指定的等值面的值小,或者一个或多个值比用户指定的等值面的值大,那么我们知道这个cube会对等值面的绘制有所贡献。为了确定cube的哪条边和等值面相交,我们创建了三角切面,将立方体切分为等值面外的区域和等值面内的区域。将等值面边界上的所有立方体的patch连接起来,我们得到了面的表示。
补充
确定三角形顶点的法线
为了实现平滑渲染的目的,经常需要为三角面的每个顶点创建法线。其中一种方法是在平面被创建后,对共享同一个三角形的顶点的面的法线进行平均。下图右边显示了平滑的结果,左边的图像对每个顶点应用了面的单一法线。
一个常见的方法是在每个顶点使用共享顶点的多边形法线的加权平均值。权值是多边形面积的倒数,所以小的多边形有更大的权值。这个想法是,小多边形可能出现在高表面曲率的区域。
最后推荐一个很好的视频:
【双语】游戏编程挑战:生成无边的水下世界 体绘制算法 | Coding Adventure: Marching Cubes