Unity Terrain Mesh 程序化地形Mesh生成

过程

  • Terrain Mesh
    • 采样高度图,生成网格position
    • 计算网格index
    • 重新计算Normal
    • 重新计算Bounds
  • Grass Point
    • 计算随机position
    • 采样高度图作为y值
    • 以MeshTopology.Points存入Mesh
  • 优化拓展
    • Job System 实现并行生成
    • Compute Shader 实现并行生成

完整代码

TerrainMesh.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class TerrainMesh : MonoBehaviour
{
public int terrainGridNum = 250;
public float terrainGridSize = 1;
public float terrainHeight = 100;
public Texture2D heightMap;
public Material terrainMaterial;
public TerrainGrass terrainGrass;

void Start()
{
CreateTerrain();
}

public void CreateTerrain()
{
List<Vector3> verts = new List<Vector3>();
List<Vector2> uvs = new List<Vector2>();
List<int> indices = new List<int>();

Vector3 center = new Vector3(terrainGridNum / 2.0f * terrainGridSize, 0,
terrainGridNum / 2.0f * terrainGridSize);
for (int i = 0; i < terrainGridNum; i++)
{
for (int j = 0; j < terrainGridNum; j++)
{
float u = (float) i / terrainGridNum * heightMap.width;
float v = (float) j / terrainGridNum * heightMap.height;
float h = heightMap.GetPixel((int) u, (int) v).grayscale * terrainHeight;
// float h = heightMap.GetPixel(i, j).grayscale * terrainHeight;
Vector3 pos = new Vector3(i * terrainGridSize, h, j * terrainGridSize);

verts.Add(pos - center);
uvs.Add(new Vector2((float) i / terrainGridNum, (float) j / terrainGridNum));

if (i == 0 || j == 0)
continue;
indices.Add(terrainGridNum * i + j);
indices.Add(terrainGridNum * i + j - 1);
indices.Add(terrainGridNum * (i - 1) + j - 1);
indices.Add(terrainGridNum * (i - 1) + j - 1);
indices.Add(terrainGridNum * (i - 1) + j);
indices.Add(terrainGridNum * i + j);
}
}

Mesh terrainMesh = new Mesh();
terrainMesh.vertices = verts.ToArray();
terrainMesh.uv = uvs.ToArray();
terrainMesh.triangles = indices.ToArray();
terrainMesh.RecalculateNormals();
terrainMesh.RecalculateBounds();

GetComponent<MeshFilter>().mesh = terrainMesh;
GetComponent<MeshRenderer>().sharedMaterial = terrainMaterial;

UpdateGrass();
}

private void UpdateGrass()
{
if (terrainGrass)
{
terrainGrass.terrainMesh = this;
terrainGrass.GeneratePoints();
}
}
}

#if UNITY_EDITOR

[CustomEditor(typeof(TerrainMesh))]
public class TerrainMeshEditor : Editor
{
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
base.OnInspectorGUI();
TerrainMesh terrainMesh = target as TerrainMesh;
if (!terrainMesh) return;

if (EditorGUI.EndChangeCheck())
{
terrainMesh.CreateTerrain();
}

if (GUILayout.Button("Update", GUILayout.MaxWidth(100)))
{
terrainMesh.CreateTerrain();
}

GUILayout.Space(12);
int verts = terrainMesh.terrainGridNum * terrainMesh.terrainGridNum;
int tris = (terrainMesh.terrainGridNum - 1) * (terrainMesh.terrainGridNum - 1) * 2;
GUILayout.Label($"Verts Num : {verts}");
GUILayout.Label($"Tris Num : {tris}");
}
}

#endif

TerrainGrass.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class TerrainGrass : MonoBehaviour
{
public int num = 1000;
[Range(1, 1000)] public int seed = 1;
public Material material;
public TerrainMesh terrainMesh;
private Vector3 m_center;
private float m_size;
private List<Vector3> m_points;
private Texture2D m_heightMap;
[Range(0, 10)] public float gizmosScale = 1;

void Start()
{
m_center = terrainMesh.gameObject.transform.position;
m_size = terrainMesh.terrainGridNum * terrainMesh.terrainGridSize;
m_heightMap = terrainMesh.heightMap;
GeneratePoints();
}

public void GeneratePoints()
{
gameObject.transform.position = m_center;

m_points = new List<Vector3>();
var indices = new int[num];

int width = m_heightMap.width;
int height = m_heightMap.height;

Random.InitState(seed);
for (int i = 0; i < num; i++)
{
var x = Random.value;
var y = Random.value;
float h = m_heightMap.GetPixel((int) (x * width), (int) (y * height)).grayscale;

var pos = new Vector3(
x * m_size - m_size / 2,
h * terrainMesh.terrainHeight,
y * m_size - m_size / 2);

m_points.Add(pos);
indices[i] = i;
}

Mesh mesh = new Mesh();

mesh.vertices = m_points.ToArray();
mesh.SetIndices(indices, MeshTopology.Points, 0);

GetComponent<MeshFilter>().mesh = mesh;
GetComponent<MeshRenderer>().sharedMaterial = material;
}

void OnDrawGizmos()
{
if (m_points == null || m_points.Count < 1) return;
Gizmos.color = Color.green;
foreach (var point in m_points)
{
Gizmos.DrawSphere(point, gizmosScale);
}
}
}

#if UNITY_EDITOR

[CustomEditor(typeof(TerrainGrass))]
public class TerrainGrassEditor : Editor
{
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
base.OnInspectorGUI();
TerrainGrass terrainGrass = target as TerrainGrass;
if (!terrainGrass) return;

if (EditorGUI.EndChangeCheck())
{
terrainGrass.GeneratePoints();
}

if (GUILayout.Button("Update", GUILayout.MaxWidth(100)))
{
terrainGrass.GeneratePoints();
}
}
}

#endif

参考


Unity Terrain Mesh 程序化地形Mesh生成
https://automask.github.io/wild/2022/06/26/lab/S_Unity_Mesh_Terrain/
作者
Kyle Zhou
发布于
2022年6月26日
许可协议