[Live] Unity Mesh 建立練習 – Practicing create mesh in Unity script

看了 能力雷達圖 (unity 多邊形繪製的應用) 文章後,對於使用腳本建立 Mesh 產生莫大的興趣,便花了一點時間用自己的理解與方法重現文章中的雷達圖組件。

netchart-mesh

在參考文章的原文中,是使用一張圖片作為雷達圖的背景,而我把目標設定為連同後方的背景全部都要使用 Mesh 自行繪製。

為了達成目標,我總共使用了兩個腳本,分別可以繪製正多邊形,以及繪製中間需要依照數值變化的多邊形。

同時研究過程我也發現,透過這次練習我可以清楚的理解 Mesh 如何構成外,這些新知也可以同時應用於我正在學習的 shader,增進對 shader 背後原理的理解深度,可謂一石二鳥。

繪製正多邊形作為雷達圖背景 – MeshNetChartBg.cs

netchart-mesh1

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

public class MeshNetChartBg : MonoBehaviour {

    [Range(1, 10)]
    public int PointCount;
    public float Radius;
    public Color PlateColor;
    public Color EdgeColor;

    private Mesh m_Mesh;
    private MeshFilter m_MeshFilter;
    private MeshRenderer m_MeshRenderer;
    private LineRenderer m_LineRender;

    void Start () {
        m_MeshFilter = gameObject.GetComponent<MeshFilter>();
        if (m_MeshFilter == null) {
            m_MeshFilter = gameObject.AddComponent<MeshFilter>();
        }
        m_MeshRenderer = gameObject.GetComponent<MeshRenderer>();
        if (m_MeshRenderer == null) {
            m_MeshRenderer = gameObject.AddComponent<MeshRenderer>();
        }
        m_LineRender = gameObject.GetComponent<LineRenderer>();
        if (m_LineRender == null) {
            m_LineRender = gameObject.AddComponent<LineRenderer>();
        }

        m_Mesh = m_MeshFilter.mesh;

        m_MeshRenderer.material = new Material(Shader.Find("Particles/Additive"));

        m_LineRender.useWorldSpace = false;
        m_LineRender.material = new Material(Shader.Find("Particles/Additive"));
        m_LineRender.SetColors(EdgeColor, EdgeColor);

        RedrawChart();
    }

    public void RedrawChart() {
        RebuildMesh();
        RedrawLineRender();
    }

    public void RebuildMesh () {
        if (Radius <= 0f) {
            Debug.LogError("Radius need a positive float.");
            return;
        }

        m_Mesh.Clear();

        int VerticeCount_ = PointCount + 1;
        Vector3[] vertices_ = new Vector3[VerticeCount_];
        Vector2[] uv_ = new Vector2[VerticeCount_];
        int[] triangles_ = new int[PointCount * 3];
        Color[] colors_ = new Color[VerticeCount_];

        float degreeDelta_ = 360f / (float)PointCount;
        float degree_ = 0f, x_ = 0f, y_ = 0f;
        vertices_[0] = Vector3.zero;
        uv_[0] = Vector2.zero;
        colors_[0] = PlateColor;

        for (int i = 1; i < VerticeCount_; i++) {
            x_ = Radius * Mathf.Cos(degree_ * Mathf.Deg2Rad);
            y_ = Radius * Mathf.Sin(degree_ * Mathf.Deg2Rad);
            degree_ += degreeDelta_;

            vertices_[i] = new Vector3(x_, y_, 0);
            if (i % 2 == 0) {
                uv_[i] = new Vector2(0, 1);
            } else {
                uv_[i] = new Vector2(1, 1);
            }

            triangles_[i * 3 - 3] = 0;
            triangles_[i * 3 - 2] = (i + 1) == VerticeCount_ ? 1 : i + 1;
            triangles_[i * 3 - 1] = i;

            colors_[i] = PlateColor;

            Debug.Log(vertices_[i]);
            Debug.Log(uv_[i]);
            Debug.Log(string.Format("({0}, {1}, {2})", triangles_[i * 3 - 3], triangles_[i * 3 - 2], triangles_[i * 3 - 1]));
        }

        m_Mesh.vertices = vertices_;
        m_Mesh.uv = uv_;
        m_Mesh.triangles = triangles_;
        m_Mesh.colors = colors_;
        m_MeshFilter.mesh = m_Mesh;
    }

    public void RedrawLineRender() {
        if (Radius <= 0f) {
            Debug.LogError("Radius need a positive float.");
            return;
        }

        int VerticeCount_ = PointCount + 1;
        m_LineRender.numPositions = VerticeCount_;

        float degreeDelta_ = 360f / (float)PointCount;
        float degree_ = 0f, x_ = 0f, y_ = 0f;

        x_ = Radius * Mathf.Cos(degree_ * Mathf.Deg2Rad);
        y_ = Radius * Mathf.Sin(degree_ * Mathf.Deg2Rad);
        m_LineRender.SetPosition(PointCount, new Vector3(x_, y_, 0));

        for (int i = 0; i < PointCount; i++) {
            x_ = Radius * Mathf.Cos(degree_ * Mathf.Deg2Rad);
            y_ = Radius * Mathf.Sin(degree_ * Mathf.Deg2Rad);
            degree_ += degreeDelta_;

            m_LineRender.SetPosition(i, new Vector3(x_, y_, 0));
        }
    }
}

依照輸入值繪製雷達圖數值部分 – MeshNetChart.cs

netchart-mesh2

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

public class MeshNetChart : MonoBehaviour {

    public List<int> Values;
    public Color PlateColor;
    public Color EdgeColor;

    private Mesh m_Mesh;
    private MeshFilter m_MeshFilter;
    private MeshRenderer m_MeshRenderer;
    private LineRenderer m_LineRender;

    void Start() {
        m_MeshFilter = gameObject.GetComponent<MeshFilter>();
        if (m_MeshFilter == null) {
            m_MeshFilter = gameObject.AddComponent<MeshFilter>();
        }
        m_MeshRenderer = gameObject.GetComponent<MeshRenderer>();
        if (m_MeshRenderer == null) {
            m_MeshRenderer = gameObject.AddComponent<MeshRenderer>();
        }
        m_LineRender = gameObject.GetComponent<LineRenderer>();
        if (m_LineRender == null) {
            m_LineRender = gameObject.AddComponent<LineRenderer>();
        }

        m_Mesh = m_MeshFilter.mesh;

        m_MeshRenderer.material = new Material(Shader.Find("Particles/Additive"));

        m_LineRender.useWorldSpace = false;
        m_LineRender.material = new Material(Shader.Find("Particles/Additive"));
        m_LineRender.SetColors(EdgeColor, EdgeColor);

        RedrawChart();

    }

    public void RedrawChart() {
        RebuildMesh();
        RedrawLineRender();
    }

    public void RebuildMesh() {
        int PointCount_ = Values.Count;
        if (PointCount_ < 3) {
            Debug.LogError("Number of Values need more than 3.");
            return;
        }

        m_Mesh.Clear();

        int VerticeCount_ = PointCount_ + 1;
        Vector3[] vertices_ = new Vector3[VerticeCount_];
        Vector2[] uv_ = new Vector2[VerticeCount_];
        int[] triangles_ = new int[PointCount_ * 3];
        Color[] colors_ = new Color[VerticeCount_];

        float degreeDelta_ = 360f / (float)PointCount_;
        float degree_ = 0f, x_ = 0f, y_ = 0f;
        vertices_[0] = Vector3.zero;
        uv_[0] = Vector2.zero;
        colors_[0] = PlateColor;

        for (int i = 1; i < VerticeCount_; i++) {
            x_ = Values[i - 1] * Mathf.Cos(degree_ * Mathf.Deg2Rad);
            y_ = Values[i - 1] * Mathf.Sin(degree_ * Mathf.Deg2Rad);
            degree_ += degreeDelta_;

            vertices_[i] = new Vector3(x_, y_, 0);
            if (i % 2 == 0) {
                uv_[i] = new Vector2(0, 1);
            } else {
                uv_[i] = new Vector2(1, 1);
            }

            triangles_[i * 3 - 3] = 0;
            triangles_[i * 3 - 2] = (i + 1) == VerticeCount_ ? 1 : i + 1;
            triangles_[i * 3 - 1] = i;

            colors_[i] = PlateColor;

            Debug.Log(vertices_[i]);
            Debug.Log(uv_[i]);
            Debug.Log(string.Format("({0}, {1}, {2})", triangles_[i * 3 - 3], triangles_[i * 3 - 2], triangles_[i * 3 - 1]));
        }

        m_Mesh.vertices = vertices_;
        m_Mesh.uv = uv_;
        m_Mesh.triangles = triangles_;
        m_Mesh.colors = colors_;
        m_MeshFilter.mesh = m_Mesh;
    }

    public void RedrawLineRender() {
        int PointCount_ = Values.Count;
        if (PointCount_ < 3) {
            Debug.LogError("Number of Values need more than 3.");
            return;
        }

        int VerticeCount_ = PointCount_ + 1;
        m_LineRender.numPositions = VerticeCount_;

        float degreeDelta_ = 360f / (float)PointCount_;
        float degree_ = 0f, x_ = 0f, y_ = 0f;

        x_ = Values[0] * Mathf.Cos(degree_ * Mathf.Deg2Rad);
        y_ = Values[0] * Mathf.Sin(degree_ * Mathf.Deg2Rad);
        m_LineRender.SetPosition(PointCount_, new Vector3(x_, y_, 0));

        for (int i = 0; i < PointCount_; i++) {
            x_ = Values[i] * Mathf.Cos(degree_ * Mathf.Deg2Rad);
            y_ = Values[i] * Mathf.Sin(degree_ * Mathf.Deg2Rad);
            degree_ += degreeDelta_;

            m_LineRender.SetPosition(i, new Vector3(x_, y_, 0));
        }
    }

}

後續討論

除了單純繪製 Mesh,我也為 Mesh 使用 MeshRenderer 掛上材質,並用 LineRender 繪製邊框。於是整個程式碼的運作結果:

netchart-mesh

由上圖可以看出目前的作法有幾個明顯的問題:

  • 作為背景圖相對單調,而且沒有數值或刻度的顯示。
  • 因為使用了 “Particles/Additive" 做為材質 (Material),結果在重疊時整個爆炸,重疊部分出現了預期外的結果。(所以才分左右展示)
  • 作為邊框的  LineRender 的收尾相接處有明顯的接縫缺陷。
netchart-mesh0
重疊後的慘狀

如果要解決前兩個問題,只要設計具有刻度貼圖的材質配合適當的 uv 給背景的 Mesh 使用即可;而邊框的缺陷則可以修改程式碼,將頭尾相接處從角落上移動至線段中來解決。不過這個專案只是做為 Mesh 的練習,就不將這些細節做到完美了。

開發過程紀錄

這次的研究與開發過程全程透過 youtube 直播進行分享,因為沒有與觀看者做 Q&A 的交流,所以全程式靜音的。

參考

Advertisements

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s