多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Unity切割模型实战:从平面求交到运行时动态Mesh拆分

Unity切割模型实战:从平面求交到运行时动态Mesh拆分 简介这是一份面向Unity初学者的模型切割实战案例围绕游戏引擎中的碰撞检测、鼠标交互与Mesh数据修改展开帮助开发者理解如何让自带基础模型被实时切开。案例采用鼠标左键蓄力、右键触发的交互设计涉及Input Manager配置、Mesh Collider碰撞检测、顶点与三角形数据更新等核心知识点适合想提升Unity物理模拟与脚本编写能力的入门到中级开发者。资源包共595个文件以meta、jpg、mat、asset、tif、cs、fbx、anim等为主涵盖C#脚本、材质、动画、模型与预设等完整工程素材压缩包约22.05MB目录结构便于按模块查阅。目前已有1311人学习下载读者可从中获得可运行的切割逻辑脚本、蓄力与切割路径计算思路、模型Mesh分割与刷新显示的完整实现以及碰撞体与刚体组件的配置参考是研究Unity模型切割原理的实用练手项目。1. 从一次切水果式的翻车说起unity切割模型案例到底在切什么很多人第一次在 Unity 里做切割模型脑子里想的是「水果忍者」那种一刀两断结果上手才发现模型不是纸片切完要么破面、要么物理穿透、要么性能直接崩。所谓 unity切割模型案例本质是解决一个具体问题运行时动态把一个完整 Mesh 拆成两个或多个独立 Mesh并让它们各自拥有正确的碰撞体、材质和物理表现。它跟建模软件里布尔运算不是一回事Unity 里没有内置的「切割」按钮你得自己算平面与三角面的交点重建顶点索引。这个方向适合做破坏效果、切割解谜、手术模拟、资源采集类玩法。热搜里 unity 物体速度怎么获取、unity 摄像机跟随 这些高频词恰恰说明大家是在做实时交互而切割正是交互里最难啃的那块骨头。下面我按自己踩过的坑把可复现的路径拆开讲。2. 切割的数学底子与最小可跑通方案平面方程、三角面求交与网格重建2.1 为什么不能直接改顶点而要重建三角面一个 Mesh 由顶点数组、三角面索引数组、法线、UV 组成。你拿一个平面去切平面会穿过若干三角面。穿过的方式只有三种整块在平面一侧、整块在另一侧、被平面切成两半。前两种直接归入对应侧第三种必须生成新顶点。新顶点位置由平面方程和三角面两条边求交得到。这里最容易翻车的是只移动顶点不重建索引结果模型看起来被切了实际上三角面还是连着的物理碰撞体一塌糊涂。常见做法是遍历所有三角面对每个三角面分类收集两侧的顶点和索引最后用Mesh.SetVertices和SetTriangles重新组装。注意切割后必须重新计算法线和切线否则光照会变成玄学模型表面一块亮一块暗。2.2 用 C# 写一个可复现的平面切割核心下面这段代码是切割的最小核心只处理单个 Mesh不涉及物理和材质拆分。把它挂到任意带 MeshFilter 的物体上按空格就能看到效果。using System.Collections.Generic; using UnityEngine; public class MeshCutter : MonoBehaviour { // 切割平面法线 平面上一点 public Vector3 planeNormal Vector3.up; public Vector3 planePoint Vector3.zero; void Update() { if (Input.GetKeyDown(KeyCode.Space)) { MeshFilter mf GetComponentMeshFilter(); Mesh mesh mf.mesh; Vector3[] verts mesh.vertices; int[] tris mesh.triangles; ListVector3 posSide new ListVector3(); ListVector3 negSide new ListVector3(); Listint posTris new Listint(); Listint negTris new Listint(); // 逐三角面处理 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 da Vector3.Dot(planeNormal, a - planePoint); float db Vector3.Dot(planeNormal, b - planePoint); float dc Vector3.Dot(planeNormal, c - planePoint); // 三个点同侧直接归类 if (da 0 db 0 dc 0) { AddTriangle(posSide, posTris, a, b, c); } else if (da 0 db 0 dc 0) { AddTriangle(negSide, negTris, a, b, c); } else { // 被平面切割需要求交点并重建 SplitTriangle(a, b, c, da, db, dc, posSide, posTris, negSide, negTris); } } // 这里只演示正侧结果负侧同理可生成另一个物体 Mesh newMesh new Mesh(); newMesh.SetVertices(posSide); newMesh.SetTriangles(posTris, 0); newMesh.RecalculateNormals(); newMesh.RecalculateBounds(); mf.mesh newMesh; } } void AddTriangle(ListVector3 verts, Listint tris, Vector3 a, Vector3 b, Vector3 c) { int idx verts.Count; verts.Add(a); verts.Add(b); verts.Add(c); tris.Add(idx); tris.Add(idx 1); tris.Add(idx 2); } void SplitTriangle(Vector3 a, Vector3 b, Vector3 c, float da, float db, float dc, ListVector3 posV, Listint posT, ListVector3 negV, Listint negT) { // 求边与平面的交点按符号分侧 // 具体实现略长核心是 Lerp(a, b, da / (da - db)) // 然后根据三个点的符号组合把原三角面拆成 1 个或 2 个新三角面 } }逻辑说明planeNormal和planePoint定义无限平面Vector3.Dot得到每个顶点到平面的有符号距离。同号直接归类异号进入SplitTriangle。参数上planeNormal必须归一化否则距离值没有几何意义planePoint一般取物体包围盒中心或碰撞点。SplitTriangle里用Lerp求交点时分母da - db不能为零否则说明边与平面平行需要单独处理。这个版本只重建了正侧负侧要生成新物体的话把negSide和negTris同样组装再Instantiate一个新 GameObject 挂上去。2.3 切割后必须补的三件事碰撞体、材质、法线只换 Mesh 不换碰撞体物理系统还在用旧形状切割后的碎片会悬空或穿透。常见做法是给每个碎片加MeshCollider并设convex true否则动态刚体不接受非凸碰撞体。材质方面如果原模型有多个 SubMesh切割后索引会错位需要按原 SubMesh 范围分别重建。法线用RecalculateNormals重算但切线要手动保留或重算否则法线贴图会失效。热搜里 unity 材质变成紫红色 很多时候就是切割后材质槽丢了Shader 找不到对应贴图。我一般会在切割前把原材质数组缓存下来切割后按新三角面的 UV 重新分配。3. 把切割接进真实项目从单机脚本到可交互的运行时切割系统3.1 用射线检测确定切割平面而不是硬编码实际项目里没人会固定一个平面切都是玩家划一刀或点击某个位置。常见做法是用Physics.Raycast从摄像机发一条射线命中点作为planePoint射线方向与摄像机右方向的叉积作为planeNormal。这样切出来的面才符合视觉直觉。注意如果模型没有 Collider射线打不中所以切割前必须确保目标有碰撞体。热搜里 unity 摄像机跟随 和 unity lookat 经常和切割一起出现因为玩家视角一动切割平面也得跟着动。我一般会把切割逻辑放在一个CutManager里由输入事件触发而不是每帧检测。Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 100f)) { Vector3 normal Vector3.Cross(ray.direction, Camera.main.transform.right).normalized; cutter.planePoint hit.point; cutter.planeNormal normal; }参数说明hit.point是切割起点normal是切割面法线normalized保证单位长度。如果射线方向与摄像机右方向平行叉积会接近零向量此时要换一个参考轴否则平面退化。3.2 切割多个物体时的性能边界与对象池一个场景里切几十个物体如果每次都new Mesh()并InstantiateGC 会爆炸。血泪经验是切割产生的碎片用对象池管理Mesh 用MeshPool复用顶点数超过一定阈值比如单碎片超过 5000 顶点就降级为不切割或直接销毁。热搜里 unity 包体优化 和 unity 游戏优化 在这里直接相关因为切割产生的临时 Mesh 如果不Destroy内存会持续上涨。我一般会在碎片上挂一个生命周期脚本5 秒后回池。另外MeshCollider的烹饪开销很大移动端上要限制同时存在的凸碰撞体数量。3.3 用 Job System 或 Burst 加速大批量顶点计算如果切割的模型顶点数上万主线程逐三角面算会卡帧。常见做法是把顶点数组和三角面索引丢进NativeArray用IJobParallelFor并行计算每个三角面的分类和交点。注意Job 里不能直接访问Mesh要先Mesh.AcquireReadOnlyMeshData拿到只读数据算完再写回。这个方案在 Unity 6 的 GPU Skins 场景下尤其要注意因为蒙皮后的顶点位置在 GPU 上CPU 拿不到切割前得先BakeMesh把当前姿态烘出来否则切的是绑定姿势和看到的完全对不上。4. 避坑与排查切割模型时最容易翻车的 5 个现场4.1 切完模型表面出现黑面或破洞现象切割后碎片边缘发黑或者直接看到背面。原因新生成的三角面绕序反了背面剔除把正面剔掉了。解决在SplitTriangle里根据平面法线方向调整顶点顺序保证新三角面的法线朝外。可以用Vector3.Dot检查新面法线与原面法线方向反了就交换两个顶点索引。4.2 切割后物理碎片抖动或飞出去现象碎片刚生成就剧烈抖动甚至弹飞。原因MeshCollider的convex没开或者刚体质量、重心没重算。解决给每个碎片加Rigidbody后调用ResetCenterOfMass并把MeshCollider.convex设为 true。如果碎片形状太薄还要开Rigidbody.collisionDetectionMode Continuous否则高速下会穿透。4.3 切割点附近 UV 拉伸贴图错乱现象新生成的顶点 UV 是默认值贴图在切割边缘被拉成一条线。原因求交点时只插值了位置没插值 UV。解决在SplitTriangle里对 UV 也做同样的Lerp插值系数和位置一致。如果原模型有多个 UV 通道每个通道都要插值。热搜里 unity 材质变成紫红色 有时就是 UV 丢失导致 Shader 采样越界。4.4 多次切割后顶点数爆炸帧率骤降现象切了五六刀后碎片顶点数比原模型还多。原因每次切割都复制了未切割侧的完整顶点没有做顶点去重。解决切割前用MeshUtility.Optimize或自己写字典去重把位置、法线、UV 相同的顶点合并。注意去重会改变索引顺序必须在重建三角面之前做。4.5 移动端上切割直接闪退现象在手机上一切就崩编辑器里没事。原因MeshCollider在移动端对非凸网格支持有限或者单次切割顶点数超过索引格式上限65535。解决切割前检查顶点数超过 65535 就改用IndexFormat.UInt32或者限制切割对象的面数。另外移动端上尽量用BoxCollider近似代替MeshCollider物理表现差一点但稳定。5. 进阶技巧用切割做可复现的破坏效果与验证方法切割做到后面真正拉开差距的不是算法而是怎么让碎片看起来「像那么回事」。我一般会在切割后给碎片加一个向外的冲量方向取碎片重心到切割点的向量大小和切割速度挂钩。这样切水果才有爽感。验证切割是否正确最直接的方法是打开 Scene 视图的 Wireframe看新生成的三角面有没有重叠或翻转。另一个习惯是写一个 Editor 脚本在切割前后分别输出顶点数和三角面数如果切割后三角面数不是原数量的 1.5 到 2 倍大概率有面被漏掉了。进阶玩法里切割和 unity 热力图、unity 数字孪生 这类场景结合时往往不需要真的把 Mesh 切开而是用 Shader 做视觉切割物理上还是整体。这种「假切割」性能好得多适合展示类项目。真切割留给需要物理交互的玩法。我自己的习惯是先跑通单 Mesh 切割再补碰撞和材质最后才上 Job System 优化。顺序反了调试成本会翻倍。希望帮到你。本文还有配套的精品资源点击获取
返回列表