Amplify Shader Editorを用いてメッシュを雪で覆うシェーダーを作る

こんにちは、落雷です。[VRChatアドベントカレンダー2019](https://adventar.org/calendars/3950)の20日目の記事となります。
前日の記事はせぎゅさんの[【VRChat】VRC_TriggerのActions一覧](https://qiita.com/segur/items/a4520ef31ddaf9c18f87)でした。
本記事では、ノードベースでシェーダーを作ることが出来る[Amplify Shader Editor](https://assetstore.unity.com/packages/tools/visual-scripting/amplify-shader-editor-68570)を用いて作った積雪シェーダーの紹介と、簡単な解説を行いたいと思います。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">雪を積もらせるシェーダー <a href="https://t.co/Tplnu58rKi">pic.twitter.com/Tplnu58rKi</a></p>&mdash; 落雷 (@rakurai5) <a href="https://twitter.com/rakurai5/status/1197189386527334400?ref_src=twsrc%5Etfw">November 20, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>こんな感じのやつです。

##シェーダーの解説
###ノード全体
![SnapCrab_NoName_2019-12-18_22-9-58_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/bf46ce08-3d0e-c52a-856d-34cb9cb3e404.png)
上のブロックでメッシュを雪で覆うためのマスクを生成し、その後作成したマスクをアルファとしてベースとなるテクスチャに対し雪を合成しています。また、Fresnelノードを利用して雪にフレネル効果を加えています。
###マスクの作成
![SnapCrab_NoName_2019-12-18_21-38-32_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/fd23bef9-6542-82ee-bba9-3d820c04c336.png)
メッシュの上方が雪で覆われているような見た目にしたいので、WorldNormalノードにノーマルマップを入力し、Y方向のみを出力しています。その後のAddノードを用いた処理でマスクの濃度（覆う雪の量？）を調整出来るようにしています。![SnapCrab_NoName_2019-12-18_21-52-43_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/c8a74638-a553-1b11-b2c5-6dba4190c4c7.png)ここではマスクをシャープ化する処理を行っています。
このノードのパターンはマスクをシャープ化する際に頻繁に使われるそうです。
###雪にフレネル効果を加える
![SnapCrab_NoName_2019-12-18_22-9-49_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/814e41ce-e45f-b6ba-7856-f229ab1ca851.png)
雪はシンプルにColorノードを用いて作っています。色を変えれば苔むしたりや灰を積もらせることも出来るかもしれません。
その後はフレネル効果の強さを調整出来るようにしたFresnelノードをアルファとして、LerpノードでSnowColorとFresnelColorを合成します。
###マスクを利用してメインテクスチャと雪を合成する
![SnapCrab_NoName_2019-12-18_22-9-36_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/bd8998a7-25a0-ef79-8ef7-4e274f0f830a.png)
Y方向のWorldNormalノードを用いて作ったマスクをLerpノードのAlphaに接続し、ベーステクスチャと雪を合成して完成です。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">アドカレ記事用 メッシュを覆う雪の量をPositionパラメーターで調整する。 <a href="https://t.co/gFQax2zN2v">pic.twitter.com/gFQax2zN2v</a></p>&mdash; 落雷 (@rakurai5) <a href="https://twitter.com/rakurai5/status/1207291362913079296?ref_src=twsrc%5Etfw">December 18, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>最終的にはこんな感じになりました、ちゃんとメッシュ上方にのみ雪が積もっています。やったね。

###Cutoutバリエーション
![SnapCrab_NoName_2019-12-18_22-51-57_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/3f6c05e5-0e97-cc2e-105f-317aee40a6c2.png)葉っぱをアルファチャンネルを含んだテクスチャで表現しているような木を雪で覆いたかったので、AlphaCutoutに対応したバリエーションも作りました。
![SnapCrab_NoName_2019-12-18_22-51-11_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/62addac4-07dc-4ce6-e1bf-3e65d1d85472.png)マスターノードのBlend ModeをMaskedに変更し、MainTextureのAlphaポートをマスターノードのOpacity Maskに接続します。
変更点はそれだけです。
##おまけ
###カスタムライティング
このシェーダーはアバターモデルに適用してVRChatに持ちこむことも出来ますが、通常ASEで生成したコードはライティングモデルとしてStandard(PBR)が使われます。
このまま使えないことも無いですが、VRChatのワールドのライティング環境を考えるとこのままでは少々心もとないです。
というわけでライティングモデルをカスタムライティングに切り替え、ライトの処理を自前で行っていきます。
###コード全体
![SnapCrab_NoName_2019-12-19_23-20-45_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/005fdf48-4744-70a6-d9e3-34685536683a.png)
実装したい機能ごとにグループを作って各処理を作った後に上部でコンポジット、マスターノードのCustom Lightingノードに繋いでいます。

基本的には一般的なトゥーンシェーダーで用いられる処理をそのままノードで実装しています。ライティングとシャドウの生成部分のみ抜粋して解説を行います。
###ライティング
![SnapCrab_NoName_2019-12-19_23-57-51_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/fccdc214-d571-6800-c4e7-13c7da8639e3.png)
リアルタイムライトに関する処理を行った後、球面調和の結果を加算し、clampノードで0-1の範囲に収めています。

LightColorノードで強度が乗算された光源の色、Light Attenuationノードでポイントライトやスポットライトの距離減衰を取得出来るため、この二つを乗算してシーン内のリアルタイムライトの情報を取得しています。

通常であれば、球面調和として入ってくるLightProbeやEnvironment Lightingの情報はビルトイン関数である`ShadeSH9`を使うことで取得できますが、現時点でASEにはそれらを取得するためのノードが存在しない為、Custom Expressionノードに
`return ShadeSH9(half4(0,0,0,1));`
を書くことで取得しています。

###シャドウ
![SnapCrab_NoName_2019-12-20_0-29-17_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/f6665174-1fc6-359a-5508-44b2d5c81a7c.png)
全体図が長すぎる

![SnapCrab_NoName_2019-12-20_0-13-34_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/dafc8162-e743-5385-67fc-d0cef6061486.png)
一般的にメッシュ表面の明るさを計算する際にはシーン内のライトの方向とメッシュの法線の内積を取ります。
トゥーンシェーダーを作る際はここで得た計算結果に対し*0.5+0.5することでハーフランバートモデルを作り、影の付き方をゆるやかにすることが多いです。
ASEにはWorld Space Light Dirというライト方向を取得出来るノードが用意されているため、このノードとWorldNormalノードの内積を取れば計算できそうです。ASE公式のトゥーンシェーダーのサンプルでも同様の処理が行われています。

…ですが、ASEにはその辺りの処理をまとめて行ってくれるHalf Lambert Termという便利なノードが用意されているため、そちらを使う方が早いです。 今回は影の生成にこのノードを利用します。
![SnapCrab_NoName_2019-12-20_0-20-10_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/d81cc6de-ef8f-ac71-566f-b80bb3ac2a07.png)
Half Lambert Termノードの下の方で行っている処理ですが、シーン内にディレクショナルライトが存在しない場合に疑似的なライトを当てるための処理です。Ifノードを利用し、シーン内のライトの強さが0の場合はこちらが採用されるようにしています。

![SnapCrab_NoName_2019-12-20_0-39-51_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/ddb5115f-95a7-29b8-9dc3-18f3f7bc44ff.png)
表面の明るさの情報を取得した後はコントラスト等々の調整を行っていますが、最終的にRemapノードを用いて影の境界ぼかし(Feather)の処理を加えています。<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">アドカレ記事用　Remapノードを用いた境界ぼかし(Feather)の実装 <a href="https://t.co/D0csUDGRiI">pic.twitter.com/D0csUDGRiI</a></p>&mdash; 落雷 (@rakurai5) <a href="https://twitter.com/rakurai5/status/1207689018030215174?ref_src=twsrc%5Etfw">December 19, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>![SnapCrab_NoName_2019-12-20_0-50-14_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/5065d4f9-06fb-f5fc-7386-ed8194acfe20.png)最後にLerpノードを用いて影色を指定出来るようにしています。
###コンポジット
![SnapCrab_NoName_2019-12-20_0-52-23_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/c5b9b28e-424f-f9dd-3031-4adfde95173b.png)
SnowCover含めて今までに作った処理をくっ付けて終わりです。
VRChatのライティング環境にある程度耐えられる作りになったとは思います。


##あとがき
簡単にですがシェーダーの実装について解説をさせて頂きました。

シェーダー入門してまだ日が浅いですが、ノードベースのエディタでの制作は途中の計算結果が見えやすい+トライアンドエラーがしやすいので適当にノードを繋いでいくだけでも結構楽しいです。
「シェーダーに興味があるけどプログラミングとかしたことないし…」という人はノードベースで制作することを考えてみてもいいかもしれません。

今回紹介した積雪シェーダーはMITライセンスのもとBoothにて配布を行っています。ASEのノードの情報を保持しているため、ASEを既にお持ちの方であればシェーダーファイルをASEで開いて改変を行うことも出来ます。
ライトのベイクにも対応しているため、ワールド制作等々で使って頂けると幸いです。
[SnowCoverShader](https://rakuraiworks.booth.pm/items/1736548)

明日の記事はfotflaさんによる [VRChatで動くポスターを作る](https://qiita.com/fotfla/items/69011f91554e21995399)です！
![SnapCrab_NoName_2019-12-18_23-1-6_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335958/fcffb544-f9ec-388b-dc0b-2c9ad64a41db.png)
身近にあるオブジェクトを雪まみれにして今年の冬を乗り越えよう！