简介这份资源是面向Unity初学者与游戏开发爱好者的模型切割实战案例围绕“只能切开自带基础模型”的设定演示如何通过碰撞检测与网格数据修改实现切割效果。案例重点讲解鼠标左键蓄力、右键触发的交互设计涉及Input Manager配置、C#脚本监听输入、Mesh Collider交点计算、顶点与三角形数组重建等核心环节并包含切割工具与目标模型两个新建对象的物理组件搭建思路。资源包共595个文件以282个meta、62张jpg、46个mat材质、46个asset、43张tif贴图、30个cs脚本、15个fbx模型及15个anim动画为主另有unity场景、prefab预制体与shadergraph着色器压缩包约22.05MB目录结构完整。目前已有1311人学习下载适合希望理解模型切割原理、掌握碰撞检测与网格操作、并可将方案迁移到自身项目的开发者参考实践。1. 从一次切水果到网格切割unity切割模型案例到底在切什么很多人第一次在 Unity 里做切割模型都是被一个很具体的需求逼出来的玩家拿刀划过一块木板、一颗水果、一面墙模型要沿着划痕裂开断面还得是实心的不能是空壳。这个需求听起来像特效实际是几何问题。Unity 本身没有内置的运行时网格切割组件所以「unity切割模型案例」这类内容本质上讲的是在运行时拿到一个 Mesh用一条平面或一条轨迹去切它重新生成两个或多个带完整断面的新 Mesh再交给 MeshFilter 和 MeshCollider 去渲染和碰撞。它适合三类人做休闲切割玩法水果、绳子、纸片的玩法程序做破坏效果墙体、地板、载具的技术美术以及做数字孪生、工业拆解演示需要按剖切面看内部结构的工程方向开发者。核心难点从来不是「切开」而是切开之后断面怎么补、UV 和法线怎么接、碰撞体怎么跟着换。下面按我实际落地的顺序把这条链路拆开讲。2. 切割的几何原理与最小可跑通方案平面切三角面2.1 为什么是「平面切三角形」而不是「切模型」Unity 的 Mesh 在底层就是一堆三角形每个三角形由三个顶点索引组成。所谓切割模型落到最底层就是用一个无限延伸的平面法线 平面上一点去和每一个三角形求交把被穿过的三角形一分为二一侧的三角形归到 A 网格另一侧归到 B 网格。没被穿过的三角形整块归到它所在的那一侧。这里有个容易被忽略的点平面切割是「一刀切到底」它不关心你的刀轨迹是直线还是曲线。想让刀痕是曲线常见做法是把刀轨迹离散成多个小平面或者干脆用「刀轨迹扫过的面」去切但那是另一个量级的复杂度。绝大多数 unity切割模型案例第一步都是先跑通单平面切割再谈轨迹。选型上运行时切割有三条路一是纯 CPU 逐三角形重建 Mesh最通用本文走这条二是用 Shader 做视觉切割但碰撞不真实适合纯特效三是用第三方破坏插件如基于 Voronoi 的碎裂。如果你要的是「沿刀痕裂开且断面实心」CPU 重建 Mesh 是唯一稳的路。2.2 最小可跑通代码单平面切割一个 Cube先准备一个带 MeshFilter 和 MeshCollider 的物体挂上下面的脚本。核心思路是遍历所有三角形按顶点在平面哪一侧分类跨平面的三角形做插值切分。using System.Collections.Generic; using UnityEngine; public class MeshCutter : MonoBehaviour { // 切割平面法线 平面上一点 public Vector3 planeNormal Vector3.up; public Vector3 planePoint Vector3.zero; // 判断点在平面哪一侧0 正面0 背面 float SideOfPlane(Vector3 p) { return Vector3.Dot(planeNormal, p - planePoint); } // 沿边 ab 求与平面的交点t 为插值系数 Vector3 Intersect(Vector3 a, Vector3 b, float sideA, float sideB) { float t sideA / (sideA - sideB); return Vector3.Lerp(a, b, t); } public void Cut() { Mesh mesh GetComponentMeshFilter().mesh; Vector3[] verts mesh.vertices; int[] tris mesh.triangles; // 正负两侧的顶点、三角形、以及断面边用于补面 ListVector3 posVerts new ListVector3(); Listint posTris new Listint(); ListVector3 negVerts new ListVector3(); Listint negTris new Listint(); ListVector3 cutEdges new ListVector3(); // 断面上的边成对存 for (int i 0; i tris.Length; i 3) { Vector3 a verts[tris[i]]; Vector3 b verts[tris[i 1]]; Vector3 c verts[tris[i 2]]; float sa SideOfPlane(a), sb SideOfPlane(b), sc SideOfPlane(c); // 三个点同侧整块归过去 if (sa 0 sb 0 sc 0) { AddTri(posVerts, posTris, a, b, c); continue; } if (sa 0 sb 0 sc 0) { AddTri(negVerts, negTris, a, b, c); continue; } // 跨平面按符号拆成 12 或 21 两种情形 // 这里只演示通用做法把三角形按平面切成多边形再三角化 SplitTriangle(a, b, c, sa, sb, sc, posVerts, posTris, negVerts, negTris, cutEdges); } // 用断面边补出实心断面三角化 FillCap(cutEdges, posVerts, posTris, planeNormal); FillCap(cutEdges, negVerts, negTris, -planeNormal); ApplyMesh(gameObject, posVerts, posTris); // 另一半通常 Instantiate 一个新物体再 ApplyMesh } void AddTri(ListVector3 vs, Listint ts, Vector3 a, Vector3 b, Vector3 c) { int i vs.Count; vs.Add(a); vs.Add(b); vs.Add(c); ts.Add(i); ts.Add(i 1); ts.Add(i 2); } void SplitTriangle(Vector3 a, Vector3 b, Vector3 c, float sa, float sb, float sc, ListVector3 pv, Listint pt, ListVector3 nv, Listint nt, ListVector3 edges) { // 把三个点按顺序排成多边形插入交点再分别归到两侧 // 具体实现略长核心是找出唯一异号点求两条边交点得到两个新点 // 异号点单独成一个小三角形另外两个点 两个交点组成四边形再切成两个三角形 // 交点连成的线段就是断面边加入 edges } void FillCap(ListVector3 edges, ListVector3 vs, Listint ts, Vector3 normal) { // 把断面边首尾相接成环再用耳切法或扇形三角化补面 // 注意法线方向要和 normal 一致否则断面会黑 } void ApplyMesh(GameObject go, ListVector3 vs, Listint ts) { Mesh m new Mesh(); m.SetVertices(vs); m.SetTriangles(ts, 0); m.RecalculateNormals(); m.RecalculateBounds(); go.GetComponentMeshFilter().mesh m; go.GetComponentMeshCollider().sharedMesh m; } }逻辑说明SideOfPlane用点积判断顶点在平面哪一侧这是所有切割的判定基础。SplitTriangle是核心跨平面的三角形必须按符号拆成「一个小三角形 一个四边形」四边形再三角化。FillCap负责把断面边连成环并补面这一步不做切开的模型就是空壳从侧面能看到内部。ApplyMesh里必须同时更新 MeshCollider否则视觉切开了、碰撞还是整块玩家会穿模。参数说明planeNormal决定切面朝向planePoint决定切面位置两者一起定义平面。实际项目里这两个值通常来自刀的运动用刀的两个连续位置求方向用刀当前位置作为平面上一点。注意RecalculateNormals之后断面法线可能朝内需要根据normal手动翻转否则断面是黑的。3. 断面补面与 UV、法线的三个必调参数3.1 断面三角化耳切法还是扇形法断面补面是把cutEdges里成对的交点连成一个或多个闭合环再三角化。如果断面是凸多边形扇形三角化固定一个点连所有其他点就够如果是凹多边形扇形法会切出模型外面的三角形必须用耳切法Ear Clipping。我一般会先把交点按绕平面法线的角度排序保证环是有序的然后判断凸凹。判断方法对每个顶点看它和前一个点、后一个点组成的叉积方向是否和平面法线一致一致为凸不一致为凹。有凹点就上耳切没有就扇形。这一步翻车的典型现象是断面出现「飞出去的三角形」从某些角度看像一根刺。// 按平面法线方向对断面点排序保证环有序 ListVector3 SortRing(ListVector3 ring, Vector3 normal) { Vector3 center Vector3.zero; foreach (var p in ring) center p; center / ring.Count; // 建立平面局部坐标系 Vector3 u Vector3.Normalize(ring[0] - center); Vector3 v Vector3.Cross(normal, u); ring.Sort((p1, p2) { Vector2 a new Vector2(Vector3.Dot(p1 - center, u), Vector3.Dot(p1 - center, v)); Vector2 b new Vector2(Vector3.Dot(p2 - center, u), Vector3.Dot(p2 - center, v)); return Mathf.Atan2(a.y, a.x).CompareTo(Mathf.Atan2(b.y, b.x)); }); return ring; }逻辑说明先求断面点集的中心再用平面法线和第一个点建立局部二维坐标系最后按极角排序。排序是补面的前提环乱了三角化必错。参数上normal必须和补面时用的法线一致否则排序方向反了三角形绕序也会反。3.2 UV 和法线怎么接才不穿帮切割后新生成的顶点UV 和法线不能丢。UV 的常见做法是对插值产生的新顶点UV 也用同样的插值系数t在两端 UV 之间 Lerp。法线则更麻烦因为断面是全新表面它的法线应该等于切割平面法线或反向而不是插值出来的。必调参数有三个一是插值系数t的精度用float没问题但要注意sideA - sideB接近 0 时会除零加一个极小值保护二是断面法线方向正面断面用planeNormal背面断面用-planeNormal三是 UV 的 wrap 模式如果原模型 UV 有接缝插值后可能出现拉伸必要时对断面单独给一套 UV比如按断面点距离映射。提示断面 UV 如果直接用插值纹理在断面上会被拉长。做木板、水果这类需要断面纹理的建议按断面点到中心的距离重新算一套 UV。3.3 碰撞体更新MeshCollider 的坑视觉切开后MeshCollider.sharedMesh必须换成新 Mesh否则碰撞还是旧的。但 MeshCollider 有个限制运行时频繁重建开销大尤其是高面数模型。常见优化是给切割后的碎片用凸包碰撞体Convex MeshCollider或者干脆用几个 BoxCollider 近似。另一个坑是sharedMesh和mesh的区别mesh会实例化一份新 MeshsharedMesh直接引用。切割后每个碎片都是独立 Mesh用sharedMesh赋值即可但要注意旧 Mesh 要手动Destroy否则内存会涨。做长时间切割玩法比如连续切几百刀不回收 Mesh 会明显掉帧这也是粒子特效内存泄露 unity 这类问题在切割场景里的变体。4. 从单平面到刀轨迹连续切割与性能取舍4.1 刀轨迹离散成多平面单平面切割只能切一刀真实玩法里刀是移动的。常见做法是把刀在一帧内的位移离散成若干小段每段用一个平面去切或者用「刀扫过的面」去切。前者实现简单但切痕会有台阶后者更平滑但求交复杂。我一般会限制每帧最多切 N 次比如 3 次超过就合并。因为每次切割都要重建 Mesh切太多次单帧开销爆炸。参数上N 取 2 到 4 之间比较稳具体看模型面数和目标机型。4.2 面数控制与 LOD切割会让面数增加因为每个被穿过的三角形都变成多个。一个 1000 面的模型切十刀可能变成 3000 面。如果不控制连续切割很快会把面数推到几万移动端直接跪。控制手段有三个一是切割前对模型做减面用低模做切割二是限制切割次数切到一定次数后碎片不再可切三是给碎片加 LOD远处用低模。unity 包体优化 的思路在这里同样适用能省的面一定省切割是面数放大器。4.3 对象管理与回收每次切割产生新物体如果不管理Hierarchy 会爆炸。常见做法是用对象池管理碎片切出来的碎片超过一定数量就回收最旧的。同时要注意Mesh、Material的引用计数避免泄漏。// 简单的碎片回收超过上限就销毁最旧的 QueueGameObject fragments new QueueGameObject(); int maxFragments 50; void RegisterFragment(GameObject frag) { fragments.Enqueue(frag); if (fragments.Count maxFragments) { GameObject old fragments.Dequeue(); Destroy(old.GetComponentMeshFilter().mesh); // 先销毁 Mesh Destroy(old); } }逻辑说明用队列记录碎片超过上限就销毁最旧的。注意先销毁 Mesh 再销毁物体否则 Mesh 会残留。参数maxFragments根据目标平台定移动端建议 30 到 50PC 可以到 100 以上。5. 避坑与排查切割模型最常见的 5 个翻车现场5.1 切完模型变黑或断面透明现象切开后断面是黑的或者从侧面能看到模型内部。 原因断面没有补面或者补面法线方向反了。 解决检查FillCap是否被调用以及断面三角形绕序是否和法线一致。用RecalculateNormals后如果还黑手动把断面法线设为planeNormal。5.2 切割后碰撞穿模现象视觉上切开了但角色还能站在原来的整块上或者碎片掉不下去。 原因MeshCollider 没有更新或者用了mesh而不是sharedMesh。 解决切割后立即给MeshCollider.sharedMesh赋新 Mesh并调用Physics.SyncTransforms。如果碎片是凸的改用convex true的 MeshCollider 性能更好。5.3 连续切割后帧率骤降现象切了几十刀后卡顿明显。 原因Mesh 没有回收面数累积GC 频繁。 解决用对象池管理碎片限制最大碎片数及时Destroy旧 Mesh。同时限制每帧切割次数。5.4 断面 UV 拉伸严重现象断面上的纹理被拉成条纹。 原因新顶点 UV 直接用插值没有按断面重新映射。 解决对断面单独生成 UV按断面点到中心的距离或按平面局部坐标映射。5.5 切割平面判定出现除零现象偶尔报 NaN 或三角形消失。 原因sideA - sideB接近 0 时除零。 解决在Intersect里加保护float denom sideA - sideB; if (Mathf.Abs(denom) 1e-6f) denom 1e-6f;。6. 进阶技巧用切割做剖切视图与验证方法做数字孪生或工业演示时切割不只是破坏还可以做剖切视图用一个可拖动的平面实时切开模型看内部结构。这种场景下切割频率低但精度要求高断面需要显示内部材质。我的做法是给断面单独一个 Material用切割平面的局部坐标做 UV这样断面纹理不会随模型旋转而错乱。验证切割是否正确我一般用三个检查一是看三角形数量切割后总面数应该约等于原面数加上断面补面数如果暴涨说明切分逻辑有重复二是看包围盒两个碎片的包围盒并集应该约等于原包围盒三是看碰撞用射线从两侧打应该能打到断面。// 验证切割后两个碎片的包围盒并集应接近原包围盒 Bounds CombineBounds(GameObject a, GameObject b) { Bounds ba a.GetComponentRenderer().bounds; Bounds bb b.GetComponentRenderer().bounds; ba.Encapsulate(bb); return ba; }逻辑说明用Encapsulate合并两个碎片的包围盒和原包围盒对比。如果偏差过大说明切分时顶点归属错了。这个方法比逐顶点检查快适合快速验证。最后一个习惯每次改切割逻辑先在一个简单 Cube 上跑确认断面、碰撞、UV 都对再换复杂模型。我早期直接在角色模型上试切出来一堆破面排查了半天才发现是模型本身有非流形边。切割对模型拓扑有要求非流形、重复顶点、未合并的边都会让结果不可预期。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?