Unity Mesh Mesh画球

Posted 御雪妃舞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
        
    



最终效果如图:



我梦寐以求的球啊,你终于出来了,也许你们有更方便的算法,也许有些步骤写的不详细,你们可以自己尝试体验下,楼主笨笨的。算了九张草稿纸,才算理解。
这是楼主的草稿纸:



慢慢喷吧,欢迎多多指教!

以上是关于Unity Mesh Mesh画球的主要内容,如果未能解决你的问题,请参考以下文章

Unity Mesh Mesh 平面图形的贴图

关于Unity中Mesh网格的详解

一Unity 生成几种常用模型mesh-----基类

unity导入fbx模型后获取其中mesh的绝对世界坐标

Unity MeshMeshFilterMeshRenderer扫盲

THREE.Mesh 的法线与原始模型不同