シェーダをグラフィカルに作れるツール「Shader Forge」を使って、ノイズ画像から立体的な地形を作ったり、シェーダで色をつけたりする方法を紹介します。作成する地形は次のような感じになります。
Shader Forgeを使うことで、難しいシェーダ言語を覚えなくてもノードを繋ぐだけで簡単にシェーダを作ることができます。画面を見ながら試行錯誤できるので、実験的なシェーダを作るのにも最適です。Shader Forgeはアセットストアから入手できます。
今回の記事の内容は次のとおりです。
- プロジェクトを作る
- テクスチャを表示する
- シェーダに対応するマテリアルを作成する
- 高さをつける
- 地形に色を付ける
- 地形に陰影を付ける
- Texture2Dノードをまとめる
- UVスクロールして地形を動かす
- 真面目に法線の再計算をして影をつける
- まとめと参考資料
プロジェクトを作る
今回はハイトマップに使うノイズ画像と、高さごとの色を指定するランプ画像、スムーズな地形を作るためのハイポリゴンなPlaneを用意します。
今回使用するサンプルは↓からダウンロード出来ます。
ダウンロードしたら、必要な素材をプロジェクトウインドウにドラッグ&ドロップし、mapをシーンビューに配置しておきましょう。
メニューバーからWindow -> Shader Forge
を選択してShader Forgeのウインドウを起動します。起動したらNew Shaderを選択して下さい。
今回はライトの影響を考慮した影付きのモデルを表示したいので「Lit(PBR)」を選択します。
起動直後は次のような画面になります。一旦不要なノードは削除しておきましょう。
ノードを削除するには、ノードを選択して⌘+Xを押します。複数のノードを選択したいときはAltキーを押したまま左ボタンでドラッグします。また、接続した線を切りたい場合は、Altキーを押した状態でマウス右ボタンをドラッグします(ちょっとヤヤコシイですね・・・)
テクスチャを表示する
まずはPlaneにテクスチャを貼り付けるところから始めましょう。右クリックしてGeometory Data -> UV Coordinates
を選択(または「uキー」で表示されるメニューから選択)してUV Coordノードを追加します。同様にProperties -> Texture 2D
を選択し、Texture2Dノードを追加します。
これらのノードが追加できたらUVノードからTexture2DノードのUVへ、Texture2DのRGBからMainのBase Colorへ接続します。
Texture2Dノードの名前をbaseに変更し、Texture2Dノード内のSelectボタンを押してノイズ画像を設定してみましょう。
また、PBRではMainノードのMetallicに値を指定する必要があります。ここではConstant Vectors -> Value
を選択し、作成したValueノードをMainノードのMetalicに接続して下さい。
これで、テクスチャを貼り付けるだけのシェーダが完成しました。Unityエディタに戻りmapシェーダを見てみるとシェーダプログラムが自動的に記述されていることがわかります。このmapシェーダは通常のシェーダファイルと同じように扱えます。
シェーダに対応するマテリアルを作成する
mapシェーダを選択した状態で右クリックし、Create -> Material
を選択して新しくマテリアルを作成してください。作成したMaterialをシーンビューのmapにドラッグ&ドロップしてください。マテリアルがアタッチできたらmapのインスペクタに表示されるMaterialの欄のbaseにノイズ画像を設定してください。シーンビューのPlaneに、いま選択したノイズ画像が表示されます。Shader Forgeで設定したテクスチャはMaterialには反映されないので注意してください!
これで、①Shader Forgeでシェーダを作成、②マテリアルの作成とセットアップ、③シーンビューで表示という一連の流れが完成しました。次からは立体的な地形になるようにシェーダを作っていきましょう。
高さをつける
続いて地形に高低差をつけましょう。「どれくらい高くするか」はノイズ画像の明度によって決めます。ここでは画像が白いところは高く、画像が黒いところは低くなるように高さを調節していきましょう。
シェーダで頂点の座標を操作するにはMainの「Vertex Offset」を使います。Vertex Offsetには「どの方向にどの程度移動させるか」のベクトルを指定します。
今回作るベクトルは、頂点の法線方向にノイズ画像の明度を掛けたものになります。数式にすると次のようになります。最後のKは頂点の移動量を調節するための係数です。
Vertex Offset = N(法線ベクトル)✕ ノイズ画像の明度(0.0〜1.0)✕ K
まず、法線の方向に関してはNormal Dirノードが用意されているので、これを配置しましょう。Geometry Data -> Normal Dir
を選択してノードを配置します。続いて法線ベクトルとノイズ画像の明度を掛け合わせるため、Multiplyノードを配置します。MultiplyノードはArithmatic->Multiply
を選択して配置して、次のように接続して下さい。
頂点の移動量を調節する係数(K)にはSliderノードを使います。SilderノードはProperies ->Slider
を選択してノードを追加ください。Siderの範囲を0〜3、Sliderの名前をheightに変更しておきましょう。
これでノイズ画像の明度によって法線方向に頂点を移動させるシェーダが完成しました。Unityエディタに戻って、インスペクタからSliderの値を調節してみてください。テクスチャと同様、Shader Forgeで設定したSliderの値は保存されないので注意してください!初期状態ではSliderの値は0になるため、スライダを動かすまでは地形の高さは変化しません。
地形に色を付ける
地形が真っ白では味気ないので、色を付けていきます。色をつける方法も先程と同様、ノイズテクスチャの明度に沿って色分けします。具体的には次のような感じにしてみましょう。
明度 | 色 |
---|---|
0~0.4 | 青 |
0.4~0.5 | 黄 |
0.5~0.7 | 緑 |
0.7~0.9 | 茶 |
0.7~1.0 | 白 |
テクスチャの明度値から色をつくるためには、Rampテクスチャと呼ばれる仕組みを使います。Rampテクスチャとは次のように、一方向に色合いの変化があるテクスチャのことで、UV座標のU値に応じた色を取り出すことが出来ます。
ノイズ画像の明度値をランプテクスチャののU値として扱うと、明度が0.2(u=0.2)なら青色、明度が0.8(u=0.8)なら茶色を取り出すことができます。これで、上に書いた表どおりの色が取り出せますね。
今回はノイズ画像の明度をU値としましたが、物体表面の明度をU値とすればトゥーンシェーダのような表現にも使うことができます。
この流れを作るため、Appendノード(Vector Operation -> Append)と、Texture2Dノードを追加して次のように接続してください。Texture2DノードにはRampという名前をつけて、Selectボタンでランプテクスチャを選択します。
ここでは、Appedノードを使ってUV座標を作っています。U値にはノイズ画像の明度値、V座標には0を入れています。作成したU座標を使ってランプテクスチャから色を取り出し、最後にその色をMainノードのDiffuseに設定しています。Unityエディタに戻って、インスペクタからRampの欄にランプテクスチャを設定して実行してみてください。
地形に陰影を付ける
実行してみると地形の高さによって色合いが変化しているのがわかります。でも、まだ全体的にのっぺりして陰影がついていません。これは頂点の座標を変化させたのは良いけれども、法線の方向を再計算していないため、すべての頂点が上を向いてしまっていることが原因です。
これでは陰影がつかずにのっぺりして見えても仕方がありません。実際の地形に沿った法線の再計算を行いましょう。
と・・・ここまで書きましたが、法線を再計算するのはやっぱり結構大変です。なので、ここではなんちゃってのライティングを考えてみましょう。
山があって、光が横から当たっていれば当然ですが明暗がつくられますね。この明暗は次の図のようにノイズテクスチャの位相を45度ずらしたものと考えることができます(ちょっと無理矢理ですが・・・)。
要するに、ノイズテクスチャは「高いところを白、低い所を黒」としているので位相を45度回せば高いところの頂点を中心として「半分は白、半分は黒」となります(何度も書きますが、この計算はなんちゃって、英語ではpseudoです。笑)
ということで、ここではノイズのテクスチャを少しだけ移動させたものを明度として、最終的なテクスチャに掛け合わせてみましょう。次のようにノードを配置してください。
ここではUVの値に(0.02, 0.02)のベクトルを足すことで、U方向とV方向のテクスチャを左上の方向に移動させたテクスチャを作っています(ここでは分かりやすいようにTexture2Dのノードを配置して、base2という名前にしています)。
最後に作成したテクスチャと、色分けしたテクスチャをMultiplyノードでかけ合わせたものをMainノードのBase Colorに接続して出力しています。また、テクスチャどおしを掛け合わせると暗くなるので、Sliderノードを使って少し明るくしています。
実行すると次のようになります。
実行する際はインスペクタからbase2にノイズテクスチャを設定するのを忘れないようにしてください。
Texture2Dノードをまとめる
先程、Texture2Dノードを追加してbase2と名前をつけましたが、インスペクタから設定する箇所が増えて不便なので、一箇所にまとめておきましょう。
次のようにもう一つTexture Assetノード(Properties->Texture Asset)を追加しbaseという名前にしましょう。続けてTexture AssetノードのTexから、Texture2DノードのTexに接続してください。Texture2DのTexに接続するとこで、インスペクタからは入力できないノードになります。
インスペクタを確認すると、テクスチャを入力する欄がbaseとrampの2つに減っていることが確認できますね。
UVスクロールして地形を動かす
最後にUVスクロールを使って地形を動かす方法を紹介します。UVスクロールは2Dゲームの背景スクロールや、水面、滝の表現など、様々な場面で使える技術です。
ここではShader Forgeを使ってUVスクロールする方法を紹介します。Shader Forgeで「時間の変化に合わせてhogehogeしたい」というときにはTimeノード(External Data->Time)を使います。
Timeノードはゲーム開始からの時間を扱うノードです。4つの出力がありますが、それぞれ時間をスケーリングしてるだけなので、使う場面に合わせたものを選びましょう。
ここでは、時間とともにUV座標のうちU座標を変化させたいので、時間変化と移動速度(u方向に0.02)をMultiplyノードで掛け合わせたものをUV座標に足しています。
次のようにノードを配置してください。
実行すると、次のように地形がu方向にスクロールしていくのがわかります。移動速度のベクトルを変化させることでUV方向にスクロールさせたり、逆方向にスクロールすることもできますよ!
Shader Forgeでプロシジャルに3Dの地形を作成してみた〜 pic.twitter.com/BbLE5Vq1l7
— 北村愛実 (@tasonco_company) 2017年9月19日
真面目に法線の再計算をして影をつける
真面目に法線の再計算をする方法を最後にサラっと紹介しておきます。法線は隣の頂点との傾きを見ることで計算できます。でも、シェーダは並列で計算されるため「隣の頂点」を見るのがとても苦手です。なので、ノイズテクスチャから仮想的に地形の傾きを計算して、その傾きから法線ベクトルを算出します。
現在注目しているテクスチャの座標値と、少し左にずれたテクスチャの明度値の差が、X方向の地形の傾きになります。同様に、現在注目しているテクスチャの座標値と、少し上にずれたテクスチャの明度値の差が、Y方向の地形の傾きになります。
この2つのベクトルとZ方向のベクトルをあわせた3次元の傾きから、法線の方向を再計算します。すこし複雑になりますが、次のようにノードを配置してください。
急に複雑になってしまいましたね(笑)でも、やっていることは通常のテクスチャを少しだけx方向に移動させたテクスチャと、少しだけy方向に移動させたテクスチャを作り、そのテクスチャと通常のテクスチャの差分をとって法線ベクトルを作成しているだけです(赤枠内)。オレンジ色で囲った部分は、次の数式のように法線の大きさが1になるようにZ方向のベクトルの大きさを決めています。
保存できたらUnityエディタで実行してみて下さい。ちゃんと陰影が反映されて立体的な地形に見えると思います。流石になんちゃっての陰影よりも遥かに綺麗ですね!
まとめと参考資料
Shader Forgeを使って、ノイズ画像から立体的な地形を作ったり、シェーダで色をつけたりする方法を紹介しました。Shader Forgeは簡単にシェーダが作れて試行錯誤できるのでオススメです!
小林信行さんの資料
Shader Forgeの使い方から、テクスチャブレンド、ライティング、トゥーンシェーダまでとってもわかり易く説明されている資料です。もはや小林さんの資料を読めば、このページはいらんのちゃうかと思わなくもないぐらい・・・笑
www.slideshare.net
Unityの教科書もよろしくおねがいしまーす!(Shaderには触れていませんが・・・笑)