うにてぃブログ

UnityやUnreal Engineの記事を書いていきます

【Unity】重心座標系を用いたワイヤーフレームの描画方法

今回は、Unityで重心座標系を利用してオブジェクトのワイヤーフレームを描画する方法を紹介します。

ワイヤーフレーム表示はデバッグや特殊なビジュアルエフェクトに役立ちますが、重心座標系を使うことで効率的に実装できます。

重心座標系とは

重心座標系(バリセントリック座標とも呼ばれます)は、三角形の各頂点に対する重みを表す座標系です。三角形内の任意の点を、頂点の重みの組み合わせとして表現できます。この性質を利用して、シェーダー内でエッジを検出し、ワイヤーフレームを描画することが可能です。

実装手順

1. 重心座標系の割り当て

まず、メッシュの各頂点に重心座標系を割り当てます。今回は頂点カラーに重心座標系のデータを割り当て、共有頂点による問題を解決するために新しくメッシュを作成します。

using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
public class BarycentricCoordinatesAssigner : MonoBehaviour
{
    [SerializeField]
    private MeshFilter _meshFilter;

    private Mesh _generateMesh;
    
    private void Reset()
    {
        _meshFilter = GetComponent<MeshFilter>();
    }

    private void OnDestroy()
    {
        if (_generateMesh)
        {
            Destroy(_generateMesh);
            _generateMesh = null;
        }
    }

    private void Start()
    {
        var mesh = _meshFilter.sharedMesh;
        var triangles = mesh.triangles;
        var verteces = mesh.vertices;
        
        var trianglesLength = triangles.Length;

        var barycentricCoords = new Color[trianglesLength];
        var newTriangles = new int[trianglesLength];
        var newVerteces = new Vector3[trianglesLength];
        
        for (int i = 0; i < trianglesLength; i += 3)
        {
            for (int j = 0; j < 3; j++)
            {
                var index = i + j;
                
                newTriangles[index] = index;
                newVerteces[index] = verteces[triangles[index]];
                var color = new Color(0, 0, 0);
                color[j] = 1f;
                barycentricCoords[index] = color;
            }
        }

        var newMesh = new Mesh();
        newMesh.vertices = newVerteces;
        newMesh.triangles = newTriangles;
        newMesh.colors = barycentricCoords;

        _generateMesh = newMesh;
        _meshFilter.mesh = _generateMesh;
    }
}

これを適応するとメッシュの頂点カラーは以下のようになります

2. Shader の作成

次に、重心座標系を利用するカスタムシェーダーを作成します。頂点カラーの最小値を取得し、それを太さ用のパラメータと比較してワイヤーフレームを描画します。

Shader "Custom/Wireframe"
{
    Properties
    {
        _WireColor ("Wireframe Color", Color) = (1,1,1,1)
        _Thickness ("Wireframe Thickness", Range(0.0001, 1.0)) = 0.02
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct appdata
            {
                float4 vertex : POSITION;
                float4 color : COLOR;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 color : COLOR;
            };

            fixed4 _WireColor;
            float _Thickness;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.color = v.color;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float edge = min(min(i.color.r, i.color.g), i.color.b);
                float alpha = step(edge, _Thickness);
                clip(alpha - 0.01);
                return _WireColor;
            }
            ENDCG
        }
    }
}

3. 動作確認

先ほど作成したスクリプトとシェーダーを使用してマテリアルを作成し、オブジェクトに適用します。シーンを再生すると、以下のようにワイヤーフレームが表示されます。

まとめ

重心座標系を利用することで、追加のジオメトリや複雑なシェーダーを使わずに効率的にワイヤーフレームを描画できます。

注意点

ワイヤーフレームの太さを完全に均一にすることは難しい場合があります。重心座標系を利用した方法では、線の太さは三角形の形状やサイズに依存します。 ・Unity Editor以外の環境(ビルド後の実行環境)では、メッシュのRead/Writeが有効になっている必要があります。メッシュのインポート設定でRead/Write Enabledをチェックしてください。