Unity中如何为SkinnedMesh对象创建3D bounding box?
Unity SkinnedMeshRenderer 3D包围盒生成方案
针对带蒙皮网格渲染器的对象,直接读取组件自带的bounds属性是预计算的静态值,骨骼动画播放时顶点位移超出初始范围就会出现包围盒不准、甚至模型被视锥体错误剔除的问题,必须基于实时蒙皮后的顶点数据计算才准确。
关键实现步骤
- 缓存目标对象的
SkinnedMeshRenderer组件引用,新建临时Mesh实例用于存储烘焙后的蒙皮网格,禁止直接修改项目内的原始Mesh资源,避免资源污染。 - 在
LateUpdate(骨骼动画更新完成的生命周期节点)调用BakeMesh接口,将当前帧所有骨骼动画、BlendShape形变计算完成后的网格数据写入临时Mesh,这一步是拿到准确蒙皮顶点的核心。 - 提取临时Mesh的所有顶点数据,根据需求选择局部/世界坐标系,遍历计算三个轴向的顶点坐标最小、最大值。
- 基于轴向极值生成
Bounds结构,即为当前帧准确的3D轴对齐包围盒;如果需要定向包围盒(OBB),额外基于顶点协方差矩阵计算特征向量得到包围盒朝向即可。 - 计算得到的顶点列表、包围盒中心、尺寸、极值点可以直接存入自定义数据结构,后续屏幕打印、JSON导出逻辑可直接读取该部分数据。
核心功能代码片段
using UnityEngine; using System.Collections.Generic; [RequireComponent(typeof(SkinnedMeshRenderer))] public class SkinnedMeshBBoxCalculator : MonoBehaviour { private SkinnedMeshRenderer _skinnedRenderer; private Mesh _tempBakedMesh; // 对外暴露计算结果 public Bounds worldSpaceAABB; public List<Vector3> bakedLocalVertices = new List<Vector3>(); void Awake() { _skinnedRenderer = GetComponent<SkinnedMeshRenderer>(); _tempBakedMesh = new Mesh(); } void LateUpdate() { // 烘焙当前帧蒙皮结果到临时网格 _skinnedRenderer.BakeMesh(_tempBakedMesh); bakedLocalVertices.Clear(); _tempBakedMesh.GetVertices(bakedLocalVertices); if (bakedLocalVertices.Count == 0) return; // 初始化世界空间极值 Vector3 firstVertWorld = transform.TransformPoint(bakedLocalVertices[0]); Vector3 minPos = firstVertWorld; Vector3 maxPos = firstVertWorld; // 遍历所有顶点计算AABB极值 for (int i = 1; i < bakedLocalVertices.Count; i++) { Vector3 vertWorld = transform.TransformPoint(bakedLocalVertices[i]); minPos = Vector3.Min(minPos, vertWorld); maxPos = Vector3.Max(maxPos, vertWorld); } // 生成最终包围盒 worldSpaceAABB.SetMinMax(minPos, maxPos); } // 场景视图下绘制包围盒调试 void OnDrawGizmosSelected() { if (worldSpaceAABB.size == Vector3.zero) return; Gizmos.color = Color.cyan; Gizmos.DrawWireCube(worldSpaceAABB.center, worldSpaceAABB.size); } void OnDestroy() { // 销毁临时网格避免内存泄漏 if (_tempBakedMesh != null) Destroy(_tempBakedMesh); } }
性能提示:如果不需要逐帧跟随动画更新包围盒(比如仅计算T-Pose下的固定包围盒),把
LateUpdate内的计算逻辑挪到Start中执行一次即可,能省掉每帧的网格烘焙和顶点遍历开销。
顶点数据说明:bakedLocalVertices存储的是蒙皮后模型局部空间的顶点坐标,如果需要世界空间顶点列表,遍历顶点时调用transform.TransformPoint转换后单独存储即可。
内容的提问来源于stack exchange,提问作者BrianAI
相关产品推荐
相关产品推荐

