【Unity】Mesh边缘的查找与绘制

前言

最近需要将一个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的方向看,图形始终在左手边。

这些调整可以直接在材质中处理:


  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Unity 是一种流行的游戏开发引擎,它提供了许多强大的功能,其中之一就是使用Mesh绘制和操作顶点。 MeshUnity中用来表示3D对象网格的组件,它由一系列的顶点和面构成。通过在Mesh上添加、修改和删除顶点,我们可以创建各种形状和模型。 在Unity中,绘制Mesh顶点有几个步骤。首先,我们需要创建一个空的GameObject,并在其上添加一个MeshFilter组件。MeshFilter用于存储和管理Mesh的数据。 接下来,我们可以使用代码或者Unity的编辑器工具来添加和编辑Mesh顶点。通过修改Mesh的vertices属性,我们可以指定顶点的位置。例如,我们可以通过创建一个Vector3数组来定义几个顶点,并将其赋值给Mesh的vertices属性。 完成顶点的编辑后,我们还可以为Mesh指定面的连接方式。在Unity中,面通常是由三个顶点组成的三角形。我们可以通过指定三角形的顶点索引来定义各个面。例如,我们可以将一个有序的整数数组赋值给Mesh的triangles属性,其中每三个整数表示一个三角形的三个顶点。 最后,我们需要为Mesh创建一个材质,并将其赋值给GameObject上的MeshRenderer组件。材质用于定义对象的外观和着色方式。 在绘制Mesh顶点后,我们可以使用Unity的摄像机和光源来渲染和显示对象。通过调整摄像机的位置、旋转和投影方式,我们可以在屏幕上看到Mesh顶点的绘制结果。 绘制Mesh顶点是Unity中创建3D物体和场景的重要步骤之一。通过熟练使用Mesh组件和顶点操作技巧,我们可以实现各种复杂的模型和效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值