Unity中用Mesh畫一個圓環

Probuider

前幾天在做一個小項目的時候,用到了Unity自帶的一個包其中的Arch生成1/4圓。

挺好玩的,可以在直接Unity中根據需要用Mesh定製生成圖形,而不用建模軟件。

但是存在一個小問題,就是在使用的時候他的中心點是在生成圖形的左下角。

旋轉的時候不符合我的需求,我想要的是生成的時候旋轉中心在圓心的位置,所以準備自己定製一個。

目標

關於生成圖形的原理可以參考這篇文章,講得雖然不算很詳細,但足夠了解基本概念了。

目標是生成下面圖中的一個1/4空心圓柱體

我們切換到Wireframe模式下,可以看出它是有一個一個的頂點,並通過一條條的直線連接起來。那麼我們如何確定這些頂點和線的位置呢?

小目標-生成一個面

其實很簡單的,我們一步一步慢慢來。一次生成一整個會有點麻煩,我們可以一面一面來。只要生成了第一個面,其他的面也是類似的方法生成就好。

在前面我們提到了我們要的是生成一個圓柱體,圓柱體一個的重要性質就是可以由一個圓形疊加產生,也就是只要我們生成一個圓形,就完成了大部分的工作。

我們知道3D建模就是由一個一個的三角形組合成的,所以我們要用三角形來模擬來一個空心的圓。

在Probuilder中生成這樣一個空心圓柱體用的是Arch,它有幾個參數,分別是
\(\color{#1E90FF}{Radius}\) 半徑,圓心到最外圈的距離
\(\color{#1E90FF}{Thickness}\) 厚度,圓心到最外圈的距離-圓心到最內圈的距離
\(\color{#1E90FF}{Depth}\) 深度
\(\color{#1E90FF}{NumberOfSides}\) 由多少個面組成,面越多越平滑,性能也越差
\(\color{#1E90FF}{DrawArchDegrees}\) 總共繪製的角度

\(\color{#1E90FF}{NumberOfSides}\)中的面是指由兩個三角形一頭一尾拼成的梯形,多個頭大腳小的梯形拼在一起便成了我們需要的圓形。

原理已經知道了,那下一步只要確定三角形頂點的位置就OK了。至於如何確定三角形頂點的位置,我們可以再看下這張圖。

是不是瞬間清晰明了,紅線的交匯處就是圓心的位置,数字則是每個頂點的編號。

我們假設圓心在原點,数字0-1所在的線為180度線。\(\color{#1E90FF}{Increment}\) = \(\color{#1E90FF}{DrawArchDegrees}\)/\(\color{#1E90FF}{NumberOfSides}\)就是線與線之間的角度。每條線的角度可以由\(\color{#1E90FF}{180-Increment*i}\)得到。i為第幾條線。

線上的點可以由\(\color{#1E90FF}{y = r* sinθ, y = r* cosθ}\)得到。

        //頂點坐標
        vertexList.Clear();
        float incrementAngle = DrawArchDegrees / NumberOfSides;
        //小於等於是因為n+1條線才能組成n個面
        for (int i = 0; i <= NumberOfSides; i++)
        {
            float angle = 180 - i * incrementAngle;
            float innerX = (Radius - Thickness) * Mathf.Cos(angle * Mathf.Deg2Rad);
            float innerY = (Radius - Thickness) * Mathf.Sin(angle * Mathf.Deg2Rad);
            vertexList.Add(new Vector3(innerX, innerY, 0));
            float outsideX = Radius * Mathf.Cos(angle * Mathf.Deg2Rad);
            float outsideY = Radius * Mathf.Sin(angle * Mathf.Deg2Rad);
            vertexList.Add(new Vector3(outsideX, outsideY, 0));
        }

在上面的代碼中我們已經計算出了頂點的位置,下一步我們要做的是按順序插入三角形頂點的位置。從這篇文章中我們可以知道,只有是三角形是正面的情況下才會被渲染。

而正反面可以通過法線的朝向進行判斷,向外的面就是正面,相反的就是背面。

在Unity中,法線的朝向可以由左手法則得到。拿出左手,伸直,拇指與其他四個指頭垂直,然後四指彎曲,指尖朝向循環的方向,拇指就指向法線的方向。

也就是說在上圖中,我們想渲染三角形,順序應該是類似這樣的012,321, 234, 543。

        //三角形索引
        triangleList.Clear();
        int direction = 1;
        for (int i = 0; i < NumberOfSides * 2; i++)
        {
            int[] triangleIndexs = getTriangleIndexs(i, direction);
            direction *= -1;
            for (int j = 0; j < triangleIndexs.Length; j++)
            {
                triangleList.Add(triangleIndexs[j]);
            }
        }

\(\color{#F08080}{getTriangleIndexs}\)代碼如下

    int[] getTriangleIndexs(int index, int direction)
    {
        int[] triangleIndexs = new int[3] { 0,1,2};
        for (int i = 0; i < triangleIndexs.Length; i++)
        {
            triangleIndexs[i] += index;
        }
        if (direction == -1)
        {
            int temp = triangleIndexs[0];
            triangleIndexs[0] = triangleIndexs[2];
            triangleIndexs[2] = temp;
        }
        return triangleIndexs;
    }

至於uv坐標就更簡單了,把內圈頂點uv坐標中的Y固定為0,外圈頂點uv坐標中的Y固定為1,而x坐標由\(\color{#1E90FF}{1/NumberOfSides}\)得到:

    //UV索引
    uvList.Clear();
    for (int i = 0; i <= NumberOfSides; i++)
    {
        float angle = 180 - i * incrementAngle;
        float littleX = (1.0f / NumberOfSides) * i;
        uvList.Add(new Vector2(littleX, 0));
        float bigX = (1.0f / NumberOfSides) * i;
        uvList.Add(new Vector2(bigX, 1));
    }

完整代碼如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//[RequireComponent(typeof(MeshFilter))]
//[RequireComponent(typeof(MeshRenderer))]
//[ExecuteInEditMode]
public class DrawArch : MonoBehaviour
{
    public float Radius = 20.0f;                //外圈的半徑
    public float Thickness = 10.0f;             //厚度,外圈半徑減去內圈半徑
    public float Depth = 1.0f;                  //厚度
    public float NumberOfSides = 30.0f;         //由多少個面組成
    public float DrawArchDegrees = 90.0f;       //要繪畫多長
    public Material archMaterial = null;
    
    private List<Vector3> vertexList = new List<Vector3>();
    private List<int> triangleList = new List<int>();
    private List<Vector2> uvList = new List<Vector2>();

    // Start is called before the first frame update
    void Start()
    {
        GenerateVertex();
    }

    void GenerateVertex()
    {
        //頂點坐標
        vertexList.Clear();
        float incrementAngle = DrawArchDegrees / NumberOfSides;
        //小於等於是因為n+1條線才能組成n個面
        for (int i = 0; i <= NumberOfSides; i++)
        {
            float angle = 180 - i * incrementAngle;
            float innerX = (Radius - Thickness) * Mathf.Cos(angle * Mathf.Deg2Rad);
            float innerY = (Radius - Thickness) * Mathf.Sin(angle * Mathf.Deg2Rad);
            vertexList.Add(new Vector3(innerX, innerY, 0));
            float outsideX = Radius * Mathf.Cos(angle * Mathf.Deg2Rad);
            float outsideY = Radius * Mathf.Sin(angle * Mathf.Deg2Rad);
            vertexList.Add(new Vector3(outsideX, outsideY, 0));
        }

        //三角形索引
        triangleList.Clear();
        int direction = 1;
        for (int i = 0; i < NumberOfSides * 2; i++)
        {
            int[] triangleIndexs = getTriangleIndexs(i, direction);
            direction *= -1;
            for (int j = 0; j < triangleIndexs.Length; j++)
            {
                triangleList.Add(triangleIndexs[j]);
            }
        }

        //UV索引
        uvList.Clear();
        for (int i = 0; i <= NumberOfSides; i++)
        {
            float angle = 180 - i * incrementAngle;
            float littleX = (1.0f / NumberOfSides) * i;
            uvList.Add(new Vector2(littleX, 0));
            float bigX = (1.0f / NumberOfSides) * i;
            uvList.Add(new Vector2(bigX, 1));
        }
        Mesh mesh = new Mesh()
        {
            vertices = vertexList.ToArray(),
            uv = uvList.ToArray(),
            triangles = triangleList.ToArray(),
        };

        mesh.RecalculateNormals();
        gameObject.AddComponent<MeshFilter>().mesh = mesh;
        gameObject.AddComponent<MeshRenderer>().material = archMaterial;
    }

    int[] getTriangleIndexs(int index, int direction)
    {
        int[] triangleIndexs = new int[3] { 0,1,2};
        for (int i = 0; i < triangleIndexs.Length; i++)
        {
            triangleIndexs[i] += index;
        }
        if (direction == -1)
        {
            int temp = triangleIndexs[0];
            triangleIndexs[0] = triangleIndexs[2];
            triangleIndexs[2] = temp;
        }
        return triangleIndexs;
    }
}

未完待續。。。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!