Unity - 製作電流效果2 - Mesh

  除了使用Unity內建的LineRenderer來製作電流效果之外(製作電流效果1-LineRenderer),還可以使用自己建立Mesh線段來取代LineRenderer製作電流效果,明顯的好處就是線段不一定要連續,以及多個線段Draw call不會增加,但是因為是用Mesh製作,所以當線段的數量越多,雖然Draw Call不變但是面數卻會大量增加,所以在使用上還是要斟酌,同時如果有美術可以製作這些效果的圖,也就可以不必用這麼多Mesh線段來模擬電流效果。



  跟之前使用LineRenderer的方式很像,首先由起始點跟終點取得原始線段,由這條線的資訊可以計算出Theta角,使用這個角度資訊再加上每個節點的偏移資訊(dir1、dir2)就可以計算出調整位置後各個節點的座標(綠色Target)。



  接著使用這個座標點(綠色Target)再加上線段寬度的資訊(使用AnimationCurve取得漸變的線段起點跟終點的寬度)即可以取得方形的四個角點的座標,最後再使用這四的角點座標去製作Mesh就可以得到這條線段。

  其實如果使用這個方法來做線段Mesh會發現一個問題,就是在轉角的地方,兩條線段並沒有密合,當然這個部份調整一下Mesh製作的部分就可以解決,不過就單純的使用上來說,目前這個方法已經堪用了。

  在使用上來說,還需要注意有些Shader無法調整顏色跟Alpha值,同時因為這個是我用在2D遊戲上的部分,如果要在3D場景中使用還需要注意Mesh的Normal,要就是使用Shader來讓正反面都有材質,不然就是使用Billboard的方式讓這線段的Mesh一直朝著Camera之類的來避免Mesh背向攝影機看不到線段的狀況。

--
實作測試:拿我之前貓貓遊戲畫的背景圖來用,點擊滑鼠放電流,背景樹狀閃電跟前景電流都只用1個draw call,不過因為是用Mesh來做所以面數會多很多,如果有美術資源可用的話也就不必用這種方式。




--
使用自訂Mesh線段的電流
public class CustomLine : MonoBehaviour {
    public Material mat; //貼圖材質
    public GameObject startPos; //線段起點,使用物件主要是方便可以在編輯視窗中拖曳,或自己調整為Vector3
    public GameObject targetPos; //線段終點
    public int count = 15; //節點數量

    //使用AnimationCurve來設定線段的寬度、顏色(須注意多數Shader並不支援這顏色的改變,自己寫或是姑且使用Unity內建的Sprites/Diffuse之類的)
    //比起LineRenderer的優點在於,不限定只能設定線段開始跟結束的寬度跟顏色
    public AnimationCurve width = AnimationCurve.Linear(0f,0.2f,1f,0.2f);
    public AnimationCurve colorR = AnimationCurve.Linear(0f,1f,1f,1f);
    public AnimationCurve colorG = AnimationCurve.Linear(0f,1f,1f,1f);
    public AnimationCurve colorB = AnimationCurve.Linear(0f,1f,1f,1f);
    public AnimationCurve colorA = AnimationCurve.Linear(0f,1f,1f,1f);
 
    private MeshFilter lineMeshFilter;
    private MeshRenderer lineMeshRenderer;
    private GameObject customLine; //建立一個物件來乘載MeshFilter跟MeshRenderer,避免跟Component掛載物件上的衝突

    void Update () {
        Lightning();
    }

    private void Lightning()
    {
        Vector3[] points = CalculatePoints (startPos, targetPos, count);

        if(customLine == null)
        {
            customLine = new GameObject ("CustomLine");
            customLine.transform.parent = this.transform;
   
            lineMeshFilter = customLine.AddComponent(typeof(MeshFilter)) as MeshFilter;
            lineMeshRenderer = customLine.AddComponent (typeof(MeshRenderer)) as MeshRenderer;
            lineMeshRenderer.material = mat;

            CreateMesh (points);
        }

        ModifyMesh (points);
    }

    private void ModifyMesh(Vector3[] points)
    {
        //取得新的verts座標
        Vector3[] verts = GetVerts (points);

        //取得顏色資訊
        Color[] colors = GetColor (verts.Length);

        //使用新的verts跟顏色資訊調整現有的Mesh
        Mesh lineMesh = lineMeshFilter.mesh;
        lineMesh.vertices = verts;
        lineMesh.colors = colors;
    }

    //計算新的線段座標點
    private Vector3[] CalculatePoints(GameObject from, GameObject to, int points)
    {
        Vector3[] result = new Vector3[points];
        Vector3 dir = to.transform.position - from.transform.position;
        Vector3 dirDelta = dir / (points - 1);
        float theda = Mathf.Atan2 (dirDelta.y, dirDelta.x);

        for(int i = 0; i < count; ++i)
        {
            //每條原始線段的起始座標點
            Vector3 pos = from.transform.position + dirDelta * i;

            int mid = count / 2;
            float ratio = (count % 2 == 0) ? (i - (i/mid) * ((i % mid + 1)*2 - 1)) : (i - (i/(mid+1)) * ((i % (mid+1) + 1)*2));

            float r1 = Random.Range(-0.06f, 0.06f) * ratio;
            float r2 = Random.Range(-0.12f, 0.12f) * ratio;

            Vector3 dir1 = new Vector3(Mathf.Cos (theda) * r1, Mathf.Sin(theda) * r1, 0);
            Vector3 dir2 = new Vector3(Mathf.Sin (theda) * r2, -Mathf.Cos(theda) * r2, 0);

            //段數調整位置後的最終座標點
            result[i] = pos + dir1 + dir2;
        }

        return result;
    }

    //建立Mesh
    private void CreateMesh(Vector3[] points)
    {
        Vector3[] verts = GetVerts (points); //取得vertices資訊
        Vector2[] UVs = new Vector2[(points.Length-1)*4];
        int[] tris = new int[(points.Length-1)*6];

        for(int i = 0; i < points.Length-1; ++i)
        {
            //建立UV資訊
            UVs[i*4] = new Vector2(0, 0);
            UVs[i*4 + 1] = new Vector2(0, 1);
            UVs[i*4 + 2] = new Vector2(1, 0);
            UVs[i*4 + 3] = new Vector2(1, 1);

            //建立三角面資訊
            tris[i*6] = i*4 + 0;
            tris[i*6 + 1] = i*4 + 2;
            tris[i*6 + 2] = i*4 + 1;
            tris[i*6 + 3] = i*4 + 1;
            tris[i*6 + 4] = i*4 + 2;
            tris[i*6 + 5] = i*4 + 3;
        }

        //取得顏色資訊
        Color[] colors = GetColor (verts.Length);

        //建立新的Mesh
        Mesh lineMesh = lineMeshFilter.mesh;
        lineMesh.vertices = verts;
        lineMesh.triangles = tris;
        lineMesh.uv = UVs;
        lineMesh.colors = colors;
        lineMesh.RecalculateBounds ();
        lineMesh.RecalculateNormals ();
    }

    //使用AnimationCurve取得漸變顏色的資訊
    private Color[] GetColor(int count)
    {
        Color[] colors = new Color[count];
        for(int i = 0; i < colors.Length; ++i)
        {
            float time = (float)i / (float)(colors.Length-1);
            colors[i] = new Color(colorR.Evaluate(time), colorG.Evaluate(time), colorB.Evaluate(time), colorA.Evaluate(time));
        }
        return colors;
    }

    private Vector3[] GetVerts(Vector3[] points)
    {
        Vector3[] verts = new Vector3[(points.Length-1)*4];
        for(int i = 0; i < points.Length-1; ++i)
        {
            Vector3 start = points[i];
            Vector3 end = points[i+1];

            //計算線段起始點跟終點的寬
            float time = (float)i/(float)(points.Length-1);
            float timeNext = (float)(i+1)/(float)(points.Length-1);
            float startPosWidth = width.Evaluate(time)/2;
            float endPosWidth = width.Evaluate(timeNext)/2;

            //計算偏移的向量
            Vector3 dir = end - start;
            float theda = Mathf.Atan2 (dir.y, dir.x);
            Vector3 widthStartDir = new Vector3(Mathf.Sin (theda) * startPosWidth, -Mathf.Cos(theda) * startPosWidth, 0);
            Vector3 widthEndDir = new Vector3(Mathf.Sin (theda) * endPosWidth, -Mathf.Cos(theda) * endPosWidth, 0);

            //使用向量取得這線段Mesh的四個角的座標點
            Vector3 leftStartPos = start - widthStartDir;
            Vector3 rightStartPos = start + widthStartDir;
            Vector3 leftEndPos = end - widthEndDir;
            Vector3 rightEndPos = end + widthEndDir;
            verts[i*4] = leftStartPos;
            verts[i*4 + 1] = rightStartPos;
            verts[i*4 + 2] = leftEndPos;
            verts[i*4 + 3] = rightEndPos;
        }
        return verts;
    }
}


No comments:

Post a Comment