在Unity中实现Townscaper风格的有机网格生成第一次玩Townscaper时我就被它那种看似随意却又和谐统一的建筑布局深深吸引。作为一个Unity开发者我一直在思考如何在自己的项目中实现类似的有机网格生成效果。经过几周的探索和实验我终于找到了一套可行的方案现在就来分享这个将复杂算法转化为Unity实用工具的全过程。1. 理解Townscaper网格的核心原理Townscaper的魅力在于它用看似简单的四边形网格构建出丰富多变的城镇景观。要实现这种效果我们需要理解几个关键算法Delaunay三角剖分这是生成基础网格的数学方法能确保生成的三角形尽可能接近等边三角形边剔除与四边形形成通过随机剔除三角形边来形成不规则四边形网格细分将剩余的三角形和四边形进一步细分形成更密集的网格松弛优化通过迭代调整顶点位置使网格看起来更自然美观在Unity中实现这些算法时我们需要特别注意性能优化和实时预览功能。下面这段代码展示了基础数据结构的设计[ExecuteInEditMode] public class OrganicGrid : MonoBehaviour { [Range(2, 12)] public int gridSize 6; [Range(0, 10000)] public int randomSeed 4242; private ListVector2 points; private ListTriangle triangles; private ListQuad quads; void OnValidate() { GenerateGrid(); } void GenerateGrid() { // 初始化数据结构 points new ListVector2(); triangles new ListTriangle(); quads new ListQuad(); // 生成基础网格 CreateBaseTriangulation(); // 后续处理步骤 RemoveEdgesToFormQuads(); SubdivideRemainingFaces(); RelaxVertices(); } }2. 构建基础三角网格第一步是创建一个均匀分布的点集然后进行Delaunay三角剖分。在Unity中我们可以采用简化的方法在六边形区域内生成随机点计算这些点的Delaunay三角剖分将结果存储在易于操作的网格结构中关键参数说明参数名作用推荐值gridSize控制网格密度4-8randomSeed确保可重复的随机结果任意整数以下是生成基础网格的核心代码void CreateBaseTriangulation() { // 生成六边形边界点 float radius gridSize * 0.5f; for (int i 0; i 6; i) { float angle i * Mathf.PI / 3f; points.Add(new Vector2( Mathf.Cos(angle) * radius, Mathf.Sin(angle) * radius )); } // 内部随机点生成 System.Random rand new System.Random(randomSeed); for (int i 0; i gridSize * 10; i) { Vector2 point RandomPointInHexagon(rand, radius); points.Add(point); } // 执行Delaunay三角剖分 triangles DelaunayTriangulation(points); }3. 从三角网格到四边形网格Townscaper的独特之处在于它主要使用四边形而非三角形。我们可以通过以下步骤实现转换随机选择三角形边进行剔除将相邻三角形合并为四边形处理剩余的独立三角形这个过程中的随机性控制非常重要边剔除概率影响最终四边形的规整程度随机种子确保结果可重复迭代次数决定网格的复杂程度void RemoveEdgesToFormQuads() { System.Random rand new System.Random(randomSeed); ListTriangle remainingTris new ListTriangle(triangles); while (remainingTris.Count 0) { int index rand.Next(remainingTris.Count); Triangle tri remainingTris[index]; // 查找共享边的相邻三角形 Triangle neighbor FindAdjacentTriangle(tri); if (neighbor ! null) { // 合并为四边形 Quad quad MergeToQuad(tri, neighbor); quads.Add(quad); // 从剩余列表中移除 remainingTris.Remove(tri); remainingTris.Remove(neighbor); } else { // 无法合并的三角形保留到最后处理 remainingTris.RemoveAt(index); } } // 处理剩余的独立三角形 foreach (Triangle tri in remainingTris) { quads.AddRange(SubdivideTriangle(tri)); } }4. 网格细分与优化为了获得更丰富的细节我们需要对现有网格进行细分将每个四边形细分为四个较小的四边形将剩余的三角形细分为三个四边形通过松弛算法优化顶点位置细分策略对比方法优点缺点均匀细分实现简单缺乏变化随机细分更有机可能产生畸形面片基于曲率细分保留特征计算复杂以下是细分和松弛的实现void SubdivideRemainingFaces() { ListQuad newQuads new ListQuad(); foreach (Quad quad in quads) { // 计算中点 Vector2 center (points[quad.a] points[quad.b] points[quad.c] points[quad.d]) / 4f; int centerIndex points.Count; points.Add(center); // 创建四个子四边形 newQuads.Add(new Quad(quad.a, quad.b, centerIndex, centerIndex)); newQuads.Add(new Quad(quad.b, quad.c, centerIndex, centerIndex)); newQuads.Add(new Quad(quad.c, quad.d, centerIndex, centerIndex)); newQuads.Add(new Quad(quad.d, quad.a, centerIndex, centerIndex)); } quads newQuads; } void RelaxVertices() { // 构建邻接关系 Dictionaryint, Listint neighbors new Dictionaryint, Listint(); foreach (Quad quad in quads) { AddNeighbor(neighbors, quad.a, quad.b); AddNeighbor(neighbors, quad.a, quad.d); // 添加其他边的邻接关系... } // 迭代松弛 for (int iter 0; iter 5; iter) { for (int i 0; i points.Count; i) { if (!neighbors.ContainsKey(i)) continue; Vector2 avg Vector2.zero; foreach (int j in neighbors[i]) { avg points[j]; } avg / neighbors[i].Count; // 渐进式移动 points[i] Vector2.Lerp(points[i], avg, 0.3f); } } }5. 在Unity中可视化网格为了让这个算法真正可用我们需要在Unity编辑器中实现实时预览使用Gizmos绘制网格线框添加参数控制面板支持网格导出为Mesh资源void OnDrawGizmos() { if (points null || quads null) return; // 绘制顶点 Gizmos.color Color.white; foreach (Vector2 point in points) { Gizmos.DrawSphere(point, 0.05f); } // 绘制四边形边 Gizmos.color Color.green; foreach (Quad quad in quads) { DrawQuadGizmo(quad); } } void DrawQuadGizmo(Quad quad) { Vector3 a points[quad.a]; Vector3 b points[quad.b]; Vector3 c points[quad.c]; Vector3 d points[quad.d]; Gizmos.DrawLine(a, b); Gizmos.DrawLine(b, c); Gizmos.DrawLine(c, d); Gizmos.DrawLine(d, a); }6. 实际应用与扩展有了基础网格生成器后我们可以进一步扩展功能3D化将2D网格赋予高度创建丘陵地形建筑生成在每个四边形区域生成不同风格的建筑动态编辑允许玩家在运行时修改网格性能优化技巧使用Job System进行并行计算实现增量式网格更新对静态区域进行烘焙// 示例简单的3D化处理 Mesh Generate3DMesh() { Mesh mesh new Mesh(); // 将2D点转换为3D顶点 Vector3[] vertices new Vector3[points.Count]; for (int i 0; i points.Count; i) { float height Mathf.PerlinNoise( points[i].x * 0.1f, points[i].y * 0.1f ) * 2f; vertices[i] new Vector3(points[i].x, height, points[i].y); } // 构建三角形面片 Listint triangles new Listint(); foreach (Quad quad in quads) { triangles.Add(quad.a); triangles.Add(quad.b); triangles.Add(quad.c); triangles.Add(quad.a); triangles.Add(quad.c); triangles.Add(quad.d); } mesh.vertices vertices; mesh.triangles triangles.ToArray(); mesh.RecalculateNormals(); return mesh; }在实际项目中我发现最有价值的不是完美复现Townscaper的效果而是理解其背后的设计哲学——在规则与随机之间找到平衡点。通过调整各种参数你可以创造出从规整城市到杂乱村庄的各种风格。