平滑算法处理本身是基于模糊图像处理,在高度图中模糊高度也是完全相同的过程,模糊处理最简单的算法是对数字求平均值。
例如下面是一个高度图像以及像素,如果是在(x,y)处模糊,这个像素周围有8个相邻像素,我们将9个值相加并除以9,然后进行相应计算:
我们将上述平滑算法应用于平滑地形:
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
public class CustomTerrain : MonoBehaviour {
public int smoothAmount = 2;
public Terrain terrain; //获取地形(物体)对象
public TerrainData terrainData;//TerrainData里有地形所有的数据
private void OnEnable()
{
terrain = gameObject.GetComponent<Terrain>(); //初始化地形数据 先获取地形再获取本身的地形数据
terrainData = Terrain.activeTerrain.terrainData;
}
//传入地形的任何一个点位置,获取这些点的邻居
//对于顶部边缘的任何像素,有的方位都没有邻居,处理好地图边缘的值
List<Vector2> GenerateNeighbours(Vector2 pos, int width, int height)
{
List<Vector2> neighbours = new List<Vector2>();
for (int y = -1; y < 2; y++)
{
for (int x = -1; x < 2; x++)
{
if (!(x == 0 && y == 0))
{
Vector2 nPos = new Vector2(Mathf.Clamp(pos.x + x, 0, width - 1),
Mathf.Clamp(pos.y + y, 0, height - 1));
if (!neighbours.Contains(nPos))
neighbours.Add(nPos);//把点的邻居放在列表里
}
}
}
return neighbours;
}
public void Smooth()
{
//平滑处理不需要重置地形
float[,] heightMap = terrainData.GetHeights(0, 0, terrainData.heightmapWidth, terrainData.heightmapHeight);
float smoothProgress = 0;//使用系统进度条告诉用户程序正在运行中而不是崩溃状态
EditorUtility.DisplayProgressBar("Smoothing Terrain",
"Progress",
smoothProgress);
#region 多次Smooth循环,多次循环应用会执行很长时间,让人感觉系统是崩溃状态,其实不然
for (int s = 0; s < smoothAmount; s++)
{
#region 这是一次Smooth循环: 获取地形高度图的每一个的高度值,求出自身与周围邻居相加的平均值
for (int y = 0; y < terrainData.heightmapHeight; y++)
{
for (int x = 0; x < terrainData.heightmapWidth; x++)
{
float avgHeight = heightMap[x, y];
List<Vector2> neighbours = GenerateNeighbours(new Vector2(x, y),
terrainData.heightmapWidth,
terrainData.heightmapHeight);
foreach (Vector2 n in neighbours)
{
//自身与周围邻居相加
avgHeight += heightMap[(int)n.x, (int)n.y];
}
//求出自身与周围邻居相加的平均值
heightMap[x, y] = avgHeight / ((float)neighbours.Count + 1);
}
}
#endregion
smoothProgress++;
#endregion
//更新系统进度条
EditorUtility.DisplayProgressBar("Smoothing Terrain",
"Progress",
smoothProgress / smoothAmount);
}
terrainData.SetHeights(0, 0, heightMap);
EditorUtility.ClearProgressBar();//平滑处理完之后系统进度条消失
}
}
解释 下上面脚本方法里的嵌套for循环:
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(CustomTerrain))]
public class CustomTerrainEditor : Editor {
bool showSmooth = false;
SerializedProperty smoothAmount;
private void OnEnable()
{
smoothAmount = serializedObject.FindProperty("smoothAmount");
}
//绘制编辑面板
public override void OnInspectorGUI()
{
//更新所有序列化的值
serializedObject.Update();
//获取自定义的地形属性脚本组件
CustomTerrain terrain = (CustomTerrain)target;
#region
//创建平滑按钮
showSmooth = EditorGUILayout.Foldout(showSmooth, "Smooth Terrain");
if (showSmooth)
{
EditorGUILayout.IntSlider(smoothAmount, 1, 10, new GUIContent("smoothAmount"));
if (GUILayout.Button("Smooth"))
{
terrain.Smooth();
}
}
#endregion
//应用发生的所有更改
serializedObject.ApplyModifiedProperties();
}
}