【Unity】Mesh邊緣的查詢與繪製
阿新 • • 發佈:2019-02-13
前言
最近需要將一個2D網格邊緣上繪製虛線,最初考慮使用渲染的後處理來實現,但是後處理代價大,而且效果也並不理想,於是打算採用LineRenderer來繪製邊緣的虛線。
最後效果如下:
四張圖分別是Quad、Plane、自定義形狀、帶空洞的自定義形狀。
步驟與原理
1、獲取目標網格上的所有頂點、三角面資訊。
2、提取出網格上所有三角面的三條邊。
3、比較所邊緣資訊,清除重複使用的邊緣。被共用的邊緣是兩個三角面的相交線,如Quad中間的那條對角線。
4、將為重複的邊緣排序連成線,對有多邊緣的形狀,返回多條線。
5、使用LineRender元件將線繪製出來。
實現
using System.Collections.Generic; using UnityEngine; public class MeshVertexLineRenderer : MonoBehaviour { public Material material; public void AddLine() { AddLine(GetComponent<MeshFilter>().sharedMesh, transform); } public void DeleteLine() { DeleteLine(transform); } private void AddLine(Mesh mesh, Transform parent) { DeleteLine(parent); Vector3[] meshVertices = mesh.vertices; List<List<Vector3>> vertices = TrianglesAndVerticesEdge(mesh.vertices, mesh.triangles); for (int i = 0; i < vertices.Count; i++) AddSingleLine(i, vertices[i].ToArray(), parent); } private void AddSingleLine(int index, Vector3[] vertices, Transform parent) { LineRenderer lineRenderer = new GameObject("MeshVertexLine_" + index, new System.Type[] { typeof(LineRenderer) }).GetComponent<LineRenderer>(); lineRenderer.transform.parent = parent; lineRenderer.transform.localPosition = Vector3.zero; lineRenderer.transform.localRotation = Quaternion.identity; lineRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; lineRenderer.receiveShadows = false; lineRenderer.allowOcclusionWhenDynamic = false; lineRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; lineRenderer.useWorldSpace = false; lineRenderer.loop = true; lineRenderer.widthMultiplier = 0.1f; lineRenderer.sortingLayerName = "GamePlay"; lineRenderer.sortingOrder = 501; lineRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; lineRenderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off; lineRenderer.alignment = LineAlignment.View; lineRenderer.textureMode = LineTextureMode.Tile; lineRenderer.material = material; lineRenderer.positionCount = vertices.Length; lineRenderer.SetPositions(vertices); } private void DeleteLine(Transform parent) { for (int i = 0; i < parent.childCount; i++) { if (parent.GetChild(i).name.Contains("MeshVertexLine")) { GameObject gameObject = parent.GetChild(i).gameObject; if (gameObject != null) { i--; #if UNITY_EDITOR if (Application.isPlaying) { Object.Destroy(gameObject); } else { Object.DestroyImmediate(gameObject); } #else Object.Destroy(gameObject); #endif } } } } /// <summary> /// 網格系統邊緣查詢,支援多邊緣 /// </summary> /// <param name="vertices"></param> /// <param name="triangles"></param> /// <returns></returns> private List<List<Vector3>> TrianglesAndVerticesEdge(Vector3[] vertices, int[] triangles) { List<Vector2Int> edgeLines = TrianglesEdgeAnalysis(triangles); List<List<Vector3>> result = SpliteLines(edgeLines, vertices); return result; } /// <summary> /// 三角面組邊緣提取 /// </summary> /// <param name="triangles"></param> /// <param name="edges"></param> /// <param name="invalidFlag"></param> /// <returns></returns> private List<Vector2Int> TrianglesEdgeAnalysis(int[] triangles) { int[,] edges = new int[triangles.Length, 2]; for (int i = 0; i < triangles.Length; i += 3) { for (int j = 0; j < 3; j++) { for (int k = 0; k < 2; k++) { int index = (j + k) % 3; edges[i + j, k] = triangles[i + index]; } } } bool[] invalidFlag = new bool[triangles.Length]; for (int i = 0; i < triangles.Length; i++) { for (int j = i + 1; j < triangles.Length; j++) { if ((edges[i, 0] == edges[j, 0] && edges[i, 1] == edges[j, 1]) || (edges[i, 0] == edges[j, 1] && edges[i, 1] == edges[j, 0])) { invalidFlag[i] = true; invalidFlag[j] = true; } } } List<Vector2Int> edgeLines = new List<Vector2Int>(); for (int i = 0; i < triangles.Length; i++) { if (!invalidFlag[i]) { edgeLines.Add(new Vector2Int(edges[i, 0], edges[i, 1])); } } if (edgeLines.Count == 0) { Debug.Log("Calculate wrong, there is not any valid line"); } return edgeLines; } /// <summary> /// 邊緣排序與分離 /// </summary> /// <param name="edgeLines"></param> /// <param name="vertices"></param> /// <returns></returns> private List<List<Vector3>> SpliteLines(List<Vector2Int> edgeLines, Vector3[] vertices) { List<List<Vector3>> result = new List<List<Vector3>>(); List<int> edgeIndex = new List<int>(); int startIndex = edgeLines[0].x; edgeIndex.Add(edgeLines[0].x); int removeIndex = 0; int currentIndex = edgeLines[0].y; while (true) { edgeLines.RemoveAt(removeIndex); edgeIndex.Add(currentIndex); bool findNew = false; for (int i = 0; i < edgeLines.Count && !findNew; i++) { if (currentIndex == edgeLines[i].x) { currentIndex = edgeLines[i].y; removeIndex = i; findNew = true; } else if (currentIndex == edgeLines[i].y) { currentIndex = edgeLines[i].x; removeIndex = i; findNew = true; } } if (findNew && currentIndex == startIndex) { Debug.Log("Complete Closed curve"); edgeLines.RemoveAt(removeIndex); List<Vector3> singleVertices = new List<Vector3>(); for (int i = 0; i < edgeIndex.Count; i++) singleVertices.Add(vertices[edgeIndex[i]]); result.Add(singleVertices); if (edgeLines.Count > 0) { edgeIndex = new List<int>(); startIndex = edgeLines[0].x; edgeIndex.Add(edgeLines[0].x); removeIndex = 0; currentIndex = edgeLines[0].y; } else { break; } } else if (!findNew) { Debug.Log("Complete curve, but not closed"); List<Vector3> singleVertices = new List<Vector3>(); for (int i = 0; i < edgeIndex.Count; i++) singleVertices.Add(vertices[edgeIndex[i]]); result.Add(singleVertices); if (edgeLines.Count > 0) { edgeIndex = new List<int>(); startIndex = edgeLines[0].x; edgeIndex.Add(edgeLines[0].x); removeIndex = 0; currentIndex = edgeLines[0].y; } else { break; } } } return result; } }
LineRenderer元件
LineRenderer元件繪製線,會沿著模型點繪製,實際上我們需要線繪製到模型的內側(或者外側)。
在Unity中,從一個方向觀察Mesh的三角面頂點排序為逆時針時,這個方向看到的就是三角面的正面。
即俯視三角面的Front時,三角面頂點排序為逆時針;我們最後得到的線為L,沿著L的方向看,圖形始終在左手邊。
這些調整可以直接在材質中處理: