Unity模型切割

概述

3d模型的任意切割一直是游戏开发里的一个很大的问题,模型切割的关键点就只有生成横切面的新顶点以及切口纹理的缝合,理论上解决了这两点,就近乎可以做到以假乱真的程度了。本篇文章就这两点进行描述

详细

一、准备工作

解压缩后得到ClipDemo.unitypackage文件,将此文件导入unity5.45中,双击main场景,运行即可。运行后可以看到一个球体,对着球体拖动鼠标做切割动作,可以看到Hierarchy面板生成多个new Model,即为切割生成的模型,可以在Scene中拖动这些物体,可以看到是切割后的物体。压缩包内容如下:

QQ截图20170825144223.png

二、程序实现

2.jpg

如上图所示,当切割模型时,对于切面上的三角面,无非是如图中3种情况(正好切在三角形的某个顶点上几乎不可能,不过也可以考虑在内,这里就不画出来了),所以每个三角形正好被切到的时候,其自身内部应该生成新的顶点(图中-1,-2点)。生成处新的顶点之后,我们需要将原来的一个三角形重新分割为如图绿色的数字标志的三个三角形,也就是原来一个三角形被分为三个三角形。代码如下:

MeshFilter mf = this.gameObject.GetComponent<MeshFilter>();
 
        //顶点数组转顶点容器
        List<Vector3> verticeList = new List<Vector3>();
        int verticeCount = mf.mesh.vertices.Length;
        for (int verticeIndex = 0; verticeIndex < verticeCount; ++verticeIndex)
        {
            verticeList.Add(mf.mesh.vertices[verticeIndex]);
        }
        //三角形数组转三角形容器
        List<int> triangleList = new List<int>();
        int triangleCount = mf.mesh.triangles.Length;
        for (int triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex)
        {
            triangleList.Add(mf.mesh.triangles[triangleIndex]);
        }
        //uv坐标数组转uv坐标容器
        List<Vector2> uvList = new List<Vector2>();
        int uvCount = mf.mesh.uv.Length;
        for (int uvIndex = 0; uvIndex < uvCount; ++uvIndex)
        {
            uvList.Add(mf.mesh.uv[uvIndex]);
        }
        //顶点颜色数组转顶点颜色容器
        List<Vector3> normalList = new List<Vector3>();
        int normalCount = mf.mesh.normals.Length;
        for (int normalIndex = 0; normalIndex < normalCount; ++normalIndex)
        {
            normalList.Add(mf.mesh.normals[normalIndex]);
        }
 
        //检查每个三角面,是否存在两个顶点连接正好在直线上
        for (int triangleIndex = 0; triangleIndex < triangleList.Count;)
        {
            int trianglePoint0 = triangleList[triangleIndex];
            int trianglePoint1 = triangleList[triangleIndex + 1];
            int trianglePoint2 = triangleList[triangleIndex + 2];
             
            Vector3 point0 = verticeList[trianglePoint0];
            Vector3 point1 = verticeList[trianglePoint1];
            Vector3 point2 = verticeList[trianglePoint2];
 
            float planeY = 0.3f;
            //0-1,1-2相连线段被切割
            if ((point0.y - planeY)* (point1.y - planeY) < 0 && (point1.y - planeY) * (point2.y - planeY) < 0)
            {
                //截断0-1之间的顶点
                float k01 = (point1.y - point0.y) / (planeY - point0.y);
                float newPointX01 = (point1.x - point0.x) / k01 + point0.x;
                float newPointZ01 = (point1.z - point0.z) / k01 + point0.z;
                Vector3 newPoint0_1 = new Vector3(newPointX01, planeY, newPointZ01);
                verticeList.Add(newPoint0_1);
                //uv
                if(uvList.Count > 0)
                {
                    Vector2 uv0 = uvList[trianglePoint0];
                    Vector2 uv1 = uvList[trianglePoint1];
                    float newUV_x = (uv1.x - uv0.x) / k01 + uv0.x;
                    float newUV_y = (uv1.y - uv0.y) / k01 + uv0.y;
                    uvList.Add(new Vector2(newUV_x, newUV_y));
                }
                //法向量
                Vector3 normalX0 = normalList[trianglePoint0];
                Vector3 normalX1 = normalList[trianglePoint1];
                Vector3 normalX2 = normalList[trianglePoint2];
                float newNoramlX01 = (normalX1.x - normalX0.x) / k01 + normalX0.x;
                float newNoramlY01 = (normalX1.y - normalX0.y) / k01 + normalX0.y;
                float newNoramlZ01 = (normalX1.z - normalX0.z) / k01 + normalX0.z;
                normalList.Add(new Vector3(newNoramlX01, newNoramlY01, newNoramlZ01));
                //截断1-2之间的顶点
                float k12 = (point2.y - point1.y) / (planeY - point1.y);
                float newPointX12 = (point2.x - point1.x) / k12 + point1.x;
                float newPointZ12 = (point2.z - point1.z) / k12 + point1.z;
                Vector3 newPoint1_2 = new Vector3(newPointX12, planeY, newPointZ12);
                verticeList.Add(newPoint1_2);
                if (uvList.Count > 0)
                {
                    Vector2 uv1 = uvList[trianglePoint1];
                    Vector2 uv2 = uvList[trianglePoint2];
                    float newUV_x = (uv2.x - uv1.x) / k12 + uv1.x;
                    float newUV_y = (uv2.y - uv1.y) / k12 + uv1.y;
                    uvList.Add(new Vector2(newUV_x, newUV_y));
                }
                //法向量
                float newNoramlX12 = (normalX2.x - normalX1.x) / k12 + normalX1.x;
                float newNoramlY12 = (normalX2.y - normalX1.y) / k12 + normalX1.y;
                float newNoramlZ12 = (normalX2.z - normalX1.z) / k12 + normalX1.z;
                normalList.Add(new Vector3(newNoramlX12, newNoramlY12, newNoramlZ12));
 
                int newVerticeCount = verticeList.Count;
                //插入顶点索引,以此构建新三角形
                triangleList.Insert(triangleIndex + 1, newVerticeCount - 2);
                triangleList.Insert(triangleIndex + 2, newVerticeCount - 1);
 
                triangleList.Insert(triangleIndex + 3, newVerticeCount - 1);
                triangleList.Insert(triangleIndex + 4, newVerticeCount - 2);
 
                triangleList.Insert(triangleIndex + 6, trianglePoint0);
                triangleList.Insert(triangleIndex + 7, newVerticeCount - 1);
            }
            //1-2,2-0相连线段被切割
            else if ((point1.y - planeY) * (point2.y - planeY) < 0 && (point2.y - planeY) * (point0.y - planeY) < 0)
            {
                //截断1-2之间的顶点
                float k12 = (point2.y - point1.y) / (planeY - point1.y);
                float newPointX12 = (point2.x - point1.x) / k12 + point1.x;
                float newPointZ12 = (point2.z - point1.z) / k12 + point1.z;
                Vector3 newPoint1_2 = new Vector3(newPointX12, planeY, newPointZ12);
                verticeList.Add(newPoint1_2);
                if (uvList.Count > 0)
                {
                    Vector2 uv1 = uvList[trianglePoint1];
                    Vector2 uv2 = uvList[trianglePoint2];
                    float newUV_x = (uv2.x - uv1.x) / k12 + uv1.x;
                    float newUV_y = (uv2.y - uv1.y) / k12 + uv1.y;
                    uvList.Add(new Vector2(newUV_x, newUV_y));
                }
                //法向量
                Vector3 normalX0 = normalList[trianglePoint0];
                Vector3 normalX1 = normalList[trianglePoint1];
                Vector3 normalX2 = normalList[trianglePoint2];
                float newNoramlX12 = (normalX2.x - normalX1.x) / k12 + normalX1.x;
                float newNoramlY12 = (normalX2.y - normalX1.y) / k12 + normalX1.y;
                float newNoramlZ12 = (normalX2.z - normalX1.z) / k12 + normalX1.z;
                normalList.Add(new Vector3(newNoramlX12, newNoramlY12, newNoramlZ12));
 
                //截断0-2之间的顶点
                float k02 = (point2.y - point0.y) / (planeY - point0.y);
                float newPointX02 = (point2.x - point0.x) / k02 + point0.x;
                float newPointZ02 = (point2.z - point0.z) / k02 + point0.z;
                Vector3 newPoint0_2 = new Vector3(newPointX02, planeY, newPointZ02);
                verticeList.Add(newPoint0_2);
                //uv
                if (uvList.Count > 0)
                {
                    Vector2 uv0 = uvList[trianglePoint0];
                    Vector2 uv2 = uvList[trianglePoint2];
                    float newUV_x = (uv2.x - uv0.x) / k02 + uv0.x;
                    float newUV_y = (uv2.y - uv0.y) / k02 + uv0.y;
                    uvList.Add(new Vector2(newUV_x, newUV_y));
                }
                //法向量
                float newNoramlX02 = (normalX1.x - normalX0.x) / k02 + normalX0.x;
                float newNoramlY02 = (normalX1.y - normalX0.y) / k02 + normalX0.y;
                float newNoramlZ02 = (normalX1.z - normalX0.z) / k02 + normalX0.z;
                normalList.Add(new Vector3(newNoramlX02, newNoramlY02, newNoramlZ02));
                 
                int newVerticeCount = verticeList.Count;
                //插入顶点索引,以此构建新三角形
 
                //{0}
                //{1}
                triangleList.Insert(triangleIndex + 2, newVerticeCount - 2);
 
                triangleList.Insert(triangleIndex + 3, newVerticeCount - 1);
                triangleList.Insert(triangleIndex + 4, newVerticeCount - 2);
                //{2}
                 
                triangleList.Insert(triangleIndex + 6, newVerticeCount - 1);
                triangleList.Insert(triangleIndex + 7, trianglePoint0);
                triangleList.Insert(triangleIndex + 8, newVerticeCount - 2);
            }
            //0-1,2-0相连线段被切割
            else if((point0.y - planeY) * (point1.y - planeY) < 0 && (point2.y - planeY) * (point0.y - planeY) < 0)
            {
                //截断0-1之间的顶点
                float k01 = (point1.y - point0.y) / (planeY - point0.y);
                float newPointX01 = (point1.x - point0.x) / k01 + point0.x;
                float newPointZ01 = (point1.z - point0.z) / k01 + point0.z;
                Vector3 newPoint0_1 = new Vector3(newPointX01, planeY, newPointZ01);
                verticeList.Add(newPoint0_1);
                //uv
                if (uvList.Count > 0)
                {
                    Vector2 uv0 = uvList[trianglePoint0];
                    Vector2 uv1 = uvList[trianglePoint1];
                    float newUV_x = (uv1.x - uv0.x) / k01 + uv0.x;
                    float newUV_y = (uv1.y - uv0.y) / k01 + uv0.y;
                    uvList.Add(new Vector2(newUV_x, newUV_y));
                }
                //法向量
                Vector3 normalX0 = normalList[trianglePoint0];
                Vector3 normalX1 = normalList[trianglePoint1];
                Vector3 normalX2 = normalList[trianglePoint2];
                float newNoramlX01 = (normalX1.x - normalX0.x) / k01 + normalX0.x;
                float newNoramlY01 = (normalX1.y - normalX0.y) / k01 + normalX0.y;
                float newNoramlZ01 = (normalX1.z - normalX0.z) / k01 + normalX0.z;
                normalList.Add(new Vector3(newNoramlX01, newNoramlY01, newNoramlZ01));
 
                //截断0-2之间的顶点
                float k02 = (point2.y - point0.y) / (planeY - point0.y);
                float newPointX02 = (point2.x - point0.x) / k02 + point0.x;
                float newPointZ02 = (point2.z - point0.z) / k02 + point0.z;
                Vector3 newPoint0_2 = new Vector3(newPointX02, planeY, newPointZ02);
                verticeList.Add(newPoint0_2);
                //uv
                if (uvList.Count > 0)
                {
                    Vector2 uv0 = uvList[trianglePoint0];
                    Vector2 uv2 = uvList[trianglePoint2];
                    float newUV_x = (uv2.x - uv0.x) / k02 + uv0.x;
                    float newUV_y = (uv2.y - uv0.y) / k02 + uv0.y;
                    uvList.Add(new Vector2(newUV_x, newUV_y));
                }
                //法向量
                float newNoramlX02 = (normalX1.x - normalX0.x) / k02 + normalX0.x;
                float newNoramlY02 = (normalX1.y - normalX0.y) / k02 + normalX0.y;
                float newNoramlZ02 = (normalX1.z - normalX0.z) / k02 + normalX0.z;
                normalList.Add(new Vector3(newNoramlX02, newNoramlY02, newNoramlZ02));
                 
                int newVerticeCount = verticeList.Count;
                //插入顶点索引,以此构建新三角形
 
                //{0}
                triangleList.Insert(triangleIndex + 1, newVerticeCount - 2);
                triangleList.Insert(triangleIndex + 2, newVerticeCount - 1);
 
                triangleList.Insert(triangleIndex + 3, newVerticeCount - 2);
                //{1}
                //{2}
                 
                triangleList.Insert(triangleIndex + 6, trianglePoint2);
                triangleList.Insert(triangleIndex + 7, newVerticeCount - 1);
                triangleList.Insert(triangleIndex + 8, newVerticeCount - 2);
            }
            //只有0-1被切
            else if((point0.y - planeY) * (point1.y - planeY) < 0)
            {
                Debug.Log("只有01被切");
            }
            //只有1-2被切
            else if ((point1.y - planeY) * (point2.y - planeY) < 0)
            {
                Debug.Log("只有12被切");
            }
            //只有2-0被切
            else if ((point2.y - planeY) * (point0.y - planeY) < 0)
            {
                Debug.Log("只有02被切");
            }
            triangleIndex += 3;
        }
 
        //筛选出切割面两侧的顶点索引
        List<int> triangles1 = new List<int>();
        List<int> triangles2 = new List<int>();
        for (int triangleIndex = 0; triangleIndex < triangleList.Count; triangleIndex += 3)
        {
            int trianglePoint0 = triangleList[triangleIndex];
            int trianglePoint1 = triangleList[triangleIndex + 1];
            int trianglePoint2 = triangleList[triangleIndex + 2];
             
            Vector3 point0 = verticeList[trianglePoint0];
            Vector3 point1 = verticeList[trianglePoint1];
            Vector3 point2 = verticeList[trianglePoint2];
            //切割面
            float planeY = 0.3f;
            if(point0.y > planeY || point1.y > planeY || point2.y > planeY)
            {
                triangles1.Add(trianglePoint0);
                triangles1.Add(trianglePoint1);
                triangles1.Add(trianglePoint2);
            }
            else
            {
                triangles2.Add(trianglePoint0);
                triangles2.Add(trianglePoint1);
                triangles2.Add(trianglePoint2);
            }
        }
 
        //缝合切口
        //for (int verticeIndex = verticeCount; verticeIndex < verticeList.Count - 2; ++verticeIndex)
        //{
        //    triangles1.Add(verticeIndex + 2);
        //    triangles1.Add(verticeIndex);
        //    triangles1.Add(verticeCount);
 
        //    triangles2.Add(verticeCount);
        //    triangles2.Add(verticeIndex);
        //    triangles2.Add(verticeIndex + 2);
        //}
 
 
        mf.mesh.vertices = verticeList.ToArray();
        mf.mesh.triangles = triangles1.ToArray();
        if (uvList.Count > 0)
        {
            mf.mesh.uv = uvList.ToArray();
        }
        mf.mesh.normals = normalList.ToArray();
 
 
        //分割模型
        GameObject newModel = new GameObject("New Model");
        MeshFilter meshFilter = newModel.AddComponent<MeshFilter>();
        meshFilter.mesh.vertices = mf.mesh.vertices;
        meshFilter.mesh.triangles = triangles2.ToArray();
        meshFilter.mesh.uv = mf.mesh.uv;
        meshFilter.mesh.normals = mf.mesh.normals;
        Renderer newRenderer = newModel.AddComponent<MeshRenderer>();
        newRenderer.material = this.gameObject.GetComponent<MeshRenderer>().material;

切出来的模型新生成的顶点是无序的,但是我们可以连接任意两个无序顶点定为参考向量,然后其他任意顶点与参考向量中的起点连接形成新的向量,求得这两个向量之间的夹角,利用这个夹角大小来排序,如图所示:
20170804110923987.png

顶点的排序算法如下:

//重新排序新生成的顶点,按照角度
        List<SortAngle> SortAngleList = new List<SortAngle>();
        for (int verticeIndex = verticeCount + 1; verticeIndex < verticeList.Count; verticeIndex++)
        {
            //计算角度,以0-1为参照
            Vector3 vec1to0 = verticeList[verticeCount + 1] - verticeList[verticeCount];
            Vector3 indexTo0 = verticeList[verticeIndex] - verticeList[verticeCount];
 
            float moIndexto0 = indexTo0.magnitude;
            float mo1to0 = vec1to0.magnitude;
            float dotRes = Vector3.Dot(indexTo0, vec1to0);
            if (moIndexto0 == 0.0f)
            {
                continue;
            }
            float angle = Mathf.Acos(dotRes / (mo1to0 * moIndexto0)); //Vector3.Angle(indexTo0.normalized, vec1to0.normalized);
            bool isExis = false;
            for (int i = 0; i < SortAngleList.Count; ++i)
            {
                //同样角度,距离近的被剔除
                if (Mathf.Abs(SortAngleList[i].Angle * 180.0f / Mathf.PI - angle * 180.0f / Mathf.PI) < 0.1f)
                {
                    float dis1 = Vector3.Distance(verticeList[SortAngleList[i].Index], verticeList[verticeCount]);
                    float dis2 = Vector3.Distance(verticeList[verticeIndex], verticeList[verticeCount]);
                    if (dis2 >= dis1)
                    {
                        SortAngleList[i].Index = verticeIndex;
                    }
                    isExis = true;
                    break;
                }
            }
            if (!isExis)
            {
                //Debug.Log(angle);
                SortAngle sortAngle = new SortAngle();
                sortAngle.Index = verticeIndex;
                sortAngle.Angle = angle;
                SortAngleList.Add(sortAngle);
            }
        }
        SortAngleList.Sort();

三、运行效果

20170809161556089.png
20170810124411379.png
20170810124421277.png

附加其他插件地址:https://github.com/DavidArayan/ezy-slice

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容