简介:这份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 = 0,n 是法线,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, List<Vector3> posList, List<Vector3> 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)作为 t,Vector3.Lerp(a, b, t)得到交点,这个 t 必须夹在 0 到 1 之间,否则说明两个点在同一侧,不该走到插值分支。很多新手在这里翻车,是因为没先判断同侧就直接插值,结果算出平面外的点。
2.3 截面封口:不做这步切面是空的
切开后如果不封口,从切口看进去模型是空心的,像纸糊的。封口要做的是:收集所有落在切割平面上的交点,把它们按顺序连成一个多边形,再三角化。常见做法是先把交点投影到平面局部坐标系,按角度排序,然后用耳切法或简单的扇形三角化。对于凸多边形,扇形三角化就够:
// 把平面上的交点按绕平面法线的角度排序后扇形三角化 // points 已投影到平面局部二维坐标 List<int> TriangulateFan(List<Vector2> points) { List<int> tris = new List<int>(); // 以第一个点为扇心,依次连接相邻点 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 换成 MeshCollider(convex 勾上,否则运行时改网格会报错)。新建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 = GetComponent<MeshFilter>(); 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.AddComponent<MeshFilter>().mesh = negMesh; negObj.AddComponent<MeshRenderer>().material = GetComponent<MeshRenderer>().material; negObj.AddComponent<MeshCollider>().convex = true; } }逻辑说明:cutNormal和cutPoint决定切哪儿、往哪切。MeshSplitter.Split是你要实现的静态方法,内部就是第 2 章讲的分类加封口。注意mf.mesh而不是sharedMesh,因为要生成独立实例,否则会污染原始资源。新物体必须复制 transform 的缩放,否则碎块大小对不上。MeshCollider 的 convex 必须为 true,Unity 对非凸网格碰撞体在运行时修改有限制。
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 画出切割平面和交点,确认几何对了再上物理。这个案例值不值得做?如果你的游戏核心玩法包含破坏,那自己实现切割比买插件更可控,性能也能按需裁剪。希望帮到你。
本文还有配套的精品资源,点击获取