10
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

こんにちは、落雷です。VRChatアドベントカレンダー2019の20日目の記事となります。
前日の記事はせぎゅさんの【VRChat】VRC_TriggerのActions一覧でした。
本記事では、ノードベースでシェーダーを作ることが出来るAmplify Shader Editorを用いて作った積雪シェーダーの紹介と、簡単な解説を行いたいと思います。

こんな感じのやつです。

シェーダーの解説

ノード全体

SnapCrab_NoName_2019-12-18_22-9-58_No-00.png
上のブロックでメッシュを雪で覆うためのマスクを生成し、その後作成したマスクをアルファとしてベースとなるテクスチャに対し雪を合成しています。また、Fresnelノードを利用して雪にフレネル効果を加えています。

マスクの作成

SnapCrab_NoName_2019-12-18_21-38-32_No-00.png
メッシュの上方が雪で覆われているような見た目にしたいので、WorldNormalノードにノーマルマップを入力し、Y方向のみを出力しています。その後のAddノードを用いた処理でマスクの濃度(覆う雪の量?)を調整出来るようにしています。SnapCrab_NoName_2019-12-18_21-52-43_No-00.pngここではマスクをシャープ化する処理を行っています。
このノードのパターンはマスクをシャープ化する際に頻繁に使われるそうです。

雪にフレネル効果を加える

SnapCrab_NoName_2019-12-18_22-9-49_No-00.png
雪はシンプルにColorノードを用いて作っています。色を変えれば苔むしたりや灰を積もらせることも出来るかもしれません。
その後はフレネル効果の強さを調整出来るようにしたFresnelノードをアルファとして、LerpノードでSnowColorとFresnelColorを合成します。

マスクを利用してメインテクスチャと雪を合成する

SnapCrab_NoName_2019-12-18_22-9-36_No-00.png
Y方向のWorldNormalノードを用いて作ったマスクをLerpノードのAlphaに接続し、ベーステクスチャと雪を合成して完成です。

最終的にはこんな感じになりました、ちゃんとメッシュ上方にのみ雪が積もっています。やったね。

Cutoutバリエーション

SnapCrab_NoName_2019-12-18_22-51-57_No-00.png葉っぱをアルファチャンネルを含んだテクスチャで表現しているような木を雪で覆いたかったので、AlphaCutoutに対応したバリエーションも作りました。
SnapCrab_NoName_2019-12-18_22-51-11_No-00.pngマスターノードのBlend ModeをMaskedに変更し、MainTextureのAlphaポートをマスターノードのOpacity Maskに接続します。
変更点はそれだけです。

おまけ

カスタムライティング

このシェーダーはアバターモデルに適用してVRChatに持ちこむことも出来ますが、通常ASEで生成したコードはライティングモデルとしてStandard(PBR)が使われます。
このまま使えないことも無いですが、VRChatのワールドのライティング環境を考えるとこのままでは少々心もとないです。
というわけでライティングモデルをカスタムライティングに切り替え、ライトの処理を自前で行っていきます。

コード全体

SnapCrab_NoName_2019-12-19_23-20-45_No-00.png
実装したい機能ごとにグループを作って各処理を作った後に上部でコンポジット、マスターノードのCustom Lightingノードに繋いでいます。

基本的には一般的なトゥーンシェーダーで用いられる処理をそのままノードで実装しています。ライティングとシャドウの生成部分のみ抜粋して解説を行います。

ライティング

SnapCrab_NoName_2019-12-19_23-57-51_No-00.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
全体図が長すぎる

SnapCrab_NoName_2019-12-20_0-13-34_No-00.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
Half Lambert Termノードの下の方で行っている処理ですが、シーン内にディレクショナルライトが存在しない場合に疑似的なライトを当てるための処理です。Ifノードを利用し、シーン内のライトの強さが0の場合はこちらが採用されるようにしています。

SnapCrab_NoName_2019-12-20_0-39-51_No-00.png
表面の明るさの情報を取得した後はコントラスト等々の調整を行っていますが、最終的にRemapノードを用いて影の境界ぼかし(Feather)の処理を加えています。

SnapCrab_NoName_2019-12-20_0-50-14_No-00.png最後にLerpノードを用いて影色を指定出来るようにしています。

コンポジット

SnapCrab_NoName_2019-12-20_0-52-23_No-00.png
SnowCover含めて今までに作った処理をくっ付けて終わりです。
VRChatのライティング環境にある程度耐えられる作りになったとは思います。

あとがき

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

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

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

明日の記事はfotflaさんによる VRChatで動くポスターを作るです!
SnapCrab_NoName_2019-12-18_23-1-6_No-00.png
身近にあるオブジェクトを雪まみれにして今年の冬を乗り越えよう!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
10
Help us understand the problem. What are the problem?