资讯动态

Unity运行时Mesh切割:原理、脚本与避坑指南

发布时间:2026/10/5 9:25:11 来源:尧图企业网站定制
简介面向 Unity 初学者的模型切割案例演示通过鼠标左键蓄力、右键触发切割的基础交互结合碰撞检测与网格数据处理实现模型分割适合学习物理模拟与网格编程的开发者。压缩包共 595 个文件约 22.05MB包含 30 个 C# 脚本、46 个材质、15 个 FBX 模型、15 段动画、纹理贴图以及 Unity 场景、预制体等目录组织便于直接导入工程对照学习。案例围绕切割工具与目标模型展开涉及网格碰撞体检测、刚体物理属性、切割路径计算、顶点与三角形拆分等关键步骤从输入监听、蓄力变量到模型实时刷新均有对应实现同时附带着色器图形、预设与若干贴图可辅助理解渲染表现。已有 1310 人学习下载。通过阅读其中的 C# 脚本和场景布局能完整掌握模型切割在物理交互与网格修改上的实现思路并迁移到自己的游戏原型中。1. 做 unity切割模型案例难的不是切而是切完那一瞬间模型碎成什么样在 Unity 里做切割模型案例最劝退的不是算法本身而是切完以后模型直接变成一根烂面条或者干脆从场景里消失。我见过不少刚开始做物理破坏、水果忍者这类玩法的团队第一版切完以后所有三角形全部绕在一起画面跟抽象画一样只能靠关掉 MeshRenderer 逃避问题。这个标题背后真正要解决的事是把一个静态 Mesh 在运行时拆成两个完整、封闭、还能继续被物理系统处理的子网格同时外观不出洞、UV 不拉伸、法线不反。它适合三类人做破坏效果的玩法开发、做训练模拟器的工具链工程师、以及想在编辑器里做自定义切分功能的扩展党。顺着这个标题往下走你会把原理、脚本、参数和坑一次踩明白。2. 切割模型的原理与选型先懂 Mesh 再写代码不然切的是玄学2.1 切割的本质是三个数组的重新分配Unity 的 Mesh 在内存里其实就是一个顶点数组、一个三角形索引数组和一套 UV / 法线数组。切割模型的本质不是“用刀切”而是“根据一个切割平面把现有三角形拆成两部分再为切面生成新的三角形把洞补上”。这段话你可以记一辈子切割就是算点、分三角形、补洞最后重新生成 Mesh。先说算点。你需要把每个三角形的三个顶点分别取出来逐个和切割平面做距离判断。如果三个到切割平面的符号一致说明整个三角形在平面一侧直接归到对应半边如果符号不一致说明三角形被平面穿过了需要在三角形边上算出交点把原来的三角形拆成两个甚至三个三角形。这一步是全部逻辑的核心。需要注意的是Unity 的 Mesh.vertices 和 Mesh.triangles 是两条平行的数组。顶点数组记录的是位置和跟随位置的一堆属性三角形索引数组只是“哪些顶点组成一个三角形”的编号。你在切割时对顶点做的任何插值都要同步处理 UV、法线和切线不能只算坐标。否则切完以后光照和贴图就会立刻翻车。2.2 运行时 Mesh 切和美术预切两条路难度差十倍做切割模型案例时你首先要想清楚用哪种方式。运行时 Mesh 切是动态的任何角度、任意位置都能切适合玩家自由发挥的水果忍者、建造破坏类游戏。美术预切则是模型师提前把物体切成几十块运行时只做“隐藏一块显示两块”的操作优势是效果完美、性能极低代价是只能按预设的路线裂开自由度和真实破坏感都大打折扣。这里给一张对比表方便你根据项目规模选型。对比项运行时 Mesh 切美术预切自由度任意平面切割固定切面性能切完需要重算 Mesh几乎零开销开发成本算法复杂边界多建模工作量大内存占用顶点数量翻倍多份预制体适用场景破坏玩法、实时模拟剧情演出、移动端轻量项目我作为一线工程师的建议是如果你做的是需要“玩家随意砍”的玩法直接走运行时 Mesh 切。如果只是让石头按固定几条裂缝碎掉老老实实用美术预切配合临时显隐别为了炫技给自己挖坑。2.3 切割平面的数学基础点、符号、插值三点定全局所有运行时切的基础是平面方程。Unity 的 Plane 类封装了最常用的功能Plane.GetSide(Vector3) 返回点在平面哪一侧Plane.GetDistanceToPoint(Vector3) 返回带符号的距离。切割时你只要把每个顶点代入距离计算就能判断它在哪一侧然后把三角形按顶点分布分到两个组。跨越切割平面的三角形需要生成新顶点。常见做法是用线性插值设 A 和 B 是两个原始顶点dA 和 dB 是它们到平面的带符号距离当 dA 和 dB 异号时交点 T A (B - A) * (dA / (dA - dB))。这个公式对位置、UV、法线同样适用千万别只插值坐标。Vector3 vertexA vertices[triA]; Vector3 vertexB vertices[triB]; float dA plane.GetDistanceToPoint(vertexA); float dB plane.GetDistanceToPoint(vertexB); float t dA / (dA - dB); Vector3 newPos Vector3.Lerp(vertexA, vertexB, t); Vector2 newUV Vector2.Lerp(uvA, uvB, t); Vector3 newNormal Vector3.Lerp(normalA, normalB, t).normalized;逻辑说明t 是由距离比例算出的插值权重它同时作用于位置、UV 和法线。用 normalized 归一化法线是为了让光照方向正确否则切面上的高光和阴影会显得脏。这里的核心参数是 dA 和 dB 的符号判定当 dA 和 dB 非常接近 0 时插值权重会跳到极值所以后续代码里要加一个容差判断。3. 用 C# 复现 Unity 切割模型的最小脚本核心函数与参数拆解3.1 搭建切割脚本骨架一个入口函数两个输出对象我一般会把切割做成一个静态类传入 Mesh 和一个 Plane返回两个 Mesh这样不管是编辑器工具还是运行时都能复用。下面是最小的入口结构public static class MeshCutter { public static void Slice(Mesh source, Plane plane, out Mesh partA, out Mesh partB) { Vector3[] vertices source.vertices; int[] triangles source.triangles; Vector2[] uvs source.uv; Vector3[] normals source.normals; ListVector3 vertsA new ListVector3(); ListVector3 vertsB new ListVector3(); ListVector2 uvsA new ListVector2(); ListVector2 uvsB new ListVector2(); ListVector3 normalsA new ListVector3(); ListVector3 normalsB new ListVector3(); Listint trisA new Listint(); Listint trisB new Listint(); // 核心处理逐三角形拆分 ProcessTriangles(source, plane, vertices, triangles, uvs, normals, vertsA, vertsB, uvsA, uvsB, normalsA, normalsB, trisA, trisB); partA BuildMesh(vertsA, trisA, uvsA, normalsA); partB BuildMesh(vertsB, trisB, uvsB, normalsB); } }逻辑说明这边把源 Mesh 的所有属性全部展开为 List方便在切割过程中动态添加。out 参数 partA 和 partB 返回两个独立的新 Mesh源 Mesh 保持不变这样调用方可以决定是替换原物体还是生成两个新物体。这一步里头没有多余的高深算法就是一个数据搬运和重新组织的过程。3.2 核心三角拆分一个三角形变成零到两个三角形接下来是核心函数 ProcessTriangles 的细节。对每个三角形取三个顶点到切割平面的距离然后根据符号分布做分支处理。常见做法是把距离转成 1 或 -1用加法判断for (int i 0; i triangles.Length; i 3) { Vector3 v0 source.vertices[triangles[i]]; Vector3 v1 source.vertices[triangles[i 1]]; Vector3 v2 source.vertices[triangles[i 2]]; float d0 plane.GetDistanceToPoint(v0); float d1 plane.GetDistanceToPoint(v1); float d2 plane.GetDistanceToPoint(v2); int side0 d0 0 ? 1 : -1; int side1 d1 0 ? 1 : -1; int side2 d2 0 ? 1 : -1; int sideSum side0 side1 side2; if (sideSum 3 || sideSum -3) { // 整个三角形在平面一侧直接归入对应列表 } else if (sideSum 1 || sideSum -1) { // 三角形被平面穿过拆成一个三角形 } else if (sideSum 0) { // 三角形被平面穿过拆成两个三角形 } }逻辑说明sideSum 的阈值是判断的关键。全在平面同一侧时 sideSum 为 3 或 -3直接搬走。sideSum 为 0 时一个顶点在一侧另外两个在另一侧需要拆成两个三角形。sideSum 为 1 或 -1 时两个顶点在一个方向、一个顶点在另一个方向只需拆成一个三角形。这里最难的是“把三角形拆成两个”的分配细节你必须保证新三角形的顶点顺序是顺时针的否则法线会反向切出来的模型会黑一半。我通常用向量叉积验证朝向遇到反向就交换三角形索引的前两位。3.3 填补切面空洞收集交点按角度排序扇形三角剖分三角形拆完以后切面上会留下一个多边形缺口。如果不补洞Mesh 就是镂空的看起来像一张被撕开的纸。补洞的常见做法是把所有新生成的交点收集起来按它们在切平面上的 2D 坐标做角度排序然后以第一个点为基准做扇形三角剖分。ListVector3 borderPoints new ListVector3(); // borderPoints 已经收集了所有新交点 Vector3 center Vector3.zero; foreach (Vector3 p in borderPoints) center p; center / borderPoints.Count; borderPoints.Sort((a, b) { Vector2 da new Vector2(a.x - center.x, a.z - center.z); Vector2 db new Vector2(b.x - center.x, b.z - center.z); return Mathf.Atan2(da.y, da.x).CompareTo(Mathf.Atan2(db.y, db.x)); }); for (int i 1; i borderPoints.Count - 1; i) { trisA.Add(borderPoints[0].vertexIndex); trisA.Add(borderPoints[i].vertexIndex); trisA.Add(borderPoints[i 1].vertexIndex); }逻辑说明排序时用投影到切割平面上的 2D 坐标计算 atan2得到的角度从小到大排列这样三角剖分不会出现交叉缠绕。代码里取的是 x 和 z但具体取哪两个轴取决于你的切割平面方向。比如切割平面是水平向上的就应该用 x 和 z竖直切割则用 x 和 y。这个细节错了剖分照样会绕成一团。3.4 三个必调参数容差、法线重算和内部材质切割脚本写完后真正影响效果的只有几个参数。你不需要过度调参但下面三个必须是可控的。第一是顶点容差。切割计算交点时浮点误差会带来微小的缝隙所以通常用一个容差值把距离极近的顶点焊接起来。常见设 0.001f太小在移动端会有缝隙太大则会把相邻三角面也焊在一起。第二是重新计算法线。源 Mesh 的法线经过插值以后只能保证平滑过渡但切面方向完全变了必须调用 mesh.RecalculateNormals() 才能让切开的边缘获得正确的光照。第三是内部材质。补出来的那部分三角形通常没有贴图需要给 MeshRenderer 设置多子材质把切面所在的 submesh 指向一个单独的“剖面材质”常见做法是给切面生成一个基础色贴图或者直接用半透明材质。Mesh newMesh new Mesh(); newMesh.vertices vertexList.ToArray(); newMesh.triangles triangleList.ToArray(); newMesh.uv uvList.ToArray(); newMesh.normals normalList.ToArray(); newMesh.RecalculateNormals(); newMesh.RecalculateBounds();逻辑说明RecalculateBounds 同样重要因为切割后 Mesh 的包围盒如果没有更新物体可能会被视锥裁剪掉表现就是切完以后一块消失。写完代码后先把 RecaluclateNormals 和 RecalculateBounds 都调用确认物体能正常显示再谈别的。4. Unity 切割模型避坑指南五处最容易翻车的 Mesh 细节4.1 切完模型直接消失三角形索引越界与包围盒未更新现象切割脚本执行完原物体不见两块新的也都不渲染场景里什么都没有。原因最常见的两个一是三角形索引数组里出现了来源不明的编号导致顶点引用越界Mesh 被 Unity 判定为非法数据二是新 Mesh 生成后没有调用 RecalculateBounds包围盒是空的渲染系统直接把物体淘汰掉。解决在 BuildMesh 函数里强制调用 newMesh.RecalculateBounds()同时打印切完后的顶点数量和三角形数量排查triangles[tri]是否始终小于 vertices.Length。我习惯在每个三角形索引写入时做一个 Validate 函数一旦发现越界就中断并输出调试信息能节省很多排查时间。4.2 切出来的边缘又黑又脏法线和 UV 没有同步插值现象切割面显示为黑色或切开处贴图被拉伸得像泼了油漆。原因新生成的交点只有位置被正确计算UV 和法线还是默认值导致光照明暗错乱贴图坐标出错。解决在生成新交点时同步对 float3 类型的 UV 和法线做线性插值。检查方法很简单在 Scene 视图开启 Mesh 显示看切面法线是否垂直于切割平面。做水果忍者效果时切面通常要朝向玩家这时可以把切面三角形的法线全部强制指向某个固定方向而不是用插值结果效果会更可控。4.3 物理碰撞体没有跟着切只有 MeshFilter 动了Collider 还停在原处现象切割后两个新的可视模型出现在场景里但玩家走过去发现仍被一个看不到的边界挡住甚至子弹打在空气上。原因切的是 MeshFilter但 MeshCollider 挂的还是旧的那份Unity 不会自动帮你把碰撞体积拆开。解决切完以后用生成的 partA 和 partB 各创建一个新的 MeshCollider把 sharedMesh 赋给对应 collider。注意切完后两个物体的 MeshCollider 需要设置 convex true否则 Unity 无法为拆分后的复杂形状生成正确的物理碰撞。整个过程是这样的partA.gameObject.AddComponentMeshCollider().sharedMesh partA然后跑一遍物理模拟确认没有穿透。4.4 顶点数超过 65535 后模型直接花掉16 位索引溢出的边界现象切一个复杂的场景模型比如雕像切到后面模型开始出现诡异的三角形乱飞顶点位置完全不对。原因旧版本 Unity 的 Mesh 默认使用 16 位索引顶点数超过 65535 以后索引不再生效三角形会引用到错误的位置。解决在生成 Mesh 前先判断顶点数量如果超过 65000则设置newMesh.indexFormat UnityEngine.Rendering.IndexFormat.UInt32;。这个改动对移动端性能有一定影响所以只在大模型或多次切割后顶点数必然会超过边界时启用。4.5 切一次卡一下GC 压力巨大临时 List 不知道该释放现象切割函数执行时 FPS 掉到个位数执行完以后内存占用不断上涨。原因切割过程中创建的 List、Vector3、Mesh 对象没有被及时释放部分旧 Mesh 还挂在场景里没有被销毁。解决用对象池管理临时 List切割完成后清空容量并缓存旧 Mesh 用 Destroy 销毁别等着 GC 自动回收。用 Profiler 看 GC Alloc每个顶点插值至少会产生几个临时对象每帧切 100 个顶点就是几百次 Alloc能压多少是多少。5. 进阶技巧让切割效果从“能跑”变成“经得起玩”——验证与内部质感5.1 给切面加上“果肉”材质用第二副贴图映射剖面纹理切面默认是纯色的玩起来不真实。常见做法是给 Mesh 生成第二个 submesh把所有切面三角形单独存到一个索引数组里然后给 MeshRenderer 的 materials 数组设置两个材质index 0 是原模型材质index 1 是剖面材质。剖面的 UV 可以直接用切割平面的投影坐标计算也可以直接用世界坐标映射到一张程序化生成的纹理贴图上。MeshRenderer renderer partObject.GetComponentMeshRenderer(); Material originalMat renderer.material; Material cutMat new Material(Shader.Find(Standard)); cutMat.color new Color(1f, 0.85f, 0.7f); // 类似木头或石头的剖面偏色 renderer.materials new Material[] { originalMat, cutMat };逻辑说明剖面材质的颜色偏暖通常更有“破坏感”同时把切面的三角形索引放到 subMesh 的第二个位置。这个过程相当于让 Unity 在渲染时把同一物体的两部分套用不同材质和 UI 分离是两回事注意 subMesh 的索引顺序不能乱。5.2 三种必做的压力测试转角度、反复切、重复合并切割功能写完之后一定要压测不然上线你就等着翻车。第一个压测是同一物体连续切 100 次每次换一个切割平面角度看最终 Mesh 的三角形数量是否爆掉FPS 是否维持在可接受范围。第二个压测是极端角度切割平面和物体表面近乎平行的时候交点插值会变得极度敏感很容易出现退化三角形。第三个压测是验证切完的两块能不能拼回原物体的包围盒用来避免出现顶点漂移或坐标偏移。5.3 我的最终建议先做平面切别一上来就做“完全随机破坏”如果你是从零开始做 unity切割模型案例我会强烈建议先把固定平面切割跑通无论什么角度割下去都不会出问题再去想随机破坏、多段切割和碎片飞溅。我自己的项目里前期贪快直接跳到了随机切割结果后期百分之八十的时间都在处理因为前期地基没打牢导致的 Mesh 索引错乱和数据泄漏。一次把一个基础行为做稳后面所有效果都是在同一个框架上加料而不是无限返工。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑