夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

Unityでメッシュのスムースを試す。

本日はUnity枠です。

前回はUnityでリメッシュを行おうとしましたが、こちらはアルゴリズムの理解が必要であり、やや時間がかかりそうでしたので、今回はメッシュのスムースにトライします。

〇メッシュのスムースとは?

 メッシュのスムースと呼ぶとシェーディングなど様々な意味を持ちますが、ここではメッシュの頂点にスムースをかけることを指します。

 メッシュの頂点はそれぞれ座標を持っていますが、スムースを掛けることでこの座標の間隔を均一にし、モデルの棘を減らす効果を持ちます。

今回は次のようなモデルを用意しました。

BlenderのUV球からランダムに選んだ頂点を棘のように伸ばしています。

これをUnity内で処理して滑らかにします。

〇スムースのコード

今回は次のようなコードを書きました。

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

using UnityEngine;

public class SmoothMesh : MonoBehaviour
{
    public int iterations = 10; // スムージングの反復回数
    public float alpha = 0.5f; // 元の頂点位置と新しい頂点位置のブレンド率(0.0 - 1.0)

    void Start()
    {
        MeshFilter meshFilter = GetComponent<MeshFilter>();
        if (meshFilter != null)
        {
            Mesh mesh = meshFilter.mesh;
            sMesh(mesh, iterations, alpha);
        }
    }

    void sMesh(Mesh mesh, int iterations, float alpha)
    {
        Vector3[] vertices = mesh.vertices;
        int[] triangles = mesh.triangles;
        Dictionary<int, List<int>> vertexNeighbors = new Dictionary<int, List<int>>();

        // 頂点の隣接関係を構築
        for (int i = 0; i < triangles.Length; i += 3)
        {
            AddNeighbor(vertexNeighbors, triangles[i], triangles[i + 1]);
            AddNeighbor(vertexNeighbors, triangles[i], triangles[i + 2]);
            AddNeighbor(vertexNeighbors, triangles[i + 1], triangles[i]);
            AddNeighbor(vertexNeighbors, triangles[i + 1], triangles[i + 2]);
            AddNeighbor(vertexNeighbors, triangles[i + 2], triangles[i]);
            AddNeighbor(vertexNeighbors, triangles[i + 2], triangles[i + 1]);
        }

        // スムージングの反復処理
        for (int iter = 0; iter < iterations; iter++)
        {
            Vector3[] newVertices = new Vector3[vertices.Length];

            for (int i = 0; i < vertices.Length; i++)
            {
                Vector3 sum = Vector3.zero;
                List<int> neighbors = vertexNeighbors[i];
                foreach (int neighbor in neighbors)
                {
                    sum += vertices[neighbor];
                }
                Vector3 average = sum / neighbors.Count;
                newVertices[i] = Vector3.Lerp(vertices[i], average, alpha);
            }

            vertices = newVertices;
        }

        mesh.vertices = vertices;
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
    }

    void AddNeighbor(Dictionary<int, List<int>> vertexNeighbors, int vertex, int neighbor)
    {
        if (!vertexNeighbors.ContainsKey(vertex))
        {
            vertexNeighbors[vertex] = new List<int>();
        }
        if (!vertexNeighbors[vertex].Contains(neighbor))
        {
            vertexNeighbors[vertex].Add(neighbor);
        }
    }
}

このコードではまず『頂点と三角形(ポリゴン)の情報を取得されます。』 次に『頂点の隣接関係を構築しスムースの指定回数頂点の位置を隣接する頂点の平均位置と元の位置のブレンドで移動します。』 最後に『メッシュの更新』を行っています。

つまりこれは取得した隣接関係の情報をもとに各頂点の座標を均一化していることになります。

このコードをメッシュのオブジェクトにアタッチすることでスムースがかかります。

元の形状

スムース回数を上げるとより滑らかになります。

上記コードの欠点として、閉じたメッシュで作成したはずにもかかわらずメッシュの破断が発生する点です。

これは①もともとのジオメトリにエッジなど実は頂点が重なっておりつながっていない箇所が存在する。②コードの問題

が考えられます。

こちらに関して解消しないとスムースとして使用できないので、例えばポリゴンごとにジオメトリが破断しないようにするなど工夫が必要そうです。

とにかくプロトタイプとしてはスムースが無事動きました。

本日は以上です。