比较常用的地形生成算法有三种:
四叉树算法,GeoMipmap算法,移动立方体算法
目前市面游戏采用的方案基本都是以这三种算法为基础实现的,下面依次进行介绍
四叉树算法
很经典的算法,在没有GPU的时代就已经出现了,原始算法是纯cpu的实现,不过在现在已经可以将其实现为GPU-Driven的形式。
这里主要介绍CPU上的实现,GPU实现将在之后进行单独的介绍。
四叉树,顾名思义,就是每个根节点最多有四个孩子的树,其结构能够很好的表现平面的分化。
算法简介:
初始化时为一个根节点
视点靠近时四分
每个节点对应一个网格
分化公式:
距离/节点边长>CL CL为自己设置的一个值,CL越大,越容易分化
此外,也可以根据屏幕占比进行分化
这里也可以进行一个优化,预处理生成一份陡峭度数据去干预分化,越复杂的地形越易分化,如果地形没有任何起伏,那么完全不分化也是没问题的。
下图展示了动态的分化过程
到这里,基本的思路就已经讲完了,但还需要解决一个问题,也就是“裂缝”,见下
如上图所示,由于不同分化层级网格毗邻处顶点数不一致,采样精度不同,就会导致漏面的情况
在经典算法中,是这么解决的:
将网格分为5个部分,其中上下左右四个部分会根据邻居节点的分化程度去控制网格的生成方式,如下所以,如果邻居的分化程度较低,则会去掉一些顶点,使边界顶点正好对齐来保证采样精度一致。
此外,也要保证相邻节点的lod层级最多相差1,在根据公式分化后还要依次检测每个节点的邻居是否存在分化程度比自身高两个层级的节点,是的话要强制分化该节点。
这个算法其实不是很好,目前也没看到哪个游戏采用这种方式。
解决裂缝的方式还有很多,下面主要介绍三种
1.强制对齐
2.向下生成一圈外围网格
3.边界处按最密进行分化
最后效果如下
至此,四叉树算法就介绍完了,之后的实现也是采用的此算法。
此外,unity也是采用的此算法,但老版unity没有采用GPUInstance,导致dc奇多,新版本虽然进行了优化,但由于unity本身不开源,无法进行定制开发,所以还是不推荐。
下面两种算法我没有进行具体的实现,所以只是简介。
GeoMipmap算法
此算法是虚幻4采用的算法
大致思路为:
将地图分为固定数量的mesh,每个mesh根据lod生成不同精度的网格
见下图,lod为0时生成完整网格,lod为1时会把黑色的顶点去掉,用剩余的白色顶点去生成网格,这个过程是完全在GPU进行的。
移动端的话基本就采用的上面的两种方式。
下面对比一下两种方式:
四叉树算法的网格近多远少,且能通过GpuInstance进行优化,dc大概在10左右,面数在6w左右。当距离较远时,极端情况只有一个mesh。由于地形一般包含多种植被,如沼泽,沙漠,森林,且移动端由于性能限制最多也只能采样3次,所以1个mesh时会不能很好的表现整个地形面貌。但可以通过烘培一个低模,远处用低模,近处用四叉树的方式优化。
Geomipmap网格固定,dc固定,大概在40-60左右,顶点数在2w左右。距离较远时,Mesh数量固定,纹理采样不受影响。但为了减dc,一般也会烘培一个低模。
最后总结一下,unity建议自己写四叉树,虚幻直接用自带的geomipmap
移动立方体算法
上面的两种算法都存在一个缺陷,由于是基于二维高度图,所以不能表现洞穴,地洞地形。
而移动立方体算法则解决了这个问题。无人深空中即采用了此算法。
该算法将场景看成由无限多个立方体组成。
当立方体足够小时,可以近似将穿过该立方体的面看做标准曲面
曲面方程表示为:
F(x,y,z)=a0+a1x+a2y+a3Z+a4xy+a5yz+a6xz+a7xyz
立方体存在8个顶点,每个顶点都有一个状态,空气中,土壤中
由0和1表示。8个顶点可以存在一个int中,如:00011001
如果为边界,则表示需要生成网格
一共有2的8次方,即256种情况
除去对称的情况,归纳为15种,这里归为15种只是为了好分析,实际编码时仍需256种情况单独考虑。针对每种情况,将网格生成方式存在一个查找表中。
这种算法存在一个二义性问题,如图所示,假设黑点为在土地中,则下面两种生成方式均可满足。
这里假设某一面所在的平面方程为z=z0,
代入式F(x,y,z)=a0+a1x+a2y+a3Z+a4xy+a5yz+a6xz+a7xyz可以得到:
b0+b1x+b2y+b3xy=C0
两边除xy即可得到一个双曲线方程
求出该双曲线两条渐近线的交点,根据正负即可决定最终该如何生成。
看起来很简单,但落实到实现还有巨多坑。
其实现在看来,这个算法生成的地形其实就相当于方块变小无数倍的“我的世界”,生成的方块数是奇多的,在无人深空中,基本常态就是百万量级的顶点,所以在移动端自然就被Pass了。