Unityのシェーダーを使って画像をマスクする

## はじめに
[CYBIRD Advent Calendar 2021](https://qiita.com/advent-calendar/2021/cybird) 25日目担当の @garyumaru です。
サイバードで、技術統括部のエンジニアとして、イケメンシリーズの開発・運用など、Unityタイトルに限らず、iOS/Androidのネイティブアプリも含めた、さまざまな案件の技術サポートをしている通りすがりのゲームデザイナーです。
24日目は @S_onizawa さんの「新卒のコードリーディング」でした。
運用中のタイトルのサポート業務では、他の人が書いたコードを読み解いて、修正を加えていくことが多いので、参考になりますね。自分が書くコードには、コメントをできるだけ正しく記述するようにしましょう。後で改修したり、参考にする人の助けになります。

## Unityのシェーダーについて、お勉強してみた
とりあえず、シェーダーについて何もわからないっていう人は見ておきましょう。

シェーダーを書けるプログラマになろう #1
https://www.youtube.com/watch?v=wUx_Y9BgC7k

## で、何をするのか？
むずかしい話は置いておいて、シェーダーを使って、画像を特定の形に切り抜いて（マスク処理して）描画してみましょう。


## マスク処理がいい感じにされるシェーダー
[Unityでアルファマスクを使う](https://github.com/keijiro/unity-alphamask#unity-でアルファマスクを使う)で公開されているシェーダーを利用しちゃいます。

``` c#:SpriteWithMask.shader
Shader "Custom/SpriteWithMask" {
	Properties {
		_MainTex ("Base", 2D) = "white" {}
		_MaskTex ("Mask", 2D) = "white" {}
		_Color ("Color", Color) = (0.5, 0.5, 0.5, 0.5)
	}
	SubShader {
		Pass {
			Blend SrcAlpha OneMinusSrcAlpha
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			sampler2D _MainTex;
			sampler2D _MaskTex;
			float4 _Color;

			struct v2f {
				float4 pos : SV_POSITION;
				float2 uv1 : TEXCOORD0;
				float2 uv2 : TEXCOORD1;
			};

			float4 _MainTex_ST;
			float4 _MaskTex_ST;

			v2f vert (appdata_base v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos (v.vertex);
				o.uv1 = TRANSFORM_TEX (v.texcoord, _MainTex);
				o.uv2 = TRANSFORM_TEX (v.texcoord, _MaskTex);
				return o;
			}

			half4 frag (v2f i) : COLOR
			{
				half4 base = tex2D (_MainTex, i.uv1);
				half4 mask = tex2D (_MaskTex, i.uv2);
				base.w = mask.x * mask.x * mask.x;
				return base * (_Color * 2.0f);
			}
			ENDCG
		}
	} 
	FallBack Off
}
```

## メイン画像を用意する
PhotoshopやGIMPなど、画像編集ソフトで作成しましょう。
![rgby_pattern.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35434/83e40442-e5d9-7cbd-a5e7-7f07d6ffe2f4.png)


## マスク画像を用意する
RGBが同じ値で変化している画像を作ります。
InkscapeでAlpha値のグラデーション画像（RGBA）を作成して、ImageMagickで、Lab画像を生成しましょう。

```:Lab画像を生成する時に使用したImageMagickのコマンド
convert mask_rgba8.png -quality 100 -colorspace Lab -separate output.png
```

![output-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35434/37c40da2-81cf-427f-9643-d84210f5e8e4.png)

## Unityで表示してみる
1) QUADオブジェクトをシーンに生成する
2) マテリアルを作成して、シェーダー(Custom/SpriteWithMask)を設定する
3) メイン画像とマスク画像（Lab画像）を設定する
4) QUADオブジェクトにマテリアルを設定する
![スクリーンショット 2021-12-22 3.53.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35434/491d21e1-2046-b25f-a017-b52ffe6d6790.png)
これでメイン画像がマスク画像で切り抜かれて表示されるようになりました！


## 出力された画像が粗い
ここで、エディタ上で確認した時に、GameViewに表示されたものが粗い場合があります。これは下記の設定が有効になっているためなので、こちらの設定を確認してください。

GameView - Aspect - Low Resolution Aspect Ratios のチェックを外す
![ss_unity_lowratio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35434/7066848e-be6e-ee44-72e2-216ed9fab3e7.png)


## One more thing!
メイン画像を回転させてみましょう。

[そろそろShaderをやるパート7　マスクしてUVを回転させる](https://zenn.dev/kento_o/articles/580e0c58f8e3ccd67e1f)を参考にしました。

1) 回転速度を設定するプロパティを追加する _RotateSpeed("Rotate Speed", float) = 1.0
2) 変数を追加する fixed _RotateSpeed;
3) メインテクスチャのUVを回転させる frag内

``` c#:SpriteWithMaskRotation.shader
Shader "Custom/SpriteWithMaskRotation" {
	Properties {
		_MainTex ("Base", 2D) = "white" {}
		_MaskTex ("Mask", 2D) = "white" {}
		_Color ("Color", Color) = (0.5, 0.5, 0.5, 0.5)
		
		_RotateSpeed ("Rotate Speed", float) = 1.0

	}
	SubShader {
		Pass {
			Blend SrcAlpha OneMinusSrcAlpha
			CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            sampler2D _MaskTex;
            float4 _Color;

            fixed _RotateSpeed;

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv1 : TEXCOORD0;
                float2 uv2 : TEXCOORD1;
            };

            float4 _MainTex_ST;
            float4 _MaskTex_ST;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos (v.vertex);
                o.uv1 = TRANSFORM_TEX (v.texcoord, _MainTex);
                o.uv2 = TRANSFORM_TEX (v.texcoord, _MaskTex);
                return o;
            }

            half4 frag (v2f i) : COLOR
            {

                // Timeを入力として現在の回転角度を作る
                half timer = _Time.x;
                // 回転行列を作る
                half angleCos = cos(timer * _RotateSpeed);
                half angleSin = sin(timer * _RotateSpeed);
                /*       |cosΘ -sinΘ|
                R(Θ) = |sinΘ  cosΘ|  2次元回転行列の公式*/
                half2x2 rotateMatrix = half2x2(angleCos, -angleSin, angleSin, angleCos);
                //中心合わせ
                half2 uv1 = i.uv1 - 0.5;
                // 中心を起点にメインテクスチャのUVを回転させる
                i.uv1 = mul(uv1, rotateMatrix) + 0.5; 

                half4 base = tex2D (_MainTex, i.uv1);
                half4 mask = tex2D (_MaskTex, i.uv2);
                base.w = mask.x * mask.x * mask.x;
                return base * (_Color * 2.0f);
            }
			ENDCG
		}
	} 
	FallBack Off
}
```

マテリアルに設定されるシェーダーを新しく作成したもの(Custom/SpriteWithMaskRotation)に切り替えると、メイン画像が回転します。
![rotation_uv_anim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35434/cb785ab0-2260-850d-de84-1daaae89582c.gif)


## まとめ
以上のように、シェーダーを使って、画像をマクス加工して表示したり、動きをつけたりすることができました。

このようなシェーダーを使ってマスクする技術を応用することで、キャラモデルに衣装差分のモデルを着せ替えしたときに、体に衣装がめり込んだりするのを回避できるようなのですが、その話は、また別の機会に！

また、今回やったようにシェーダーをエディタで編集するのではなく、ShaderGraphを利用して、シェーダーを学んでみても良いかもしれません。
https://unity.com/ja/shader-graph

今年の[CYBIRD Advent Calendar 2021](https://qiita.com/advent-calendar/2021/cybird)は、これにて終了となります。
来年もよろしくお願いいたします！みなさま、良いお年をお迎えください！


## 参考情報
[そろそろShaderをやるパート6　画像をマスクする](https://zenn.dev/kento_o/articles/6a6ebd5f70c54e43efb9)
[そろそろShaderをやるパート7　マスクしてUVを回転させる](https://zenn.dev/kento_o/articles/580e0c58f8e3ccd67e1f)
[Unityでアルファマスクを使う](https://github.com/keijiro/unity-alphamask#unity-でアルファマスクを使う)


## Unity以外で使用したツール
[GIMP](https://www.gimp.org/)
[Inkscape](https://inkscape.org/)
[ImageMagick](https://imagemagick.org/index.php)


## シェーダー関連のホットな話題
https://unity.com/ja/visual-effect-graph

https://developers.cyberagent.co.jp/blog/archives/34009/