如何通过代码实现可控制vertices的capsule mesh程序化生成?
程序化生成胶囊体网格(Capsule Mesh)的实现方案
胶囊体网格的生成逻辑非常成熟,完全可以在Runtime阶段自行实现,核心思路是将胶囊体拆分为上半球、中间圆柱体、下半球三个部分分别生成后拼接,保证衔接处顶点完全重合即可避免接缝,以下是具体实现方案:
核心实现步骤
- 先定义可配置的基础参数:半球半径r、圆柱体高度h、径向分段数radialSegments、半球轴向分段数hemisphereSegments、圆柱体轴向分段数cylinderSegments,可根据精度需求自由调整参数
- 生成上半球顶点:以
(0, h/2, 0)为球心,纬度取值范围为0~π/2,每个纬度环的半径为r*cos(纬度角),y坐标为h/2 + r*sin(纬度角),x/z坐标按经度角循环计算即可 - 生成中间圆柱体顶点:上下两个端环的顶点坐标和上下半球的赤道环顶点完全对齐,y坐标从
h/2到-h/2按圆柱轴向分段数均匀分布,每个高度环的半径固定为r,x/z计算逻辑和半球赤道环保持一致 - 生成下半球顶点:以
(0, -h/2, 0)为球心,纬度取值范围为π/2~π,其余计算逻辑和上半球完全一致 - 计算三角面索引:分别为三个部分拼接三角索引,保证所有面的环绕顺序统一,避免背面剔除出现异常
- 补充附属属性:法线可直接通过顶点相对于对应球心/圆柱中心的归一化向量快速计算,也可根据需求生成UV、切线等属性
核心代码示例(C# 逻辑通用)
Mesh GenerateCapsuleMesh(float radius, float height, int radialSegments = 16, int hemiSegments = 8, int cylinderSegments = 8) { Mesh mesh = new Mesh(); List<Vector3> vertices = new List<Vector3>(); List<int> triangles = new List<int>(); // 生成上半球顶点 for (int lat = 0; lat <= hemiSegments; lat++) { float latAngle = Mathf.PI * 0.5f * lat / hemiSegments; float sinLat = Mathf.Sin(latAngle); float cosLat = Mathf.Cos(latAngle); for (int lon = 0; lon <= radialSegments; lon++) { float lonAngle = 2 * Mathf.PI * lon / radialSegments; float sinLon = Mathf.Sin(lonAngle); float cosLon = Mathf.Cos(lonAngle); vertices.Add(new Vector3(radius * cosLat * cosLon, height/2 + radius * sinLat, radius * cosLat * sinLon)); } } // 生成中间圆柱体顶点 int cylinderVertStart = vertices.Count; for (int hSeg = 1; hSeg < cylinderSegments; hSeg++) { float y = height/2 - (float)hSeg / cylinderSegments * height; for (int lon = 0; lon <= radialSegments; lon++) { float lonAngle = 2 * Mathf.PI * lon / radialSegments; vertices.Add(new Vector3(radius * Mathf.Cos(lonAngle), y, radius * Mathf.Sin(lonAngle))); } } // 生成下半球顶点 for (int lat = 1; lat <= hemiSegments; lat++) { float latAngle = Mathf.PI * 0.5f + Mathf.PI * 0.5f * lat / hemiSegments; float sinLat = Mathf.Sin(latAngle); float cosLat = Mathf.Cos(latAngle); for (int lon = 0; lon <= radialSegments; lon++) { float lonAngle = 2 * Mathf.PI * lon / radialSegments; float sinLon = Mathf.Sin(lonAngle); float cosLon = Mathf.Cos(lonAngle); vertices.Add(new Vector3(radius * cosLat * cosLon, -height/2 + radius * sinLat, radius * cosLat * sinLon)); } } // 三角面索引计算:相邻两个环的每4个连续顶点拼成2个三角面,按分段逐个拼接即可 int vertsPerLoop = radialSegments + 1; // 上半球索引 for (int lat = 0; lat < hemiSegments; lat++) { for (int lon = 0; lon < radialSegments; lon++) { int cur = lat * vertsPerLoop + lon; int next = cur + vertsPerLoop; triangles.Add(cur); triangles.Add(next); triangles.Add(cur + 1); triangles.Add(cur + 1); triangles.Add(next); triangles.Add(next + 1); } } // 圆柱、下半球索引逻辑和上半球一致,可复用相同逻辑 // ... 省略剩余索引拼接代码 mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); mesh.RecalculateNormals(); mesh.RecalculateBounds(); return mesh; }
注意事项
- 拼接三个部分时必须保证衔接处顶点坐标完全一致,否则会出现可见接缝
- Runtime生成时建议对相同参数的网格做缓存复用,避免频繁生成导致GC
- 要动态修改顶点属性时,直接获取
Mesh.vertices数组修改后重新赋值给网格,调用RecalculateBounds、RecalculateNormals即可生效 - 性能敏感场景可以降低分段数减少顶点数,平衡性能和显示效果
内容的提问来源于stack exchange,提问作者user15944701
相关产品推荐
相关产品推荐

