🥅

【Unity】Meshを一時的に書き換えるコンポーネント

に公開

これが必要となる場面はそんなに多くはないとは思いますが、元のモデルデータには手を加えず、一時的にメッシュを変更したいことがあったので作りました。

実現方法

Awake() でメッシュをコピーして編集してセットし直し、 Destroy() で削除するようにしました。
この方法だと当然プレイ中にのみ編集結果を確認できるということになります。
その分、コンポーネントを外せば元通りになるし、保存データも増えないので気軽に試せるのが良いです。

サンプル

処理内容

複数個の頂点が同じ座標に存在する場合はそれらの法線の平均を算出し、各頂点の法線を平均のものに設定し直すということを行いました。

ソースコード

Meshの法線を共通する頂点の平均の法線に書き替えるコンポーネント
#nullable enable
using System.Linq;
using UnityEngine;

/// <summary>
/// Meshの法線を共通する頂点の平均の法線に書き替えるコンポーネント
/// </summary>
public class AveragingNormalLines : MonoBehaviour
{
    private Mesh? modifiedMesh;

    private void Awake()
    {
        this.AverageNormals(this.GetComponent<MeshFilter>());
    }

    private void OnDestroy()
    {
        UnityEngine.Object.Destroy(this.modifiedMesh);
    }

    private void AverageNormals(MeshFilter meshFilter)
    {
        var baseMesh = meshFilter.mesh;

        var normals = baseMesh.normals;
        baseMesh.vertices
            .Select((x, idx) => (Pos: x, Index: idx, Normal: baseMesh.normals[idx]))
            .GroupBy(x => x.Pos)
            .ToList()
            .ForEach(x =>
            {
                var avg = x.Aggregate(Vector3.zero, (sum, next) => sum + next.Normal).normalized;
                x.ToList().ForEach(y => normals[y.Index] = avg);
            });

        this.modifiedMesh = new Mesh();
        this.modifiedMesh.SetVertices(baseMesh.vertices);
        this.modifiedMesh.SetNormals(normals);
        this.modifiedMesh.SetUVs(0, baseMesh.uv);
        this.modifiedMesh.SetIndices(baseMesh.GetIndices(0), MeshTopology.Triangles, 0);
        meshFilter.mesh = this.modifiedMesh;
    }
}

実行結果

Cubeに上記コンポーネントを付与しました。

プレイしていないときはただのCubeです。
通常時.png

プレイ開始すると、法線が書き換わったメッシュが再設定され、ライティングが変化しました。
角丸四角のような法線になるので、辺や頂点付近は隣に置いたSphereに近いライティングとなりました。
プレイ中.png

プレイ中にメッシュのインスペクタを開くと、そこでも変化を見ることができます。

コピーして何も編集していない場合
Cubeのメッシュ変更前.png
法線を書き換えた場合
Cubeの法線を平均化.png

リリテックラボ

Discussion