做太空题材或策略游戏时程序化生成行星几乎是必答题。这篇文章要拆解的是基于 Unity 和 Blender 构建一套六边形行星生成器引擎的完整技术路线用 C# 在 Unity 里生成六边形球面网格用噪声算法做出山脉、海洋和生态分布再用 Blender 配合制作面板资产、烘焙贴图和材质预览。整个过程不仅覆盖网格生成还会落到关卡设计六边形网格上的寻路、地块类型、批量生成多个行星以及运行时性能怎么观察、怎么优化。文章会先讲清楚“为什么是六边形”再给六边形球面网格的算法基础然后分别拆解 Unity 侧脚本与 Blender 侧资产流程。后面补上噪声地形、关卡设计落地的具体写法、性能观察和排查清单。适合正在做星球探索、太空策略、RTS 或回合制品类又不想靠手工摆地形想用程序化管线批量产出星球场景的开发者。即使你只是对六边形网格算法感兴趣这篇也值得收藏后慢慢看。项目本身不是某个仓库名而是一套完整的程序化内容生成PCG技术方案。你不需要第三方收费插件用 Unity 的 Mesh API、C# 基础和 Blender 的几何节点就能把主体跑起来。这也就意味着自己动手改造的余量非常大从地形风格到玩法规则全部可控。1. 核心概念与技术选型1.1 什么是六边形行星生成器六边形行星生成器顾名思义是把一颗星球从“球体几何”到“地形高度”再到“地块属性”都交给程序生成的引擎工具。它不是简单地在球上贴一张高度图而是先把球面拆成紧凑的六边形网格然后对每个六边形地块做高度偏移、生态类型、资源分布和颜色控制。最终得到的不是一张贴图而是一套可以被游戏逻辑查询和修改的数据结构。这和传统的 Quad Sphere 方案有本质区别。四边形球面方案在极点区域会有严重的 UV 拉伸而且网格拓扑结构不够均匀六边形网格有更好的拓扑表示地块之间共享边而不是共享角非常适合策略游戏里的区域操作、移动消耗计算和势力划分。1.2 Unity 与 Blender 的分工这套架构里Unity 和 Blender 不是二选一的关系而是各管一段阶段工具职责运行时网格生成Unity生成二十面体、细分、转对偶网格、采样噪声、输出 Mesh地形与生态逻辑Unity根据噪声和温度/湿度信息设置地块类型、资源权重面板资产制作Blender制作六边形面板、岩壁、建筑底座的低模资产预览配色贴图与光照烘焙Blender烘焙 AO、法线贴图导出 FBX/Prefab 到 Unity批量生成与配置Unity使用 ScriptableObject 管理参数批量生成多个行星简单理解Blender 负责“把单块六边形地板做得好看”Unity 负责“把几千上万个六边形地板拼成一颗星球并且能响应游戏玩法”。1.3 为什么是六边形网格而不是四边形网格六边形网格在关卡设计里的优势非常明显每个格子有 6 个邻居移动路径比四边形更自然转弯角度更平滑。相邻地块共享整条边适合做边界判定、区域接壤、战争迷雾扩散。六边形中心到每个邻居中心的距离一致不会出现四边形里“斜角距离更远”的问题。在球面上六边形网格比矩形网格更接近等距分布极点畸变更小。代价也很直接六边形网格的顶点和三角形关系更复杂UV 展开困难而且球面上无法做到全部是六边形一定会混入五边形。这一点后面会详细讲。2. 六边形球面网格的算法基础2.1 从二十面体开始为什么用二十面体要在球面上生成尽量均匀的六边形网格最常见的做法是先建一个二十面体Icosphere然后不断细分最后做对偶网格转换。二十面体有 20 个三角形面、30 条边、12 个顶点。它的顶点均匀分布在球面上是所有多面体里最适合做球形细分的基础形状。初始顶点可以用黄金比例计算float phi (1f Mathf.Sqrt(5f)) * 0.5f; Vector3[] vertices new Vector3[12] { new Vector3(-1, phi, 0), new Vector3( 1, phi, 0), new Vector3(-1, -phi, 0), new Vector3( 1, -phi, 0), new Vector3( 0, -1, phi), new Vector3( 0, 1, phi), new Vector3( 0, -1, -phi), new Vector3( 0, 1, -phi), new Vector3( phi, 0, -1), new Vector3( phi, 0, 1), new Vector3(-phi, 0, -1), new Vector3(-phi, 0, 1) };这些顶点需要归一化到单位球面上然后按二十面体的固定索引表组成 20 个三角形。这里的索引表是一组公开的标准几何数据在 Unity 里可以直接存入int[] triangles。2.2 细分把三角形变成更细的网格细分的核心逻辑是对每一条边插入中点然后把一个三角形拆成四个小三角形。关键点是边的中点不能重复创建因为相邻三角形共享同一条边。Dictionarylong, int midpointCache new Dictionarylong, int(); int GetMidpointIndex(int a, int b) { long key a b ? (a 16) b : (b 16) a; if (midpointCache.TryGetValue(key, out int index)) return index; Vector3 mid (vertices[a] vertices[b]) * 0.5f; mid.Normalize(); index vertices.Count; vertices.Add(mid); midpointCache[key] index; return index; }一次细分后三角形数量从 20 变为 80。做 3 次细分三角形数量约为 1280六边形地块数量在几百到一千左右已经可以看清星球轮廓做 5 次细分三角形数量约 20480地块数接近一万一适合正式关卡。细分次数每增加一次三角形数翻四倍所以性能瓶颈会非常快地出现。2.3 对偶网格三角形转六边形三角形网格本身不是六边形。要把三角形网格变成六边形网格需要做“对偶”操作对原始网格中的每一个顶点收集它周围的所有三角形把这些三角形的质心按顺序连接成一个多边形。这个操作在任意球面上都会产生两种面原始顶点连接 6 个三角形时得到六边形。原始顶点连接 5 个三角形时得到五边形。这里必须强调一个数学事实一个封闭的球面网格不可能全部由六边形构成。根据欧拉公式球面上至少要有 12 个五边形顶点。这 12 个五边形在二十面体的 12 个原始顶点位置附近。它们不是 bug而是球面几何的必然结果。处理得好它们可以变成地表建筑的锚点或者资源富集区让关卡更有记忆点。对偶转换的示意代码Dictionaryint, Listint vertexTriangles new Dictionaryint, Listint(); for (int i 0; i triangles.Count; i 3) { int a triangles[i]; int b triangles[i 1]; int c triangles[i 2]; AddToDict(vertexTriangles, a, i / 3); AddToDict(vertexTriangles, b, i / 3); AddToDict(vertexTriangles, c, i / 3); } ListVector3 dualVertices new ListVector3(); Listint dualTriangles new Listint(); foreach (KeyValuePairint, Listint pair in vertexTriangles) { Listint incident pair.Value; Vector3 center Vector3.zero; foreach (int triIndex in incident) { Vector3 v0 vertices[triangles[triIndex * 3]]; Vector3 v1 vertices[triangles[triIndex * 3 1]]; Vector3 v2 vertices[triangles[triIndex * 3 2]]; center (v0 v1 v2) / 3f; } center / incident.Count; center.Normalize(); dualVertices.Add(center); }这里最需要小心的坑是入射三角形的质心顺序不保证是绕顶点一圈排列的直接连多边形会出现面片交叉。正确做法是先按质心相对顶点的方位角排序再生成三角形扇。这一步处理不好生成的行星表面会产生大量扭曲三角形肉眼看起来就像噪声糊在球面上。2.4 噪声采样的坐标系选择六边形球面网格生成后要给每个地块计算海拔。常见的错误是在纬度/经度坐标系里采样二维噪声再映射到球面这样极区会有严重的拉伸和重复。正确做法是直接用三维噪声采样把六边形顶点的归一化方向向量当作采样坐标。三维 Perlin/Simplex 噪声天然没有接缝采样点在球面均匀分布时生成的噪声场连续且方向无关。如果需要山脊效果可以用abs()扭曲噪声值需要海洋和大陆可以用频率更低的多频叠加。3. 环境准备与前置条件一套完整的开发环境大致如下具体版本建议以你本机的 Unity 和 Blender 版本为准不强制踩新版本工具用途建议版本Unity运行时行星生成、C# 逻辑2021 LTS 或更高版本Blender六边形资产建模、烘焙贴图3.6 LTS 或更高版本C# IDE脚本编写与调试Visual Studio / Rider版本控制管理代码和场景文件Git Git LFS配置页面建议开启内置渲染管线的 URP方便后处理效果如果只做纯数据生成不依赖渲染效果内置渲染管线也完全够用。启动工程前检查以下几点Unity 已安装对应模块能创建 3D 项目。Blender 能打开几何节点工作区。磁盘预留足够空间Unity 烘焙光照贴图和 Blender 导出纹理都需要临时空间。Git LFS 提前配置避免 FBX 和纹理文件损坏。4. 在 Unity 中实现六边形行星生成器核心4.1 项目结构与脚本规划建议把生成器代码按职责拆成四个脚本脚本职责IcosahedronBuilder.cs生成二十面体顶点和三角形SphereSubdivider.cs细分三角形维护边中点缓存DualMeshConverter.cs对偶网格转换输出多边形面PlanetMeshGenerator.cs高层入口串联全部流程赋值 MeshFilter/MeshCollider再加一个数据配置类PlanetConfig用 ScriptableObject 保存细分次数、噪声参数、颜色梯度、高度缩放。这样后续生成多个行星只需要创建多个配置文件不需要改代码。4.2 构建二十面体与细分管线在PlanetMeshGenerator的入口处把整条流程按顺序调用public Mesh BuildPlanetMesh(PlanetConfig config) { IcosahedronBuilder builder new IcosahedronBuilder(); builder.Build(); SphereSubdivider subdivider new SphereSubdivider(builder.Vertices, builder.Triangles); for (int i 0; i config.SubdivisionLevels; i) { subdivider.SubdivideOnce(); } DualMeshConverter dual new DualMeshConverter( subdivider.Vertices, subdivider.Triangles ); dual.Convert(); ApplyNoiseElevation(dual.Vertices, config); Mesh mesh BuildMeshFrom(dual); return mesh; }命名和接口可以按你的工程风格调整但这个流程本身是稳定的二十面体 → 细分 → 对偶 → 噪声位移 → 生成 Mesh。4.3 地形高度与多频噪声对每个对偶顶点需要把它的方向向量作为三轴噪声采样坐标。多频叠加的做法如下float SampleElevation(Vector3 point, PlanetConfig config) { float value 0f; float amplitude 1f; float frequency 1f; for (int octave 0; octave config.Octaves; octave) { float noise Mathf.PerlinNoise( point.x * frequency config.SeedOffset, point.y * frequency config.SeedOffset ); // 这里是 2D 噪声简化示例生产环境建议用 3D Simplex value noise * amplitude; amplitude * config.Persistence; frequency * config.Lacunarity; } return value; }注意上面示例用 2D Perlin 只是为了展示叠加思路。真正的球面采样强烈建议用 3D Simplex 或 OpenSimplex避免极点接缝。Unity 内置的Mathf.PerlinNoise是 2D API直接用它做球面地形会有明显缺陷。更好的选择是引入 LibNoise.Unity 或自己写一个 3D Simplex 实现。采样后把高度作用到顶点位置Vector3 dir dualVertices[i].normalized; dualVertices[i] dir * (baseRadius elevation * heightScale);生成 Mesh 时法线可以直接使用dir方向因为高度只改变半径不改变方位。4.4 顶点色与地块材质六边形行星不能只有高度。按高度和噪声分配顶点色可以让海洋、沙滩、平原、山脉、雪峰一目了然。Color EvaluateColor(float elevation, float moisture) { if (elevation config.SeaLevel) return config.WaterGradient.Evaluate(elevation / config.SeaLevel); if (elevation config.BeachLevel) return Color.Lerp(config.SandColor, config.GrassColor, (elevation - config.SeaLevel) / (config.BeachLevel - config.SeaLevel)); return config.MountainGradient.Evaluate((elevation - config.BeachLevel) / (config.MaxElevation - config.BeachLevel)); }这只是一个示例规则。你可以在 Actual 开发里加入温度带、湿度噪声、轴倾斜导致的气候带把行星生成器从“地形工具”升级为“生态工具”。5. Blender 侧资产准备与流程配合5.1 使用几何节点生成六边形面板Blender 的几何节点非常适合快速制作六边形面板的基础资产。思路是新建一个Grid节点再通过Dual Mesh节点得到六边形排列然后挤出厚度或者直接使用Cylinder节点并设置顶点数为 6得到单个六边形柱子底座。如果你想做更复杂的星球表面建筑基地可以用几何节点构建一个六边形瓦片的集合面板中心、边缘凸起、四角铆钉。这样每次修改都不会破坏基础拓扑。5.2 导出 FBX 和贴图的注意点导出前把 Blender 单位设置成米和 Unity 的默认单位一致。贴图命名不能用中文避免 Unity 导入时出现路径告警。如果面板资产带倒角烘焙 AO 后在 Unity 里看起来体积感会强很多。法线贴图需要选择“Tangent Space”导出直接导出 Object Space 法线会在 Unity 里出现方向错乱。在 Blender 里先清掉未使用的材质球再导出 FBX可以减小工程体积。5.3 与 Unity 脚本配合的资产管线Blender 做好的单个六边形面板导入 Unity 后不要直接把它当成地面格子逐块摆。行星运行时生成的网格数量非常大用 Prefab 逐块实例化会带来巨大的 Draw Call 压力。正确做法是把 Blender 面板作为“视觉母版”在 Unity 里用 Mesh 合并或 GPU Instancing 把相同的面板按地块位置批量实例化或者只把 Blender 面板的高模和贴图作为烘焙参考运行时使用 Unity 生成的低模六边形网格配合顶点色和 Triplanar 贴图采样。简单说Blender 负责“长什么样”Unity 负责“怎么快速铺满星球”。6. 关卡设计六边形行星上的玩法落地6.1 六边形网格坐标模型在地块逻辑层面不要直接操作 Mesh 顶点。更好的做法是先建立一块数据网格把每个地块的坐标、邻居、高度、生态类型、归属势力放在一个类里。比如使用 Cube Coordinates 或 Offset Coordinates六边形寻路会非常顺手。public struct HexCoord { public int X; public int Y; public int Z; public static ListHexCoord Neighbors(HexCoord h) { return new ListHexCoord() { new HexCoord { X h.X 1, Y h.Y - 1, Z h.Z }, new HexCoord { X h.X 1, Y h.Y, Z h.Z - 1 }, new HexCoord { X h.X, Y h.Y 1, Z h.Z - 1 }, new HexCoord { X h.X - 1, Y h.Y 1, Z h.Z }, new HexCoord { X h.X - 1, Y h.Y, Z h.Z 1 }, new HexCoord { X h.X, Y h.Y - 1, Z h.Z 1 } }; } }这只是一个平面六边形的邻居模型。球形六边形网格的邻居关系不需要重新发明把三角形网格对偶后得到的多边形面的“共享边关系”导出成一张邻接表即可。6.2 地块类型与规则生成地块类型可以按高度和湿度进行规则划分高度区间湿度条件地块类型低于海平面任意海洋海平面到沙滩线任意沙滩沙滩线到中海拔高湿度森林沙滩线到中海拔低湿度草原高海拔任意山地极高海拔任意雪峰这张规则表可以写到PlanetConfig里让策划在不改代码的前提下调权重。行星生成器从“纯技术工具”变成“可配置的关卡生产管线”关键就在于把规则暴露到配置层。6.3 批量生成多个行星与参数配置用 ScriptableObject 管理配置后批量生成多颗行星只需要遍历配置列表public ListPlanetData GeneratePlanets(ListPlanetConfig configs) { ListPlanetData results new ListPlanetData(); foreach (PlanetConfig config in configs) { PlanetData data new PlanetData(); data.Mesh BuildPlanetMesh(config); data.BiomeMap GenerateBiomeMap(data.Vertices, config); data.HexNodes BuildHexNodesFromMesh(data.Mesh, config); results.Add(data); } return results; }这里的关键点是Mesh、BiomeMap、HexNodes 必须解耦。Mesh 只是渲染数据BiomeMap 决定地块属性HexNodes 才是玩法逻辑直接查询的对象。如果你让玩法逻辑直接依赖 Mesh 三角形索引后面改分辨率或换网格算法时会非常痛苦。6.4 五边形地块的特殊处理前面提到球面必然有 12 个五边形。这些五边形的邻居只有 5 个而不是 6 个。寻路时如果直接按六边形规则遍历邻居五边形地块的其中一个访问方向会越界或缺失。实际关卡设计里可以把五边形做成“特殊节点”资源高产区。副本入口。大型建筑的地基。行星传送门。这样不仅规避了寻路问题还能让玩家在星球上找到结构性地标。这是六边形行星生成器相较于四边形球的一个隐藏优点天然拥有视觉奇观点。7. 性能优化与资源占用观察7.1 观察性能的几个维度运行六边形行星生成器时需要重点观察以下数据维度观察方式关注点三角形数量Unity Profiler / Mesh 信息面板生成时间与渲染压力内存占用Profiler Memory Module顶点数组、索引数组、邻接表占用主线程耗时CPU Usage集中式循环是否卡顿Draw CallFrame Debugger实例化是否合并Mesh Collider 重建Profiler物理碰撞计算成本注意没有固定的“显存占用”数值可以套用因为行星网格的渲染开销取决于三角形数量、材质复杂度和后处理特效。在低端设备上建议控制细分次数先用 3 次细分跑通逻辑再逐步提高。7.2 降低顶点数量的手段细分次数不要无脑调高。5 次细分已经是很多策略游戏的上限。对远离玩家的半球做简化或者使用基于与相机距离的 LOD 切换。把相邻的同生态地块合并成一个 Mesh 子网格减少渲染批次。六边形地板不需要每块都带大量顶点平地区域可以展平减少高度采样精度。7.3 减少主线程压力生成大量网格时CPU 会停顿。减少卡顿的手段按推荐顺序排列把生成流程放到协程里分帧执行。使用 Unity Job System把噪声采样和顶点计算放到工作线程。用 Burst Compiler 编译高频循环。对 Mesh 的MarkDynamic()做必要设置避免不必要的 CPU 与 GPU 间数据拷贝。7.4 内存与资源释放每次重新生成行星时必须手动释放旧 Mesh 和旧邻接表if (existingMesh ! null) { Destroy(existingMesh); existingMesh null; }这句话写不好反复生成多颗行星后在 Profiler 里会看到网格内存居高不下。旧 Mesh 不会因为MeshFilter.mesh被替换就自动释放需要手动销毁。8. 常见问题与排查方法问题现象可能原因排查方式解决方案行星表面出现大量扭曲三角形对偶网格顶点未按环绕排序查看对偶网格的面索引顺序按质心方位角排序后生成三角扇部分地块看起来是三角形而不是六边形细分次数不足五边形占比过高统计地块边数分布增加细分次数但保留对 12 个五边形的特殊处理地形在极点区域出现拉伸或接缝使用了 2D 噪声采样球面坐标检查噪声采样输入向量改用 3D Simplex/OpenSimplex 噪声生成后的网格背面朝外三角形环绕序反向在 Scene 视图检查法线方向反转三角形索引顺序或重新计算法线重复生成多个行星后内存暴涨旧 Mesh 未销毁Profiler Memory 查看 Mesh 数量生成前销毁旧 Mesh 和邻接表Mesh Collider 碰撞极不准确Collider 直接使用高密度 MeshPhysics Debug 视图为碰撞单独生成低密度 MeshBlender 导出的六边形资产在 Unity 中颜色发黑法线贴图空间不一致检查导入设置导出 Tangent Space 法线并确认 Unity 导入参数整体生成时间过长细分次数过高或主线程集中计算Profiler CPU Usage调低细分改用 Job System游戏逻辑找不到某个六边形地块的邻居邻接表未同步对偶网格拓扑检查五边形地块的邻居数量手动补邻居或把五边形设为特殊地块9. 最佳实践与合规建议9.1 工程组织建议把噪声采样、网格生成、地块逻辑分成三个独立程序集方便单测。用 ScriptableObject 存放所有生成参数禁止在代码里写死星球半径、噪声频率、颜色梯度。保持一套最小可运行配置用于每次改动后回归测试。把输出行星的数据序列化为 JSON便于调试和版本对比。{ planetName: TestPlanet, subdivisionLevel: 3, baseRadius: 100, seaLevel: 0.35, octaves: 5, seedOffset: 12345 }这个 JSON 可以导出为配置文件也可以用同一结构在 Unity 里生成ScriptableObject。策划和程序只要约定好字段名就可以无痛协作。9.2 测试与回归第一次跑通后先把细分次数设为 1、关闭噪声确认球体成型再把噪声打开观察地形是否连续最后加入地块类型和寻路验证玩法数据。这个顺序能快速定位问题出在哪一层。9.3 版权与合规提醒用 Blender 制作六边形资产时如果参考了外部模型或纹理确认授权范围能否用于商业项目。AI 辅助生成地形或贴图素材时检查生成模型的使用条款避免商用版权纠纷。如果项目涉及真实星球数据或公开地图数据确认数据来源的许可协议。游戏上线前对资源、文本、音效做完整合规核查程序化生成工具也要纳入版本归档。10. 总结与后续扩展这套六边形行星生成器引擎最值得尝试的点是它把程序化几何、噪声地形、关卡设计和美术资产管线完整串联起来。你最先应该验证的不是画面好看而是“细分之后对偶网格有没有正确变成六边形”以及“12 个五边形是否被妥善处理”。最容易踩坑的也恰好是这两个点对偶网格的顶点排序和五边形地块的寻路边界。后续可以继续扩展的方向非常多加上 Lod 调度让行星可以从太空无缝切入地表引入资源分布噪声让采矿玩法有据可依加上轴倾斜和气候带把地形生成升级成生态生成。如果再把 Job System 和 Burst 编译器接进去完全可以在中等配置的笔记本上批量生成几十颗可探索星球。方案不复杂先跑通一个小型六边形球再做噪声、再做地块逻辑。建议收藏备用等你把第一个会转的六边形星球跑出来后面的扩张空间会非常开阔。