一、前言
网格的三种处理:网格细分,网格简化,网格正则化,细分会产生更多的三角面片来让模型更加光滑,简化则相反会减少网格的三角面片数量,正则化则会让三角形面更加规则。如上图中最右边两幅图,左边的一张奶牛网格的三角面片有很多狭长的三角形,而正则化可以让它变成右边那幅图,也就是三角面片都接近于正三角形,这在计算的时候会带来很多便利。而本篇我们主要介绍网格的细分和简化这两部分。
二、网格细分—Mesh subdivision
1.Loop subdivision—Loop细分
Loop细分分为两步,第一步是增加三角形的数量,它增加三角形数量的方法非常直接,直接取三条边的中点连线就可以从一个三角形细分出4个三角形。第二步就是调整顶点的位置,从而让模型更加光滑一些,而顶点又分为新生成的顶点和原来的顶点,它们的调整策略是不同的。
新生成顶点的调整策略:只要这个新顶点不在边界上,如上图,在网格内部的白点。那么我们设白点所在的这条线的两端顶点分别为AB,另外两个顶点为CD(顺序无所谓),那么新顶点的位置应该被更新为3/8*(A+B)+1/8*(C+D),实际上就是一种加权平均,AB两点因为距离近所以对它的影响更大一些。这也就是新生成顶点的调整策略。
原来的老顶点调整策略:同样用到加权平均,只不过用了新的规则,一部分受周围老的顶点的影响,另一部分受自己影响。(1 - n*u) * original_position + u * neighbor_position_sum。
简单的说就是先确定这个老顶点(图中白点)的度 n(度:一个顶点连接几条边),再确定它临近顶点位置的总和sum,(再规定一个数u根据实际情况决定),那么就得到了上面的公式,也就是: (1-n*u)*自己的位置+u*sum,很好理解,它揭示了如果这个点连接的三角形很多,则说明这个点不那么重要,所以它调整的位置更多的是根据它周围老顶点的影响,而反之,如果它连接的三角形很少,则说明这个顶点很重要,它自己的影响要大一些,决定调整位置的权重也就大一些。
2.Catmull-Clark Subdivision(General Mesh)—Catmull-Clark细分
为什么要引入Catmull-Clark细分呢?我们前面提到的Loop细分只能细分三角形网格,而Catmull-Clark细分适用于更常规的情况,也就是它可以处理既有三角形又有四边形的网格。
在介绍之前我们先定义Catmull-Clark细分中的几个概念。
1.奇异点:度不为4的点,也就是上图中的紫色顶点
2.非四边形面:也就是上图黄色三角形标注的面
同样的Catmull-Clark细分也分为两部分,(1)引入新的顶点,(2)调整顶点的位置。
怎么引入新的顶点呢,我们对于每个面,在它们中间加入一个点,在它们的每个边上增加一个中点,再将它们连起来,如下图。
从这我们还可以得出几个性质,我们发现细分之后增加了2个奇异点,老的奇异点的度为5,新的奇异点的度为3,而且我们发现只要在非四边形面点一点,那么它一定是奇异点。但我们也发现经过一次细分后,所有的非四边形面消失了,也就是说经过一次细分后,再次细分的时候就不会增加新的奇异点了。这说明Catmull-Clark细分具有很好的性质。
又经过了2次Catmull-Clark细分后的网格
接下来就是调整顶点的操作,分为三种点,第一种是在面内新生成的点,第二种是在边中心点生成的新的顶点,第三种是老的顶点。它们分别有对应的不同的调整公式,如上图,总体来说仍然是一种加权平均的思想。
三、网格简化—Mesh simplification
网格的简化和细分同样重要,在有些情况,我们的计算性能不足以支持过多的三角形顶点的变换,在这种情况下我们就需要通过网格简化来减少顶点和三角形面片数来降低对性能的消耗。同时,在一个场景中,如果一个模型距离视线较远,那我们就不需要显示过多的层次细节,也就可以用细节层次较低的模型来减少性能消耗,这和Mipmap对纹理的优化是同一个道理,当然,纹理的层次结构的连续过度很好做,但网格的层次结构优化当今仍然是图形学正在研究的困难问题之一。
1.边坍缩—Edge Collapsing
这里介绍一种方法叫做边坍缩,也就是把几个顶点捏在一起,这样就减少了边面数,当然这里也有很多值得说的地方,比如我们怎么确定要坍缩哪条边,也就是哪条边对整个网格形体来说不太重要,以及新捏出来的点的位置应该放在哪?
而这里用到的方法叫做二次误差度量,对于新捏出的简化后的一个顶点,也就是上图中的蓝色的顶点,我们找到影响它的各个面,把该顶点与各个面的距离的平方和求出来,而在调整这个点的过程中,一定有一个位置是使得这个平方和最小的,那么这个位置也就是最优位置,那么新捏成的这个点的位置也就随之确定了。
再回到我们另外一个问题,怎么确定坍缩哪条边呢?这里同样用到二次误差度量,在一个网格中,我们对每条边都做一次二次度量误差,也就是说对每条边坍缩后对网格的整体影响做一个预计算并排序,那么开始坍缩的时候,先从那条二次度量误差最小的那条边做坍缩。
这里也有问题,那就是,我们每坍缩一条边之后,会对其它边的二次度量误差产生影响,也就是说,每坍缩一条边之后,我们的其它边坍缩后的对网格的影响要重新计算。也就是说我们需要一种数据结构,我们能瞬间取到最小值,也就是O(1),并且我们能动态的更新重新排序剩下的受影响的数据,也就是优先队列/堆。这也就支持了我们每次取二次度量误差最小的边进行坍缩,然后再取受影响之后的二次度量误差最小的边进行坍缩,然后循环往复的过程。
还有一个问题,我们想对整体的网格进行简化且不损失整体轮廓,也就是说我们想找到的是对全局的一个最优化办法,但我们实际上做的是对一条边也就是局部找最优化办法,我们在试图通过局部优化达到整体的优化,这实际上是一种贪心算法,这并不是一种能完全保证全局最优性的算法。但在实际的场景中,几乎没有特别复杂的网格使得实际优化后的模型和这种贪心算法的结果相差的特别远的情况,所以人们还在用这种方法简化网格 。
四、总结
到这里,几何部分的内容就就讲完了,我们从最开始的隐式,显式表示,到贝塞尔曲线和曲面,再到本篇的网格细分和简化都介绍了一遍,这部分和shader中的曲面细分着色器以及3ds MAX中的网格平滑操作都有很大的关系。下一篇将开启GAMES101光线追踪的部分,会涉及到很多物理学的知识,我们先从几何第三节的结尾的阴影图开始介绍,最后把鄙人所理解的通过我认为简单易懂的方式写出来,下篇见。
参考:
Lecture 12 Geometry 3_哔哩哔哩_bilibili