别再只会用建模软件了!手把手教你用C#脚本在Unity里“捏”出一个3D模型(附完整项目源码)
用C#脚本在Unity中创造程序化3D艺术:从数学公式到动态网格生成
在数字艺术与游戏开发领域,程序化建模正逐渐成为创作者们的新宠。与传统的Blender、Maya等建模工具不同,通过代码直接生成3D模型不仅能实现动态变化的效果,更能将数学之美转化为视觉奇观。本文将带您深入Unity的Mesh系统,探索如何用C#脚本从零构建复杂几何形态,让算法成为您的3D雕刻刀。
1. 理解Unity网格系统的核心架构
1.1 Mesh类:数字雕塑的骨架
Unity中的Mesh类就像3D模型的DNA,它通过几组关键数据定义了模型的形态:
public class Mesh { public Vector3[] vertices; // 顶点坐标集合 public int[] triangles; // 三角形索引序列 public Vector3[] normals; // 每个顶点的法线方向 public Vector2[] uv; // 纹理映射坐标 public Color[] colors; // 顶点颜色数据(可选) }这些数据之间的关系可以用以下结构表示:
| 数据元素 | 作用 | 示例值 |
|---|---|---|
| vertices | 定义3D空间中的点 | Vector3(0,1,0) |
| triangles | 连接顶点形成面 | [0,1,2]表示三个顶点组成的三角形 |
| normals | 决定光照反射方向 | Vector3(0,0,1)表示Z轴正向 |
| uv | 控制纹理贴图映射 | Vector2(0.5,0.5)表示纹理中心 |
1.2 渲染管线中的关键组件
要让创建的网格真正显示在场景中,需要理解Unity的渲染组件协同工作方式:
- MeshFilter:存储网格数据容器
- MeshRenderer:负责将网格数据转化为屏幕像素
- Material:定义表面着色规则和外观特性
重要提示:修改vertices数组后必须调用RecalculateNormals()和RecalculateBounds(),否则可能导致光照异常或视锥体裁剪错误。
2. 从基础几何到复杂形态的构建方法
2.1 构建参数化基本几何体
让我们从创建一个可配置的圆环面开始,演示如何用数学参数控制形状:
Mesh CreateTorus(float radius, float thickness, int segments, int sides) { Mesh mesh = new Mesh(); List<Vector3> vertices = new List<Vector3>(); List<int> triangles = new List<int>(); // 生成顶点 for (int i = 0; i < segments; i++) { float segmentAngle = i * Mathf.PI * 2 / segments; Vector3 segmentCenter = new Vector3( Mathf.Cos(segmentAngle) * radius, 0, Mathf.Sin(segmentAngle) * radius); for (int j = 0; j < sides; j++) { float sideAngle = j * Mathf.PI * 2 / sides; Vector3 offset = new Vector3( Mathf.Cos(sideAngle) * thickness, Mathf.Sin(sideAngle) * thickness, 0); vertices.Add(segmentCenter + offset); } } // 连接三角形 for (int i = 0; i < segments; i++) { for (int j = 0; j < sides; j++) { int current = i * sides + j; int next = current + sides; if (next >= vertices.Count) next -= vertices.Count; triangles.Add(current); triangles.Add((j == sides-1) ? current-j : current+1); triangles.Add(next); triangles.Add(next); triangles.Add((j == sides-1) ? current-j : current+1); triangles.Add((j == sides-1) ? next-j : next+1); } } mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); mesh.RecalculateNormals(); return mesh; }通过调整radius(主半径)、thickness(截面半径)、segments(环段数)和sides(截面边数)参数,可以创建从光滑圆环到棱角分明的多边环等各种形态。
2.2 应用噪声算法创造有机形态
Perlin噪声是生成自然形态的利器,以下示例展示如何用噪声函数变形网格:
void ApplyNoiseDeformation(Mesh mesh, float noiseScale, float strength) { Vector3[] vertices = mesh.vertices; for (int i = 0; i < vertices.Length; i++) { Vector3 vertex = vertices[i]; float noise = Mathf.PerlinNoise( vertex.x * noiseScale, vertex.z * noiseScale); vertices[i] = vertex + Vector3.up * noise * strength; } mesh.vertices = vertices; mesh.RecalculateNormals(); }将此技术应用于基础球体网格,可以轻松创建出类似地形、云朵或生物表皮的有机形态。
3. 高级程序化建模技术实战
3.1 分形几何的递归生成
分形结构以其无限细节著称,以下代码展示如何递归生成分形四面体:
void GenerateFractalTetrahedron(Mesh mesh, int depth, Vector3 a, Vector3 b, Vector3 c, Vector3 d) { if (depth <= 0) { AddTetrahedron(mesh, a, b, c, d); return; } Vector3 ab = (a + b) / 2; Vector3 ac = (a + c) / 2; Vector3 ad = (a + d) / 2; Vector3 bc = (b + c) / 2; Vector3 bd = (b + d) / 2; Vector3 cd = (c + d) / 2; GenerateFractalTetrahedron(mesh, depth-1, a, ab, ac, ad); GenerateFractalTetrahedron(mesh, depth-1, ab, b, bc, bd); GenerateFractalTetrahedron(mesh, depth-1, ac, bc, c, cd); GenerateFractalTetrahedron(mesh, depth-1, ad, bd, cd, d); }每增加一级递归深度,几何复杂度呈指数增长,可以创造出令人惊叹的细节结构。
3.2 动态合批与性能优化
当场景中存在大量程序化生成的网格时,动态合批技术至关重要:
| 优化技术 | 实施方法 | 适用场景 |
|---|---|---|
| 静态合批 | 标记为Static | 不变的背景元素 |
| GPU Instancing | 使用相同材质 | 重复但位置不同的对象 |
| 自定义合批 | 合并顶点数据 | 需要动态变形的对象 |
实现自定义合批的示例代码:
Mesh CombineMeshes(List<Mesh> meshes) { CombineInstance[] combine = new CombineInstance[meshes.Count]; for (int i = 0; i < meshes.Count; i++) { combine[i].mesh = meshes[i]; combine[i].transform = Matrix4x4.identity; } Mesh finalMesh = new Mesh(); finalMesh.CombineMeshes(combine); return finalMesh; }4. 从数学方程到视觉奇迹:创意编码实践
4.1 参数化曲面生成
许多迷人的3D形态都可以用数学方程描述。以下代码展示如何将参数方程转化为网格:
Mesh CreateParametricSurface(int uSteps, int vSteps, Func<float, float, Vector3> equation) { Mesh mesh = new Mesh(); Vector3[] vertices = new Vector3[uSteps * vSteps]; Vector2[] uv = new Vector2[vertices.Length]; int[] triangles = new int[(uSteps-1) * (vSteps-1) * 6]; // 生成顶点 for (int u = 0; u < uSteps; u++) { for (int v = 0; v < vSteps; v++) { float uNorm = (float)u / (uSteps-1); float vNorm = (float)v / (vSteps-1); vertices[u * vSteps + v] = equation(uNorm, vNorm); uv[u * vSteps + v] = new Vector2(uNorm, vNorm); } } // 连接三角形 int triIndex = 0; for (int u = 0; u < uSteps-1; u++) { for (int v = 0; v < vSteps-1; v++) { int current = u * vSteps + v; int next = current + vSteps; triangles[triIndex++] = current; triangles[triIndex++] = current + 1; triangles[triIndex++] = next; triangles[triIndex++] = next; triangles[triIndex++] = current + 1; triangles[triIndex++] = next + 1; } } mesh.vertices = vertices; mesh.uv = uv; mesh.triangles = triangles; mesh.RecalculateNormals(); return mesh; }使用这个通用生成器,只需传入不同的方程就能创造各种曲面:
// 克莱因瓶 Func<float, float, Vector3> kleinBottle = (u, v) => { u *= Mathf.PI * 2; v *= Mathf.PI * 2; float x = 3 * Mathf.Cos(u) * (1 + Mathf.Sin(u)) + (2 * (1 - Mathf.Cos(u)/2)) * Mathf.Cos(u) * Mathf.Cos(v); float y = -8 * Mathf.Sin(u) - 2 * (1 - Mathf.Cos(u)/2) * Mathf.Sin(u) * Mathf.Cos(v); float z = 2 * (1 - Mathf.Cos(u)/2) * Mathf.Sin(v); return new Vector3(x, y, z) * 0.1f; };4.2 实时变形与交互响应
程序化建模的强大之处在于可以实现实时动态变化。以下代码展示如何让网格对鼠标交互做出反应:
public class InteractiveMesh : MonoBehaviour { private Mesh originalMesh; private Mesh deformedMesh; private Vector3[] originalVertices; void Start() { originalMesh = GetComponent<MeshFilter>().mesh; deformedMesh = Instantiate(originalMesh); GetComponent<MeshFilter>().mesh = deformedMesh; originalVertices = originalMesh.vertices; } void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { Vector3[] vertices = deformedMesh.vertices; for (int i = 0; i < vertices.Length; i++) { Vector3 vertex = originalVertices[i]; float distance = Vector3.Distance( transform.TransformPoint(vertex), hit.point); if (distance < 2.0f) { float falloff = 1 - (distance / 2.0f); vertices[i] = vertex + hit.normal * falloff * 0.5f; } else { vertices[i] = originalVertices[i]; } } deformedMesh.vertices = vertices; deformedMesh.RecalculateNormals(); } } }这种技术可以用于创建可塑材料、交互式地形等动态效果。
