【Unity】モデルインポート時にアウトライン用の法線を生成する

# はじめに
<iframe width="560" height="315" src="https://www.youtube.com/embed/gPCtS33jx7Q" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
こちらの動画を見て欲しいなと思ったので自分で作ってみました。<br>
<br>
動画の方法では法線をUnity側で再計算してそれを格納していますが、<br>
コードが長くなるので、モデルの法線を元にこちらで計算したものを格納しています。<br>
なのでモデル側で法線を調整していた場合、モデルのImport設定で結果が変わります。<br>

![import.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2398516/2d61f355-940c-96e3-ec77-883cbdc428b9.png)

# スクリプト
普通に書いたら遅かったので、Job Systemを使って高速化しています。  
NativeHashMapを使用しているのでPackage ManagerでCollectionsを入れてください
![スクリーンショット 2023-06-14 210700.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2398516/95347796-b66b-ae58-b649-23fce054a99e.png)
内容についてはコメントを読めば分かると思います。
今回は計算した法線をUV1に入れてますが、お好みの場所に保存してください。

```C#
using Unity.Collections;
using Unity.Jobs;
using Unity.Burst;
using UnityEngine;
using UnityEditor;

public class NormalSmoothProcessor : AssetPostprocessor
{
    private void OnPostprocessModel(GameObject go)
    {
        //meshを取得
        var meshFilters = go.GetComponentsInChildren<MeshFilter>();
        var skinnedmeshs = go.GetComponentsInChildren<SkinnedMeshRenderer>();
        var meshs = new Mesh[meshFilters.Length + skinnedmeshs.Length];
        for (int i = 0; i < meshFilters.Length; i++)
            meshs[i] = meshFilters[i].sharedMesh;
        for (int i = 0; i < skinnedmeshs.Length; i++)
            meshs[i + meshFilters.Length] = skinnedmeshs[i].sharedMesh;

        //jobの入出力に使うデータを予め全メッシュ分用意
        var vertices = new NativeArray<Vector3>[meshs.Length];
        var normals = new NativeArray<Vector3>[meshs.Length];
        var smoothNormals = new NativeArray<Vector3>[meshs.Length];
        var handles = new JobHandle[meshs.Length];

        //全メッシュ分のjobを実行
        for (int i = 0; i < meshs.Length; i++)
        {
            //必要なデータを格納
            vertices[i] = new NativeArray<Vector3>(meshs[i].vertices, Allocator.TempJob);
            normals[i] = new NativeArray<Vector3>(meshs[i].normals, Allocator.TempJob);
            smoothNormals[i] = new NativeArray<Vector3>(meshs[i].vertexCount, Allocator.TempJob);

            var Job = new Job {
                vertices = vertices[i],
                normals = normals[i],
                smoothNormal = smoothNormals[i],
            };

            //全てのメッシュのjobを実行してから待つ
            handles[i] = Job.Schedule();
        }
        for (int i = 0; i < meshs.Length; i++)
        {
            handles[i].Complete();
            //お好みの場所に保存
            meshs[i].SetUVs(1, smoothNormals[i]);

            vertices[i].Dispose();
            normals[i].Dispose();
            smoothNormals[i].Dispose();
        }
    }
    [BurstCompile]
    struct Job : IJob
    {
        [ReadOnly] public NativeArray<Vector3> vertices;
        [ReadOnly] public NativeArray<Vector3> normals;
        [WriteOnly] public NativeArray<Vector3> smoothNormal;

        public void Execute()
        {
            var normalsum = new NativeHashMap<Vector3, Vector3>(vertices.Length,Allocator.TempJob);

            //同じposの頂点の法線を合算
            for (int i = 0; i < vertices.Length; i++)
                if (!normalsum.TryAdd(vertices[i], normals[i]))
                    normalsum[vertices[i]] += normals[i];

            //normalizeした法線を配列に格納
            for (int i = 0; i < vertices.Length; i++)
                smoothNormal[i] = normalsum[vertices[i]].normalized;

            normalsum.Dispose();
        }
    }
}
```
後はこれをコピペしてプロジェクト内に置いとけばimport時に勝手に実行されます。

# アウトライン用ShaderGraph
一応サンプル乗せておきます。
UV1から取り出した法線を普通にその方向に頂点を押し出してるだけですね。
![スクリーンショット 2023-06-14 212153.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2398516/9e0d166e-0ca6-cfd1-23dc-8a51fc9fc894.png)
# 使ってみる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2398516/e8ad5841-c973-177b-aa32-3cb2fecb9e0b.png)
ちゃんと斜めに押し出されてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2398516/d8785da0-d0ca-02e6-0adc-a34d1291c39a.png)
使用したモデル：[こここや - ソラハ【オリジナル3Dモデル】](https://booth.pm/ja/items/4208278)
髪の毛とかも良くなってそうですね。

# おわりに
今回初めて記事を書いてみたのと、JobSystemを始めて触ったので、
何かおかしいところがあっても温かい目で見てください。