Unity Mesh(三) Mesh畫球

關於畫球一開始真是一點思路都沒有,樓主也查了好多資料,比較有代表性的是兩篇帖子。


一篇是Jasper Flick的帖子,一個很厲害的人:

http://www.binpress.com/tutorial/creating-an-octahedron-sphere/162#comments


這一篇的思路是根據柏拉圖體,正八面體分割成的球。


第二篇是OpenGL或者XNA回答的思路,是根據柏拉圖體正二十面體畫的


http://gamedev.stackexchange.com/questions/31308/algorithm-for-creating-spheres#


如果你能直接看懂上面兩篇中的任何一篇,那麼樓主下面寫的對於你來說都是廢話,你可以直接不用看了。


一、思路


簡單的說下,首先是畫出一個正八面體,這個我們上一篇文章:Unity Mesh(二) Mesh畫立方體和八面體,已經寫了怎麼畫正八面體,然後我們的思路是取每條邊的重點,細分三角形,比如那正八面體的一個面來說,我們拆分一次的情況如圖所示:




兩次的情況如圖:



以此類推,根據前面兩篇我們可以瞭解到,Mesh畫圖形必須知道三角形的頂點和三角形的點順序,這樣的話我們需要知道的參數有三個,三角形的頂點數,三角形的個數,三角形的頂點順序

下面我們根據這三個數據的要求,一次進行計算說明。


二、計算三角形的個數


             我們以正八面體中的一個面爲例,我們拆分一次會有四個小三角形(這裏不算總共,我們Mesh畫以最小單位的三角形畫),拆分兩次會有16個三角形,這樣的話我們假設拆分次數爲s(subdivisions),這樣每個面會有4^s個三角形,我們的正八面體有八個面,我們一共就有8*4^s個三角形,也就是2^(2s+3)個三角形。


結論:三角形的個數是:2^(2s+3)


三、計算三角形的頂點數


這裏我們以四分之一個正八面體爲單位進行運算,舉列如圖:





1次拆分:1+2+1=4
2次拆分:1+2+3+2+1=9
3次拆分:1+2+3+4+5+4+3+2+1=25

以一個面來說我們用r(row)表示三角形的行數,r=2^s就代表一個面的三角形的行數,這樣我們四分之一的正八面體有的頂點數爲(r+1)^2.

現在我們知道了1/4個正八面體有(r+1)^2.個頂點,但是整個合並不是4*(r+1)^2個頂點,因爲我們還要捨去重合的點,爲了更好的理解,我在Untiy裏放了4個1/4個正八面體來演示合併後的頂點的計算流程。




如圖所示,是四個四分之一正八面體,每個單個都有(r+1)^2個頂點,但是當上面兩個合併時:



我們發現重合了兩條我們計算的邊,那麼重複計算的就是這兩條邊上的頂點,看如下圖的演示:



所以,當我們兩個四分之一的正八面體合併時,重複了2r+1個頂點,那麼就可以理解爲我們二分之一個正八面體有(r+1)^2-(2r+1)個頂點。

圖示:



最後我們再將這兩個合併,我們會發現重合了四條邊:




由此可得我們正八面體拆分後一共有4(r+1)^2-2(2r+1)-4r個頂點



這是我們常規的算法下我們需要的頂點數,我們可以用0次拆分6個頂點,1次拆分18個頂點進行驗證這個公式,樓主自己驗證過是沒問題的。


四、頂點優化


上面一步中我們已經計算出了正八面體拆分後的頂點數,但是這個還是不夠的,我們還得添加,在我們計算三角形順序的時候我們會遇到這樣的問題:




這樣我們三角形的順序就變爲
0,1,2,
                0,2,3
0,3,4
0,4,5

這樣的話,我們的腳本寫起了就方便多了。

0次拆分,我們添加1個頂點。
1次拆分,我們添加3個頂點。
2次拆分,我們添加7個頂點。

相當於每有一個橫着的四邊形,我們添加一個,於是我們添加了2r-1個頂點。

綜合之前的,我們mesh的Verticles的大小爲4(r+1)^2-8r-2+(2r-1)=4(r+1)^2-3(2r+1).

五、三角形的頂點順序


    三角形的頂點順序,首先我們三角形的頂點數組的大小應該就是三角形的個數*3,然後我們從頂點或者底部的點出發,遞歸遍歷即可,這裏我是直接參考的Jasper Flick的方法。

 private static int CreateLowerStrip(int steps, int vTop, int vBottom, int t, int[] triangles)
    {
        for (int i = 1; i < steps; i++)
        {
            triangles[t++] = vBottom;
            triangles[t++] = vTop - 1;
            triangles[t++] = vTop;

            triangles[t++] = vBottom++;
            triangles[t++] = vTop++;
            triangles[t++] = vBottom;
        }
        triangles[t++] = vBottom;
        triangles[t++] = vTop - 1;
        triangles[t++] = vTop;
        return t;
    }


六、畫球


這些條件都有了,我們來畫球,詳細的代碼如下:
using UnityEngine;
using System.Collections;

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class DrawOctahedronSphere1 : MonoBehaviour
{
    public Material mat;

    private static Vector3[] directions = {
        Vector3.left,
        Vector3.back,
        Vector3.right,
        Vector3.forward
    };

    void Start()
    {
        DrawSphere(1, 1);
    }

    public void DrawSphere(int subdivisions = 0, float radius = 1)
    {
        gameObject.GetComponent<MeshRenderer>().material = mat;

        Mesh mesh = GetComponent<MeshFilter>().mesh;
        mesh.Clear();

        int resolution = 1 << subdivisions;
        Vector3[] vertices = new Vector3[(resolution + 1) * (resolution + 1) * 4 - 3*(resolution * 2 + 1)];
        int[] triangles = new int[(1 << (subdivisions * 2 + 3)) * 3];
        CreateOctahedron(vertices, triangles, resolution);

        Debug.LogError(triangles.Length + "  " + vertices.Length);

        foreach (var item in triangles)
        {
            Debug.Log(item);
        }

        foreach (var item in vertices)
        {
            Debug.Log(item);
        }

 

        mesh.vertices = vertices;
        mesh.triangles = triangles;
        

    }

    private static void CreateOctahedron(Vector3[] vertices, int[] triangles, int resolution)
    {
        int v = 0, vBottom = 0, t = 0;

        vertices[v++] = Vector3.down;

        for (int i = 1; i <= resolution; i++)
        {
            float progress = (float)i / resolution;
            Vector3 from, to;
            vertices[v++] = to = Vector3.Lerp(Vector3.down, Vector3.forward, progress);
            for (int d = 0; d < 4; d++)
            {
                from = to;
                to = Vector3.Lerp(Vector3.down, directions[d], progress);
                t = CreateLowerStrip(i, v, vBottom, t, triangles);
                v = CreateVertexLine(from, to, i, v, vertices);
                vBottom += i > 1 ? (i - 1) : 0;
            }
            vBottom = v - 1 - i * 4;
        }

        for (int i = resolution - 1; i >= 1; i--)
        {
            float progress = (float)i / resolution;
            Vector3 from, to;
            vertices[v++] = to = Vector3.Lerp(Vector3.up, Vector3.forward, progress);
            for (int d = 0; d < 4; d++)
            {
                from = to;
                to = Vector3.Lerp(Vector3.up, directions[d], progress);
                t = CreateUpperStrip(i, v, vBottom, t, triangles);
                v = CreateVertexLine(from, to, i, v, vertices);
                vBottom += i + 1;
            }
            vBottom = v - 1 - i * 4;
        }

        vertices[vertices.Length - 1] = Vector3.up;

        for (int i = 0; i < 4; i++)
        {
            triangles[t++] = vBottom;
            triangles[t++] = v;
            triangles[t++] = ++vBottom;
        }
    }

    private static int CreateVertexLine(Vector3 from, Vector3 to, int steps, int v, Vector3[] vertices)
    {
        for (int i = 1; i <= steps; i++)
        {
            vertices[v++] = Vector3.Lerp(from, to, (float)i / steps);
        }
        return v;
    }

    private static int CreateLowerStrip(int steps, int vTop, int vBottom, int t, int[] triangles)
    {
        for (int i = 1; i < steps; i++)
        {
            triangles[t++] = vBottom;
            triangles[t++] = vTop - 1;
            triangles[t++] = vTop;

            triangles[t++] = vBottom++;
            triangles[t++] = vTop++;
            triangles[t++] = vBottom;
        }
        triangles[t++] = vBottom;
        triangles[t++] = vTop - 1;
        triangles[t++] = vTop;
        return t;
    }

    private static int CreateUpperStrip(int steps, int vTop, int vBottom, int t, int[] triangles)
    {
        triangles[t++] = vBottom;
        triangles[t++] = vTop - 1;
        triangles[t++] = ++vBottom;
        for (int i = 1; i <= steps; i++)
        {
            triangles[t++] = vTop - 1;
            triangles[t++] = vTop;
            triangles[t++] = vBottom;

            triangles[t++] = vBottom;
            triangles[t++] = vTop++;
            triangles[t++] = ++vBottom;
        }
        return t;
    }

   


}

這裏默認大小爲1,拆分一次的效果如圖:


拆分四次的效果如圖



基本上四次就夠了,按照Jasper的計算,6次是上限,也許到這裏,你會問,這不是個球啊,是的,這裏還差一步,我們沒有設置頂點的法線,球的法線,是從球心到頂點的,於是我們加上法線的代碼:
private static void Normalize(Vector3[] vertices, Vector3[] normals)
    {
        for (int i = 0; i < vertices.Length; i++)
        {
            normals[i] = vertices[i] = vertices[i].normalized;
        }
    }

還有就是球的大小用radius*verticle就好,我們把拆分次數和半徑寫到控制面板上,最後我們優化後的完整代碼如下:
using UnityEngine;
using System.Collections;

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class DrawOctahedronSphere : MonoBehaviour
{
    public Material mat;

    public int subdivisions;
    public int radius;

    private static Vector3[] directions = {
        Vector3.left,
        Vector3.back,
        Vector3.right,
        Vector3.forward
    };

    void Start()
    {
        DrawSphere(subdivisions, radius);
    }

    public void DrawSphere(int subdivisions = 0, float radius = 1)
    {
        if (subdivisions > 4)
        {
            subdivisions = 4;
        }

        gameObject.GetComponent<MeshRenderer>().material = mat;

        Mesh mesh = GetComponent<MeshFilter>().mesh;
        mesh.Clear();

        int resolution = 1 << subdivisions;
        Vector3[] vertices = new Vector3[(resolution + 1) * (resolution + 1) * 4 - 3 * (resolution * 2 + 1)];
        int[] triangles = new int[(1 << (subdivisions * 2 + 3)) * 3];
        CreateOctahedron(vertices, triangles, resolution);

        if (radius != 1f)
        {
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i] *= radius;
            }
        }

        Vector3[] normals = new Vector3[vertices.Length];
        Normalize(vertices, normals);

        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.normals = normals;

    }

    private static void CreateOctahedron(Vector3[] vertices, int[] triangles, int resolution)
    {
        int v = 0, vBottom = 0, t = 0;

        vertices[v++] = Vector3.down;

        for (int i = 1; i <= resolution; i++)
        {
            float progress = (float)i / resolution;
            Vector3 from, to;
            vertices[v++] = to = Vector3.Lerp(Vector3.down, Vector3.forward, progress);
            for (int d = 0; d < 4; d++)
            {
                from = to;
                to = Vector3.Lerp(Vector3.down, directions[d], progress);
                t = CreateLowerStrip(i, v, vBottom, t, triangles);
                v = CreateVertexLine(from, to, i, v, vertices);
                vBottom += i > 1 ? (i - 1) : 0;
            }
            vBottom = v - 1 - i * 4;
        }

        for (int i = resolution - 1; i >= 1; i--)
        {
            float progress = (float)i / resolution;
            Vector3 from, to;
            vertices[v++] = to = Vector3.Lerp(Vector3.up, Vector3.forward, progress);
            for (int d = 0; d < 4; d++)
            {
                from = to;
                to = Vector3.Lerp(Vector3.up, directions[d], progress);
                t = CreateUpperStrip(i, v, vBottom, t, triangles);
                v = CreateVertexLine(from, to, i, v, vertices);
                vBottom += i + 1;
            }
            vBottom = v - 1 - i * 4;
        }

        vertices[vertices.Length - 1] = Vector3.up;

        for (int i = 0; i < 4; i++)
        {
            triangles[t++] = vBottom;
            triangles[t++] = v;
            triangles[t++] = ++vBottom;
        }
    }

    private static int CreateVertexLine(Vector3 from, Vector3 to, int steps, int v, Vector3[] vertices)
    {
        for (int i = 1; i <= steps; i++)
        {
            vertices[v++] = Vector3.Lerp(from, to, (float)i / steps);
        }
        return v;
    }

    private static int CreateLowerStrip(int steps, int vTop, int vBottom, int t, int[] triangles)
    {
        for (int i = 1; i < steps; i++)
        {
            triangles[t++] = vBottom;
            triangles[t++] = vTop - 1;
            triangles[t++] = vTop;

            triangles[t++] = vBottom++;
            triangles[t++] = vTop++;
            triangles[t++] = vBottom;
        }
        triangles[t++] = vBottom;
        triangles[t++] = vTop - 1;
        triangles[t++] = vTop;
        return t;
    }

    private static int CreateUpperStrip(int steps, int vTop, int vBottom, int t, int[] triangles)
    {
        triangles[t++] = vBottom;
        triangles[t++] = vTop - 1;
        triangles[t++] = ++vBottom;
        for (int i = 1; i <= steps; i++)
        {
            triangles[t++] = vTop - 1;
            triangles[t++] = vTop;
            triangles[t++] = vBottom;

            triangles[t++] = vBottom;
            triangles[t++] = vTop++;
            triangles[t++] = ++vBottom;
        }
        return t;
    }


    private static void Normalize(Vector3[] vertices, Vector3[] normals)
    {
        for (int i = 0; i < vertices.Length; i++)
        {
            normals[i] = vertices[i] = vertices[i].normalized;
        }
    }

}

最終效果如圖:




我夢寐以求的球啊,你終於出來了,也許你們有更方便的算法,也許有些步驟寫的不詳細,你們可以自己嘗試體驗下,樓主笨笨的。算了九張草稿紙,纔算理解。

這是樓主的草稿紙:




慢慢噴吧,歡迎多多指教!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章