ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Unity切割模型实战:从Mesh切割到凸包封口与性能优化

Unity切割模型实战:从Mesh切割到凸包封口与性能优化 简介这份Unity切割模型案例面向游戏引擎初学者与希望掌握物理交互的开发者围绕“模型切割”这一常见需求提供可运行的实践项目。案例重点讲解碰撞检测、鼠标左键蓄力与右键触发切割的交互逻辑以及通过修改Mesh顶点与索引数据实现模型分割的完整思路帮助读者理解从输入监听到网格重建的核心流程。资源包共595个文件约22.05MB包含30个C#脚本、15个FBX模型、15个动画文件、46个材质与46个资源文件以及着色器、预制体、贴图等配套内容覆盖脚本逻辑、美术资源与场景配置多个层面。目前已有1310人学习下载。通过研究其中的脚本与场景组织读者可掌握切割路径计算、Mesh更新与实时显示等关键环节并将这套方案迁移到自己的Unity项目中。1. 从一块完整墙体到满地碎块unity切割模型案例到底在切什么做 FPS 打墙、动作游戏断肢、解谜游戏劈箱子绕不开一个需求模型不能只是播个动画假装碎了得真的被切开露出截面碎块还能各自飞出去。这就是 unity 切割模型案例要解决的事。它切的是网格Mesh不是贴图也不是把模型藏起来换一个预先做好的碎块。核心动作是运行时拿到一个 MeshFilter 里的网格数据用一个平面去和它求交把三角形重新分组生成两个甚至多个新网格再补上截面。适合谁做过基础 Unity 脚本、知道 Mesh 是什么、想给游戏加真实破坏效果但不想买昂贵插件的开发者。新手能跟着跑通最小案例熟手能看清凸包、封口、性能这几个边界。2. 切开一个 Mesh 到底动了哪些数据顶点、三角形与截面封口2.1 为什么不能直接改顶点数组Unity 的 Mesh 由几个数组构成vertices顶点位置、triangles三角形索引每三个一组、normals法线、uv贴图坐标。切割的本质是遍历每个三角形判断它的三个顶点在切割平面的哪一侧。全在正侧的原样保留全在负侧的归到另一半跨越平面的三角形被切成一个或多个小三角形交点作为新顶点插入。这里有个血泪经验直接改 vertices 数组而不重建 triangles网格会变成一团乱麻因为索引全错位了。正确做法是准备两个新的顶点列表和索引列表边遍历边往里塞。2.2 平面方程与顶点分类切割平面用数学表达就是n · p d 0n 是法线d 是到原点的距离。判断一个顶点在哪侧代入求值大于 0 在正侧小于 0 在负侧等于 0 在平面上。Unity 里可以用Plane结构体它自带GetSide和Raycast但自己算更可控。下面是最小可跑的分类逻辑// 判断顶点相对于切割平面的位置 // planeNormal 已归一化planePoint 是平面上任意一点 float SignedDistance(Vector3 planeNormal, Vector3 planePoint, Vector3 vertex) { // 点到平面的有符号距离正负代表两侧 return Vector3.Dot(planeNormal, vertex - planePoint); } // 对单个三角形做切割返回正侧和负侧的顶点列表 void SplitTriangle(Vector3 a, Vector3 b, Vector3 c, Vector3 n, Vector3 p, ListVector3 posList, ListVector3 negList) { float da SignedDistance(n, p, a); float db SignedDistance(n, p, b); float dc SignedDistance(n, p, c); // 三个点同侧的情况直接归类不产生交点 if (da 0 db 0 dc 0) { posList.Add(a); posList.Add(b); posList.Add(c); return; } if (da 0 db 0 dc 0) { negList.Add(a); negList.Add(b); negList.Add(c); return; } // 跨侧情况求边与平面的交点按拓扑重新组三角形 // 这里省略具体插值核心是 Lerp(a, b, da / (da - db)) }这段代码的关键参数是planeNormal和planePoint。法线方向决定哪边算正侧一般取刀具朝向或子弹飞行方向。SignedDistance返回值的绝对值大小不影响分类只影响插值比例。插值时用da / (da - db)作为 tVector3.Lerp(a, b, t)得到交点这个 t 必须夹在 0 到 1 之间否则说明两个点在同一侧不该走到插值分支。很多新手在这里翻车是因为没先判断同侧就直接插值结果算出平面外的点。2.3 截面封口不做这步切面是空的切开后如果不封口从切口看进去模型是空心的像纸糊的。封口要做的是收集所有落在切割平面上的交点把它们按顺序连成一个多边形再三角化。常见做法是先把交点投影到平面局部坐标系按角度排序然后用耳切法或简单的扇形三角化。对于凸多边形扇形三角化就够// 把平面上的交点按绕平面法线的角度排序后扇形三角化 // points 已投影到平面局部二维坐标 Listint TriangulateFan(ListVector2 points) { Listint tris new Listint(); // 以第一个点为扇心依次连接相邻点 for (int i 1; i points.Count - 1; i) { tris.Add(0); tris.Add(i); tris.Add(i 1); } return tris; }参数说明points必须已经按逆时针或顺时针排好序否则三角形会翻面。排序时用Mathf.Atan2求每个点相对质心的角度再Sort。封口网格的法线统一朝切割方向否则光照会黑。这一步是 unity 切割模型案例里最容易出玄学 bug 的地方截面时有时无多半是排序方向反了或者质心算错。3. 从零跑通一个可切割立方体脚本、材质与切割触发3.1 场景搭建与最小脚本挂载先建一个 Cube去掉 BoxCollider 换成 MeshColliderconvex 勾上否则运行时改网格会报错。新建MeshCutter.cs挂上去。核心流程是检测到切割输入取当前 MeshFilter.sharedMesh调用切割函数把返回的两个网格分别赋给原物体和一个新生成的物体。新物体复制原材质位置保持一致。下面是一个可抄的切割入口using UnityEngine; using System.Collections.Generic; public class MeshCutter : MonoBehaviour { public Vector3 cutNormal Vector3.up; // 切割平面法线 public Vector3 cutPoint; // 切割平面经过的点 void Update() { if (Input.GetKeyDown(KeyCode.Space)) { DoCut(); } } void DoCut() { MeshFilter mf GetComponentMeshFilter(); Mesh source mf.sharedMesh; // 切割结果正侧和负侧两个网格 Mesh posMesh, negMesh; MeshSplitter.Split(source, cutNormal.normalized, cutPoint, out posMesh, out negMesh); mf.mesh posMesh; // 生成负侧物体 GameObject negObj new GameObject(CutPiece); negObj.transform.position transform.position; negObj.transform.rotation transform.rotation; negObj.transform.localScale transform.localScale; negObj.AddComponentMeshFilter().mesh negMesh; negObj.AddComponentMeshRenderer().material GetComponentMeshRenderer().material; negObj.AddComponentMeshCollider().convex true; } }逻辑说明cutNormal和cutPoint决定切哪儿、往哪切。MeshSplitter.Split是你要实现的静态方法内部就是第 2 章讲的分类加封口。注意mf.mesh而不是sharedMesh因为要生成独立实例否则会污染原始资源。新物体必须复制 transform 的缩放否则碎块大小对不上。MeshCollider 的 convex 必须为 trueUnity 对非凸网格碰撞体在运行时修改有限制。3.2 切割平面的三种常见来源切割平面不是随便定的实际项目里通常来自三种输入。第一种是武器挥砍用武器碰撞点的位置和武器朝向叉乘角色朝向得到法线。第二种是子弹射击用射线检测的 hit.point 和 hit.normal但注意 hit.normal 是表面法线直接当切割法线会把模型沿着表面削一层通常要取射线方向作为法线。第三种是鼠标拖拽划线把屏幕坐标转世界坐标取两个点的连线方向作为平面内方向法线用相机朝向叉乘连线。参数上cutPoint一般取碰撞点cutNormal取运动方向。如果切出来方向反了把法线取负即可不用改其他逻辑。3.3 材质与 UV 的继承切割后新顶点没有 UV如果不处理截面贴图会拉伸成一条线。常见做法是给截面单独一个材质用世界坐标或局部坐标做平面映射。简单方案是给封口三角形单独一个 submesh赋一个纯色或网格纹理材质。UV 可以按交点在平面上的二维坐标直接映射// 为封口顶点生成 UV基于平面局部坐标 Vector2 uv new Vector2( Vector3.Dot(point - planePoint, planeRight), Vector3.Dot(point - planePoint, planeUp) ) * uvScale;planeRight和planeUp是平面内两个正交基uvScale控制贴图密度。这一步不做截面就是纯色做了才有木纹、石纹。注意原网格的 UV 要原样保留给非截面部分别混用。4. 避坑与排查切割模型时最常见的五类翻车4.1 切完模型变黑或法线错乱现象切割后碎块表面全黑或者光照方向明显不对。原因新生成的三角形顶点顺序反了导致法线朝内。Unity 用左手坐标系三角形正面是顺时针。解决在生成索引时检查三角形法线方向用Vector3.Cross(b - a, c - a)和期望法线点乘小于 0 就交换两个索引。封口三角形的法线要单独设为切割方向。4.2 截面封口出现破洞或重叠现象切口处有的地方封上了有的地方是空的或者出现重叠的三角面。原因交点排序错误或者同一个交点被重复加入。解决用字典按位置去重容差取 0.0001。排序时先算所有交点的质心再按Mathf.Atan2(p.y - center.y, p.x - center.x)排序。如果多边形是凹的扇形三角化会出错需要换成耳切法。4.3 运行时改 Mesh 报 Mesh is not readable现象一调用切割就报错提示网格不可读。原因模型导入时没勾 Read/Write Enabled。解决在模型导入设置里勾上 Read/Write Enabled或者运行时用Mesh.AcquireReadOnlyMeshData读取再重建。注意勾上会增加内存占用移动端要权衡。4.4 碎块碰撞体穿透或抖动现象切下来的碎块掉到地上穿模或者两个碎块互相抖动。原因MeshCollider 的 convex 没勾或者碎块太薄导致物理引擎算不准。解决勾 convex给碎块加 Rigidbody 时把 collisionDetectionMode 设为 Continuous薄片碎块可以加一个略大的 BoxCollider 兜底。4.5 连续切割性能骤降现象切了十几刀后帧率掉到个位数。原因每次切割都新建 Mesh 和 GameObject顶点数指数增长GC 压力大。解决限制切割次数碎块小于一定体积就销毁用对象池复用碎块切割计算放到 Job System 或分帧执行。顶点数超过 65535 要换 UInt16 索引格式否则会溢出。5. 进阶用凸包封口和分帧切割把案例推到能用前面讲的扇形封口只适合凸多边形真实模型切出来的截面往往是凹的甚至带孔洞。我一般会引入凸包算法把所有交点丢进去算凸包用凸包三角形封口虽然会多封一点但绝不会破洞视觉上完全能接受。Unity 的Physics没有直接暴露凸包接口但可以用MeshCollider.convex true让 Unity 自己算或者移植一份 QuickHull。下面是一个分帧切割的调度思路// 把切割任务拆到多帧避免单帧卡顿 IEnumerator CutOverFrames(Mesh source, Vector3 n, Vector3 p) { // 第一帧分类顶点 yield return null; // 第二帧求交点并排序 yield return null; // 第三帧生成网格并封口 yield return null; // 第四帧赋给物体并处理碰撞 }参数上每帧处理的三角形数量控制在 2000 以内移动端减到 500。凸包封口的容差取 0.001太小会把共面点算成凸包顶点太大封口会缩水。验证方法很简单切一个带凹槽的模型从侧面看截面是否完整再用Mesh.RecalculateNormals后检查有没有黑面。我自己的习惯是任何切割逻辑先在编辑器里用 Gizmos 画出切割平面和交点确认几何对了再上物理。这个案例值不值得做如果你的游戏核心玩法包含破坏那自己实现切割比买插件更可控性能也能按需裁剪。希望帮到你。本文还有配套的精品资源点击获取
返回列表