Unity中用Mesh畫一個圓環

Probuider

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

挺好玩的,能夠在直接Unity中根據須要用Mesh定製生成圖形,而不用建模軟件。c#

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

旋轉的時候不符合個人需求,我想要的是生成的時候旋轉中心在圓心的位置,因此準備本身定製一個。性能

目標

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

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

咱們切換到Wireframe模式下,能夠看出它是有一個一個的頂點,並經過一條條的直線鏈接起來。那麼咱們如何肯定這些頂點和線的位置呢?3d

小目標-生成一個面

其實很簡單的,咱們一步一步慢慢來。一次生成一整個會有點麻煩,咱們能夠一面一面來。只要生成了第一個面,其餘的面也是相似的方法生成就好。code

在前面咱們提到了咱們要的是生成一個圓柱體,圓柱體一個的重要性質就是能夠由一個圓形疊加產生,也就是隻要咱們生成一個圓形,就完成了大部分的工做。orm

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

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

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

原理已經知道了,那下一步只要肯定三角形頂點的位置就OK了。至於如何肯定三角形頂點的位置,咱們能夠再看下這張圖。
Mesh2.png

是否是瞬間清晰明瞭,紅線的交匯處就是圓心的位置,數字則是每一個頂點的編號。

咱們假設圓心在原點,數字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));
        }

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

而正反面能夠經過法線的朝向進行判斷,向外的面就是正面,相反的就是背面。

在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;
    }
}

未完待續。。。

相關文章
相關標籤/搜索