从零开始做一个SLG游戏(二):用mesh实现简单的地形

本文详细介绍了如何在Unity3D中利用Mesh进行地形的细化和扰动处理,创建出高山、平原、水域等不同地形。通过递归细化三角形,使用Perlin噪声函数实现地形表面的随机褶皱,最终形成具有立体感的地形模型。此外,还展示了如何根据地形类型调整材质颜色和绘制不同地形的边,以及如何处理湖泊和高山的特殊效果。
摘要由CSDN通过智能技术生成

本文主要是用mesh实现简单的地形。暂时先绘制三种地形:高山、平原、水域。

首先要做的是网格的细化:

上一篇已经实现了单个六边形的绘制,实现方式是将六边形分割成6个等边三角形,然后分别绘制。

现在需要将每个三角形再次细化,将一个三角形细化为4个小三角形。

如下图:
 

细化原理如下图:
 


在上一篇文章中,封装了三角形绘制的函数:
 

  1. private void AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3)
  2.         {
  3.                 int count = triangles.Count;
  4.                 vertices.Add(v1);
  5.                 triangles.Add(count++);
  6.                 vertices.Add(v2);
  7.                 triangles.Add(count++);
  8.                 vertices.Add(v3);
  9.                 triangles.Add(count++);
  10.         }

复制代码


其中v1,v2,v3如图所示,而v4,v5,v6分别为三条边的中点。

所以有:

Vector3 v4 = Vector3.Lerp(v1, v2, 0.5f);
Vector3 v5 = Vector3.Lerp(v2, v3, 0.5f);
Vector3 v6 = Vector3.Lerp(v1, v3, 0.5f);
所以新的4个三角形分别为:(v1, v4, v6)(v4, v2, v5)(v4, v5, v6)(v3, v6, v5)

于是,新写一个递归函数用于细化(原函数保留):
 

  1.          private void AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3, int time)
  2.         {
  3.                 if (time == 0)
  4.                 {
  5.                         AddTriangle(v1, v2, v3);
  6.                 }
  7.                 else
  8.                 {
  9.                         time--;
  10.                         Vector3 v4 = Vector3.Lerp(v1, v2, 0.5f);
  11.                         Vector3 v5 = Vector3.Lerp(v2, v3, 0.5f);
  12.                         Vector3 v6 = Vector3.Lerp(v1, v3, 0.5f);
  13.                         AddTriangle(v1, v4, v6, time);
  14.                         AddTriangle(v4, v2, v5, time);
  15.                         AddTriangle(v4, v5, v6, time);
  16.                         AddTriangle(v3, v6, v5, time);
  17.                 }
  18.         }
  19.         /// <summary>
  20.         /// 绘制地形
  21.         /// </summary>
  22.         public void Draw(HexTerrian type)
  23.         {
  24.                 ……
  25.                 for (HexDirection dir = HexDirection.NE; dir <= HexDirection.NW; dir++)
  26.                 {
  27.                         Vector3 v1 = HexMetrics.corners[(int)dir];
  28.                         Vector3 v2 = HexMetrics.corners[(int)dir + 1];
  29.                         AddTriangle(center, v1, v2, 2);
  30.                 }
  31.                 UpdateMesh();
  32.         }

复制代码


其中time指的是细化次数。
 


但是,如今生成的图片还是一个六边形,与原来没有变化。原因是,没有对图片上的点做扰动处理,所以虽然生成的时候是细化了生成的,但拼在一起还是平的。

现加入扰动处理的函数:
 

  1. private Vector3 Perturb(Vector3 pos)
  2.         {
  3.                 float level = 0.5f;
  4.                 Vector3 localPos = transform.localPosition + pos;
  5.                 pos.x += level * (Mathf.PerlinNoise(localPos.x, localPos.z) - 0.5f);
  6.                 pos.y += level * (Mathf.PerlinNoise(localPos.x + 1f, localPos.z + 1f) - 0.5f);
  7.                 pos.z += level * (Mathf.PerlinNoise(localPos.x + 2f, localPos.z + 2f) - 0.5f);
  8.                 return pos;
  9.         }

复制代码


这里用的是unity自带的柏林噪声函数 Mathf.PerlinNoise(float x,float y),这个算法会根据x以及y的值生成一个随机的函数,固定的x和y,生成的随机值是固定的。所以暂时先用这个做扰动。

Mathf.PerlinNoise(float x,float y)得出的是0到1的一个值。

所以减去0.5。得到的是-0.5到0.5的一个值。

level是一个扰动的参数,扰动后,单个坐标最多偏移0.25个单位,这样显示出来的就是一个有轻微褶皱的地形图片。

将在AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3, int time)函数中绘制三角形的部分加入扰动:
 

  1.          private void AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3, int time)
  2.         {
  3.                 if (time == 0)
  4.                 {
  5.                         AddTriangle(Perturb(v1), Perturb(v2), Perturb(v3));
  6.                 }
  7.                 ………………
  8.         }

复制代码


运行后,我们得到如下的图:
 


然后再调一下材质的颜色,本游戏采用的是lowpoly风格,该风格的反射很弱,所以需要把材质上的specular hightlight的钩去掉,然后再调整一下图片的颜色:
 


有一点泥土的感觉了,暂时先这么用着吧。

下一步要解决的问题是,目前只是一个单面的六边形,将这个六边形绘制成一个棱柱,表现会更好一些,当然,棱柱的底就不画了,反正看不到。

首先写一个函数用于绘制一个矩形:
 

  1. /// <summary>
  2.         /// v3  v4
  3.         ///
  4.         /// v1  v2
  5.         /// </summary>
  6.         /// <param name="v1"></param>
  7.         /// <param name="v2"></param>
  8.         /// <param name="v3"></param>
  9.         /// <param name="v4"></param>
  10.         private void AddSquare(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)
  11.         {
  12.                 AddTriangle(v1, v3, v2);
  13.                 AddTriangle(v3, v4, v2);
  14.         }

复制代码


v1,v2,v3,v4的位置备注所示。

如果不考虑细化,那么绘制边上的面的函数就可以如下表示:
 

  1. /// <summary>
  2.         /// 绘制地形
  3.         /// </summary>
  4.         public void Draw(HexTerrian type)
  5.         {
  6.                 ……………………
  7.                 for (HexDirection dir = HexDirection.NE; dir <= HexDirection.NW; dir++)
  8.                 {
  9.                         Vector3 v1 = HexMetrics.corners[(int)dir];
  10.                         Vector3 v2 = HexMetrics.corners[(int)dir + 1];
  11.                         AddTriangle(center, v1, v2, 2);
  12.                         Vector3 v3 = v1 + 5f * Vector3.down;
  13.                         Vector3 v4 = v2 + 5f * Vector3.down;
  14.                         AddSquare(v1, v2, v3, v4);
  15.                 }
  16.                 ……………………
  17.         }

复制代码


接下来考虑分形的做法,先看下示意图:
 


然后是代码:
 

  1. private void AddEdge(Vector3 v1, Vector3 v2, int time)
  2.         {
  3.                 if (time == 0)
  4.                 {
  5.                         v1 = Perturb(v1);
  6.                         v2 = Perturb(v2);
  7.                         float d = 5f;
  8.                         Vector3 v3 = v1 + d * Vector3.down;
  9.                         Vector3 v4 = v2 + d * Vector3.down;
  10.                         AddSquare(v1, v2, v3, v4);
  11.                 }
  12.                 else
  13.                 {
  14.                         time--;
  15.                         Vector3 v5 = Vector3.Lerp(v1, v2, 0.5f);
  16.                         AddEdge(v1, v5, time);
  17.                         AddEdge(v5, v2, time);
  18.                 }
  19.         }

复制代码


因为v3,v4是通过v1,v2计算得来的,所以输入就只需要v1,v2就行了。

因为点的扰动是最后计算的,所以会和六边形的边重合,手游账号买卖不会出现错位。

效果如下图:


接下来是河流和高山地块。

先定义一个地形类型的枚举:
 

  1. public enum HexTerrian
  2. {
  3.         Water,
  4.         Plain,
  5.         Mountain,
  6. }

复制代码


然后在HexCell类中加入地形类型,用于表示当前地形的类型,同时定义一个材质的数组用于保存不同地形对应的材质,并在绘制函数中加入地形参数:
 

  1. public class HexCell : MonoBehaviour {
  2.         ……
  3.         public HexTerrian terr;
  4.         public Material[] materials;
  5.         ……
  6.         public void Draw(HexTerrian type)
  7.         {
  8.                 terrianType = type;
  9.                 GetComponent<Renderer>().material = materials[(int)type];
  10.         ……
  11. }

复制代码


将做好的3个地形材质拖到数组上,记得和枚举一一对应。

先处理湖泊地形,湖泊和平原相比,只在于水平面将会比平原低一点:
 

  1. ………
  2.         private readonly float deep = 5;//方块厚度
  3.         private readonly float waterLevel = 2;//水平面离地距离
  4.         public int fractalTime = 3;//细化次数
  5. ………
  6.                 for (HexDirection dir = HexDirection.NE; dir <= HexDirection.NW; dir++)
  7.                 {
  8.                         Vector3 v1 = HexMetrics.corners[(int)dir];
  9.                         Vector3 v2 = HexMetrics.corners[(int)dir + 1];
  10.                         switch (type)
  11.                         {
  12.                                 case HexTerrian.Water:
  13.                                         if (dir == HexDirection.NE)
  14.                                         {
  15.                                                 center -= waterLevel * Vector3.up;
  16.                                         }
  17.                                         v1 -= waterLevel * Vector3.up;
  18.                                         v2 -= waterLevel * Vector3.up;
  19.                                         break;
  20.                                 case HexTerrian.Plain:
  21.                                         break;
  22.                         }
  23.                         AddTriangle(center, v1, v2, fractalTime);
  24.                         AddEdge(v1, v2, fractalTime);
  25.                 }
  26. ……

复制代码


边缘六个面绘制的时候也要记得短一些:


 

  1. private void AddEdge(Vector3 v1, Vector3 v2, int time)
  2.         {
  3.                 if (time == 0)
  4.                 {
  5.                         v1 = Perturb(v1);
  6.                         v2 = Perturb(v2);
  7.                         float d = (terrianType == HexTerrian.Water) ? (deep - waterLevel) : deep;
  8.                         Vector3 v3 = v1 + d * Vector3.down;
  9.                         Vector3 v4 = v2 + d * Vector3.down;
  10.                         AddSquare(v1, v2, v3, v4);
  11.                 }
  12.                 ……
  13.         }

复制代码


接下来绘制高山,对于高山,先简单处理一下:将顶点提高一些,然后细化一下。
 

  1. ……
  2. case HexTerrian.Mountain:
  3. if (dir == HexDirection.NE)
  4. {
  5.         center += 5f * Vector3.up;
  6. }
  7. break;
  8. ……

复制代码


然后测试一下地形效果,随机地形在这篇文章就不做了,主要是把所有的地形都显示出来,所以随便写一下吧:

  1.                 foreach (HexCell c in hexCells)
  2.                 {
  3.                         int height = (c.Pos.x + c.Pos.y) % 3;
  4.                         c.Draw((HexTerrian)height);
  5.                 }

复制代码


于是,得到了封面上的效果图:
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值