Unityでコンポジット用素材を出力する

#自己紹介
こんにちはゆずです。@Yuzu_Unity

#はじめに
今回は一般的なVtuberではなく映像特化したVtuber？向けです。
例　Happy Elements Asia Pacific株式会社さんのリブドル！
https://www.youtube.com/watch?v=Xt8xN6Eq-hw&t=29s
のような映像を作成するためのマップ作成について
[CGWORLD (シージーワールド) 2019年 03月号](https://www.amazon.co.jp/dp/B07L854SRW/ref=cm_sw_r_tw_dp_U_x_nbRZCb4BEHP)
より
※この雑誌公開より前に作成したものですのであしからず…

#今回出力するマップ
1.ベースカラー
2.1影アルファ
3.デプス
4.落ち影(VFX用)

※この記事を読んでもわからないと思うので
GitHubにも一応プロジェクトデータあげておきました
https://github.com/yuzu-unity/Unity-Composite

#方法
まずUnityではカメラの数分レンダリングすることが可能であり、
レイヤーごとにマスクをかけることができるということを利用します。
つまりメッシュをカメラの数分用意すれば同時にすべてのマップを出力可能になります。
今回はUTS2.0採用

##マテリアル変更
メッシュをカメラの数分用意するにあたってマテリアルもその分変更しなければなりません。
そのため、マテリアル変更ツールを作成しました。
先にマテリアルをその数分用意しスクリプトで一括変更するスクリプトです。
今回は以下のようにマテリアルを用意しました
1.通常（影もあり）
2.ベースカラーのみ
3.1影のみ
4.1影の出る具合（白黒マスク）
5.落ち影

マテリアルを変更したいオブジェクトにアタッチするスクリプト

```c#:MaterialChange.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MaterialChange : MonoBehaviour
{
    public bool meshAdd;
    public List<SkinnedMeshRenderer> skinnedMeshRenderers;
    public List<MeshRenderer> meshRenderers;
    public MaterialData[] materialData;
    public bool materialChange;
    public MaterialPreset materialPreset;
    private MaterialPreset tmp;

    private void OnValidate()
    {
        if (meshAdd)
        {
            skinnedMeshRenderers.Clear();
            meshRenderers.Clear();
            AddMesh(this.gameObject);
            meshAdd = false;
        }
        
        if (!materialChange) { return; }
        materialChange = false;

        if (materialPreset == tmp) return;
        if (materialPreset == MaterialPreset.ShadowOnly) {
            for (int i = 0; i < skinnedMeshRenderers.Count; i++)
            {
                skinnedMeshRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly;
            }
            for (int i = 0; i < meshRenderers.Count; i++)
            {
                skinnedMeshRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly;
            }
        }
        else if (tmp == MaterialPreset.ShadowOnly) {
            for (int i = 0; i < skinnedMeshRenderers.Count; i++)
            {
                skinnedMeshRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.TwoSided;
            }
            for (int i = 0; i < meshRenderers.Count; i++)
            {

                skinnedMeshRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.TwoSided;
            }
        }
        tmp=materialPreset;
        int select = (int)materialPreset;

        // Debug.Log("種類 " + materialPreset + "　選択番号　" + select);

        //子を含むレイヤー変更
        //Debug.Log(materialPreset.ToString());
        ChangeLayer(this.gameObject, materialPreset.ToString());


        for (int i = 0; i < skinnedMeshRenderers.Count; i++)
        {
            Material[] mats = skinnedMeshRenderers[i].materials;
            for (int m = 0; m < skinnedMeshRenderers[i].materials.Length; m++)
            {
                mats[m] = ChangeMat(skinnedMeshRenderers[i].materials[m], select);
            }
            skinnedMeshRenderers[i].materials = mats;
        }
        for (int i = 0; i < meshRenderers.Count; i++)
        {
            Material[] mats = meshRenderers[i].materials;
            for (int m = 0; m < meshRenderers[i].materials.Length; m++)
            {
                mats[m] = ChangeMat(meshRenderers[i].materials[m], select);
            }
            meshRenderers[i].materials = mats;
        }
    }

    private void AddMesh(GameObject self)
    {
        if (self.GetComponent<SkinnedMeshRenderer>())
        {
            SkinnedMeshRenderer skintmp = self.GetComponent<SkinnedMeshRenderer>();
            skinnedMeshRenderers.Add(skintmp);
            skintmp.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.TwoSided;
        }else if (self.GetComponent<MeshRenderer>())
        {
            MeshRenderer meshtmp= self.GetComponent<MeshRenderer>();
            meshRenderers.Add(meshtmp);
            meshtmp.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.TwoSided;
        }

        foreach (Transform transform in self.transform)
        {
            AddMesh(transform.gameObject);
        }
    }
    //再起で変更
    private void ChangeLayer(GameObject self,string layer)
    {
        self.layer = LayerMask.NameToLayer(layer);
        foreach (Transform transform in self.transform)
        {
            ChangeLayer(transform.gameObject, layer);
        }
    }

    private Material ChangeMat(Material mat,int select){
        // Debug.Log(mat.name);
        for (int f = 0; f < materialData.Length; f++)
        {
            foreach (MaterialObj materialObj in materialData[f].materialObjs)
            {
                bool check = false;
                for (int i = 0; i < materialObj.material.Length; i++)
                {

                    string nameTmp = (materialObj.materialName).Replace(" ", "");
                    // Debug.Log(nameTmp + " (instance)");
                    string matname = mat.name.Replace("(Instance)", "");
                    matname = matname.Replace(" ", "");
                    // Debug.Log(matname);
                    if (matname == nameTmp + i || matname == nameTmp)
                    {
                        check = true;
                        break;
                    }
                }
                if (check)
                {
                    if (materialObj.material[select] != null)
                    {
                        // Debug.Log(materialObj.material[select].name);
                        return materialObj.material[select];
                    }
                    else
                    {
                        Debug.Log("エラー" + materialObj.materialName);
                        return mat;
                    }
                }

            }
        }
        Debug.Log("エラー　該当なし");
        return mat;
    }
}
```
//エディタ拡張
ScriptableObjectです。変更したいマテリアルデータを用意
1.通常（影もあり）
2.ベースカラーのみ
3.1影のみ
4.1影の出る具合
分です

```c#:MaterialData.cs
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections.Generic;

[System.Serializable]
public class MaterialObj
{
    public string materialName;
    [Header("ViewMaterial,normal, shade1st, shade2nd, shadeMask")]
    public Material[] material = new Material[4];
}

public enum MaterialPreset
{
    Normal,Base, Shade1st, ShadeMask,ShadowOnly
}

[CreateAssetMenu]
public class MaterialData : ScriptableObject
{
    public bool changeName;
    public MaterialObj[] materialObjs;

    private void OnValidate()
    {
        if (!changeName) { return; }
         changeName=false;
        foreach (MaterialObj materialObj in materialObjs) {
            materialObj.materialName = materialObj.material[0].name;

            for (int i = 1; i < materialObj.material.Length; i++)
            {
                if (materialObj.material[i] != null)
                {
                    materialObj.material[i].name = materialObj.material[0].name + " " + i;
                }
            }
        }
    }
}
```
##デプス書き出しポストプロセス
シェーダー手書きでもよかったのですが、
PostProcessingStack用のノードでシェーダー作れる神アセットがあったので使用します。
[Post Processing Ultimate](https://assetstore.unity.com/packages/tools/visual-scripting/post-processing-ultimate-104053)
![image.png](https://assetstorev1-prd-cdn.unity3d.com/key-image/9a66405e-006a-472f-a3f9-45ddb9dbd505.jpg)
一瞬ですねｗ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308905/55136d1e-1569-3b30-e588-95c5943c2510.png)


##カメラ用意、RanderTexture
カメラをレンダリングしたい分用意しレイヤー設定します。
Unity Recorderを用いてレンダリングするのですが、
現状1影アルファを出力できていません
AE等で作成すればいいのですが、どうせならUnity上でコンポジットしましょう
レンダーテクスチャをカスタムレンダーテクスチャを使用してコンポジットします。
過去記事参照　なんでもできますね
https://qiita.com/Yuzu_Unity/items/2c4e0a19b8fc8dd64209
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308905/6c8e904e-413a-b19e-0236-587be14e39f7.png)

あとはカスタムレンダーテクスチャをUnity Recorderに入れるだけです。
またこの方法を用いればポスプロ付きのアルファ画像も書き出せますね。

以上書き出し結果
![image_0001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308905/0fbed38f-4857-03f6-7266-883ae7835822.png)

#まとめ

ここまでする映像制作は映画くらいしかなさそう…