上一篇我们了解了该算法的大致计算过程,下面我们通过midpoint displacement算法(Diamond Step、Square step)创建地形
(一)、Diamond Step
先计算第一个Diamond Step:
结果:
解释一下:
至此第一阶段:上面的第一个Diamond Step完成。如果我们通过不断重复这些Diamond Steps,它指最终建立起来并访问地形的每个顶点。
计算多个Diamond Step:
此时地形表现形式如下:
上面目前看起来还不是随机地形,我们添加一些随机值,比如随机高度值:
结果每个点高度总是很接近最高高度,这时我们要做的是添加阻尼效果:
(二)、Square Step
Square Step:分别计算上图4个小黄点位置的高度
最后附上最终代码:
using UnityEngine;
public class CustomTerrain : MonoBehaviour {
public bool resetTerrain = true;
//改变地形随机高度数值
public float MPDheightMin = -2f;
public float MPDheightMax = 2f;
//添加阻尼值
public float MPDheightDampenerPower = 2.0f;
//阻尼值受粗糙度的影响
public float MPDroughness = 2.0f;
float[,] GetHeightMap() //这个函数每次创建新的高度图时使用
{
if (!resetTerrain)
{
return terrainData.GetHeights(0, 0, terrainData.heightmapWidth, terrainData.heightmapHeight);
}
else
return new float[terrainData.heightmapWidth, terrainData.heightmapHeight];
}
public Terrain terrain; //获取地形(物体)对象
public TerrainData terrainData;//TerrainData里有地形所有的数据
private void OnEnable()
{
terrain = gameObject.GetComponent<Terrain>(); //初始化地形数据 先获取地形再获取本身的地形数据
terrainData = Terrain.activeTerrain.terrainData;
}
public void MidPointDisplacement()
{
float[,] heightMap = GetHeightMap();//获得高度图
int width = terrainData.heightmapWidth - 1;//获得宽度
int squareSize = width;//由获得的宽度设置正方形,之所以减1,是因为要围绕网格外部创建正方形
int cornerX, cornerY;
int midX, midY;
int pmidXL, pmidXR, pmidYU, pmidYD;//Square Step变量
float heightMin = MPDheightMin;
float heightMax = MPDheightMax;
float heightDampener = (float)Mathf.Pow(MPDheightDampenerPower, -1 * MPDroughness);
//设置网格的(大正方形的已知的四个顶点数值),即地形的四个角的随机高度
//之所以注释掉是减少地形四角的随机性
//heightMap[0, 0] = UnityEngine.Random.Range(0f, 0.2f);
//heightMap[0, terrainData.heightmapHeight - 2] = Random.Range(0f, 0.2f);
//heightMap[terrainData.heightmapWidth - 2, 0] = Random.Range(0f, 0.2f);
//heightMap[terrainData.heightmapWidth - 2, terrainData.heightmapHeight - 2] = Random.Range(0f, 0.2f);
while (squareSize > 0)
{
#region Diamnod Step
for (int x = 0; x < width; x += squareSize)//实际上是跨出一个完整的正方形以获得下一个值
{
for (int y = 0; y < width; y += squareSize) //设置地形中间点的随机高度
{
cornerX = (x + squareSize);
cornerY = (y + squareSize);
midX = (int)(x + squareSize / 2.0f);
midY = (int)(y + squareSize / 2.0f);
heightMap[midX, midY] = (float)((heightMap[x, y] + heightMap[cornerX, y] + heightMap[x, cornerY]
+ heightMap[cornerX, cornerY]) / 4.0f + UnityEngine.Random.Range(heightMin, heightMax));
}
}
#endregion
#region Square Step
for (int x = 0; x < width; x += squareSize)
{
for (int y = 0; y < width; y += squareSize)
{
cornerX = (x + squareSize);
cornerY = (y + squareSize);
midX = (int)(x + squareSize / 2.0f);
midY = (int)(y + squareSize / 2.0f);
pmidXR = (int)(midX + squareSize);
pmidYU = (int)(midY + squareSize);
pmidXL = (int)(midX - squareSize);
pmidYD = (int)(midY - squareSize);
//下面这句话是如果点正好处于地形的边缘,我们不往下进行,继续下一轮循环,阻止我们得到错误索引
if (pmidXL <= 0 || pmidYD <= 0
|| pmidXR >= width - 1 || pmidYU >= width - 1) continue;
//Calculate the square value for the bottom side
heightMap[midX, y] = (float)((heightMap[midX, midY] +
heightMap[x, y] +
heightMap[midX, pmidYD] +
heightMap[cornerX, y]) / 4.0f +
UnityEngine.Random.Range(heightMin, heightMax));
//Calculate the square value for the top side
heightMap[midX, cornerY] = (float)((heightMap[x, cornerY] +
heightMap[midX, midY] +
heightMap[cornerX, cornerY] +
heightMap[midX, pmidYU]) / 4.0f +
UnityEngine.Random.Range(heightMin, heightMax));
//Calculate the square value for the left side
heightMap[x, midY] = (float)((heightMap[x, y] +
heightMap[pmidXL, midY] +
heightMap[x, cornerY] +
heightMap[midX, midY]) / 4.0f +
UnityEngine.Random.Range(heightMin, heightMax));
//Calculate the square value for the right side
heightMap[cornerX, midY] = (float)((heightMap[midX, y] +
heightMap[midX, midY] +
heightMap[cornerX, cornerY] +
heightMap[pmidXR, midY]) / 4.0f +
UnityEngine.Random.Range(heightMin, heightMax));
}
}
#endregion
squareSize = (int)(squareSize / 2.0f);
//乘以阻尼值降低地形高度,同时减小最大值最小值
heightMin *= heightDampener;
heightMax *= heightDampener;
}
terrainData.SetHeights(0, 0, heightMap);
}
}
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(CustomTerrain))]
public class CustomTerrainEditor : Editor {
SerializedProperty resetTerrain;
// 改变地形随机高度数值
SerializedProperty MPDheightMin;
SerializedProperty MPDheightMax;
//阻尼值受粗糙度的影响
SerializedProperty MPDheightDampenerPower;
SerializedProperty MPDroughness;
bool showMPD = false;
private void OnEnable()
{
resetTerrain = serializedObject.FindProperty("resetTerrain");
MPDheightMin = serializedObject.FindProperty("MPDheightMin");
MPDheightMax = serializedObject.FindProperty("MPDheightMax");
MPDheightDampenerPower = serializedObject.FindProperty("MPDheightDampenerPower");
MPDroughness = serializedObject.FindProperty("MPDroughness");
}
//绘制编辑面板
public override void OnInspectorGUI()
{
//更新所有序列化的值
serializedObject.Update();
//获取自定义的地形属性脚本组件
CustomTerrain terrain = (CustomTerrain)target;
EditorGUILayout.PropertyField(resetTerrain);
#region
showMPD = EditorGUILayout.Foldout(showMPD, "Midpoint Displacement");
EditorGUILayout.PropertyField(MPDheightMin);
EditorGUILayout.PropertyField(MPDheightMax);
EditorGUILayout.PropertyField(MPDheightDampenerPower);
EditorGUILayout.PropertyField(MPDroughness);
if (showMPD)
{
if (GUILayout.Button("MPD"))
{
terrain.MidPointDisplacement();
}
}
#endregion
//应用发生的所有更改
serializedObject.ApplyModifiedProperties();
}
}