當前位置: 華文問答 > 遊戲

Unity中如何進一步最佳化MeshSkinning.Update+Animator.Update?

2016-11-01遊戲

前言
我們在開發遊戲的時候經常會有一些特殊的遊戲玩法等,需要涉及Mesh切割。比如3D切水果, 在地圖的城墻上挖一個洞,今天給大家來分享一個Mesh切割的演算法,幫助大家解決專案中需要用到的Mesh切割的問題。本文主要從一下幾個方面來講解Mesh切割。

對啦!這裏有個遊戲開發交流小組裏面聚集了一幫熱愛學習unity的零基礎小白,也有一些正在從事unity開發的技術大佬,源碼素材獲取 歡迎你來交流學習。

(圖1)

如何接受玩家的觸摸操作,生成切割面

做模型切割的時候,我們首先要根據玩家的觸摸操作來生成一個切割平面。如圖1所示,根據玩家的黑色的劃線,我們要基於黑色的劃線來生成一個切割平面。主要的步驟如下:

(1) 獲取起點的螢幕座標,並結合3D攝影機,把起點的螢幕座標轉到攝影機的視口座標系下。

if (!dragging && Input.GetMouseButtonDown(0)) { start = cam.ScreenToViewportPoint(Input.mousePosition); dragging = true; }

(2) 獲取終點的螢幕座標,並結合3D攝影機,把重點的螢幕座標賺到攝影機的視口座標系下。

if (dragging && Input.GetMouseButtonUp(0)) { // Finished dragging. We draw the line segment end = cam.ScreenToViewportPoint(Input.mousePosition); dragging = false; }

(3) 基於攝影機,將起點與終點生成兩條射線出來,並計算出射線與攝影機的Near平面的交點。

var startRay = cam.ViewportPointToRay(start); var endRay = cam.ViewportPointToRay(end); var startPoint = startRay.GetPoint(cam.nearClipPlane), var endPoint = endRay.GetPoint(cam.nearClipPlane),

(4) 有了兩個點後,就有了一條線,還要有一個方向才能確定一個平面(線按照方向移動,就成了面),這個方向我們取endRay射線的方向為向量A。

(圖2)

(圖3)

var planeTangent = (end - start).normalized; if (planeTangent == Vector3.zero) planeTangent = Vector3.right; var normalVec = Vector3.Cross(depth, planeTangent);

根據 start與normalVec,就可以確定我們的切割面了,它就是過start點,向上的方向向量為normalVec的平面。

3D模型Mesh物件中的主要數據組成

在詳細的講解Mesh切割演算法之前,先給大家講解一下一個模型的Mesh數據主要包含哪些部份,等下生成新的Mesh的時候,我們知道每一部份的數據都代表什麽。Unity中網格數據被生成到Mesh物件裏面,一個Mesh物件主要包含以下重要的數據:

三角形頂點索引數據 : Mesh物件中的每個面對應的三角形數據,而三角形是基於頂點索引的。如面A由三個頂點A1, A2, A3組成,這裏是頂點索引,A1由索引,到頂點數據裏面獲取頂點數據,由A1索引到法線數據裏面獲取法線。在Unity裏面可以透過Mesh物件的介面函數獲取上面的數據,如下:

mesh.GetVertices(ogVertices); mesh.GetTriangles(ogTriangles, 0); mesh.GetNormals(ogNormals); mesh.GetUVs(0, ogUvs);

模型Mesh切割演算法步驟詳解

確定了切割面以後,接下來我們來分析Mesh切割演算法的主要步驟:

var localNormal = ((Vector3)(obj.transform.localToWorldMatrix.transpose * normal)).normalized; var localPoint = obj.transform.InverseTransformPoint(point);

再根據過localPoint, 法線向量為localNormal,來生成一個Plane平面物件。這裏的平面物件是相對於模型的座標系而言的。

var slicePlane = new Plane(); slicePlane.SetNormalAndPosition(localNormal , localPoint);

Step3: 判斷以下Mesh與平面是否相交,如果Mesh與平面完全沒有焦點,則演算法結束,不用分割Mesh;

if (!Interps.BoundPlaneIntersect(mesh, ref slice)) return false;

Step4: 獲取原來Mesh中的網格數據, 並清理切割後存放的正向與反向的Mesh數據集合。

mesh.GetVertices(ogVertices); mesh.GetTriangles(ogTriangles, 0); mesh.GetNormals(ogNormals); mesh.GetUVs(0, ogUvs); PositiveMesh.Clear(); NegativeMesh.Clear(); addedPairs.Clear();

Step5: 遍歷所有的頂點,看哪些頂點在切割面的上部,哪些頂點在切割面的下步,將他們分開放置到PositiveMesh與NegativeMesh中。

for(int i = 0; i < ogVertices.Count; ++i) { if (slice.GetDistanceToPoint(ogVertices[i]) >= 0) PositiveMesh.AddVertex(ogVertices, ogNormals, ogUvs, i); else NegativeMesh.AddVertex(ogVertices, ogNormals, ogUvs, i); }

Step6: 將切割面與物體的交點計算出來,為生成新的面做好準備;

// 3. Separate triangles and cut those that intersect the plane for (int i = 0; i < ogTriangles.Count; i += 3) { if (intersect.TrianglePlaneIntersect(ogVertices, ogUvs, ogTriangles, i, ref slice, PositiveMesh, NegativeMesh, intersectPair)) addedPairs.AddRange(intersectPair); }

Step7: 根據新增的點來生成新的面

private void FillBoundaryFace(List<Vector3> added) { // 1. Reorder added so in order ot their occurence along the perimeter. MeshUtils.ReorderList(added); // 2. Find actual face vertices var face = FindRealPolygon(added); // 3. Create triangle fans int t_fwd = 0, t_bwd = face.Count - 1, t_new = 1; bool incr_fwd = true; while (t_new != t_fwd && t_new != t_bwd) { AddTriangle(face, t_bwd, t_fwd, t_new); if (incr_fwd) t_fwd = t_new; else t_bwd = t_new; incr_fwd = !incr_fwd; t_new = incr_fwd ? t_fwd + 1 : t_bwd - 1; } }

(圖4)

Step8: 例項化一個新物體,將切割後的2個Mesh,一個復制給原來的節點,一個復制給新建立的節點。

// Create new Sliced object with the other mesh GameObject newObject = Instantiate(obj, ObjectContainer); newObject.transform.SetPositionAndRotation(obj.transform.position, obj.transform.rotation); var newObjMesh = newObject.GetComponent<MeshFilter>().mesh; // Put the bigger mesh in the original object // TODO: Enable collider generation (either the exact mesh or compute smallest enclosing sphere) ReplaceMesh(mesh, biggerMesh); ReplaceMesh(newObjMesh, smallerMesh);