Unity シェーダーチュートリアル 滝を作ってみる
シェーダー
今まで数回にわたって基本的なシェーダーコードの解説を書いてきましたが、
1つ1つの要素をバラバラに解説されても掴みどころが分からない、なんて事もあると思います。
なので今回はもう少し実践的にやってみたいと思います。
実践編第1弾は「滝を作ってみる」です。
過去の案件で森&滝を作った事があるので、これを解説していきます。
今回は滝のシェーダーをどのように作っていったか、
順番に積み上げて見ていった方が分かりやすいと思うので、
完成版のシェーダーコードは最後に載せます。
モデルの用意
シェーダーコードを見る前に、まずは滝のモデルを用意します。
3DCGで髪の毛を作る時の常套手段で、短冊状のモデルを重ねるバナナリーフと呼ばれる手法です。
シェーダー作成
とりあえず
さて、ではどのように滝を表現すれば良いでしょうか?
とりあえずは透明シェーダーで、上から下に水流っぽいテクスチャがスクロールしていれば、
それっぽく見えるかもしれません。やってみましょう。
Unityのシェーダーでは _Time で時間が取得できるので、アニメーションに利用することができます。
xyzwで時間の流れる速さを選べます。[x:1/20、y:1、z:2、w:3]
テクスチャを縦にスクロールさせたい場合は UV の V を _Time で動かせば良いのです。
fixed flowTex = tex2D(_MainTex, IN.uv_MainTex + half2(0, _Time.x * _Flow)).r;
こんなテクスチャをスクロールさせてみました。
明るすぎて白飛びしてしまいました。明度を調節できるようにしましょう。
_Brightness プロパティを追加しました。
横スクロールも入れてみよう
とりあえず上下に流れるようにはなりましたが、何か単調です。
微妙な横スクロールを追加して、より流れてる感が演出できないでしょうか。
縦スクロールとは違うテクスチャを使ってみましょう。こんなのにします。
何か水のしなやかな流れっぽくないですか?
ここでちょっと工夫します。縦スクロールも横スクロールも、
使用しているのは1チャンネルのみなので(0~1の透明情報が欲しいだけだから)、
1つのテクスチャにまとめてしまいましょう。
パラメーター的なテクスチャをRGBAチャンネルに振り分けて格納するのは、
メモリ削減のためにもよく使われる手法です。
今回は R チャンネルに横用、 B チャンネルに縦用を振り分けました。
横と縦でそれぞれテクスチャをサンプリングしています。
スクロールの方向が違うので _Time を使用してアニメーションさせるのは横が X 、縦が Y です。
また、横と縦でテクスチャのチャンネルを使い分けています。
fixed flowTexU = tex2D(_MainTex, IN.uv_MainTex + half2(_Time.x * _FlowU, 0)).r; // 横スクロールテクスチャ
fixed flowTexV = tex2D(_MainTex, IN.uv_MainTex + half2(0, _Time.x * _FlowV)).g; // 縦スクロールテクスチャ
だんだんと良い感じになってきました。が、皆さん気になる点がありませんか?
カオスが欲しい
3DCGというのは人の手作業で積み上げていくものなので、単調になりがちです。
特に自然や有機物を作る際には、形状の乱れや汚しなど、
カオスを入れれば入れるほど情報量が多くなり、リアルに近づいていきます。
先ほどのテクスチャスクロールを見てください。
今のままだとすべてが同じスピードで流れていて表現力に乏しく、
また、横スクロールに至っては同じ方向にしか動いていないので非常に不自然です。
もっとランダムさが欲しいですね。
バナナリーフの短冊ごとに流れるスピードを変えてみましょう。
Q. モデルの箇所によって流れるスピードを変えるにはどうすれば良いでしょうか?
① 短冊ごとにマテリアルを分けて Flow Speed を変える ・・・・・ やってられません
② 短冊ごとに違う値を設定できるようにテクスチャで描き分ける ・・・・・ ちょっとメンドクサイ
③ 短冊ごとに頂点カラーの明度を変えて Flow Speed に幅を付ける ・・・・・ 簡単そう
ということで、頂点カラーの明度によって Flow Speed を最小値~最大値に振り分けてみましょう。
頂点カラーの使い方については前回の記事に書いてあります。
先ほどのモデルにはすでに頂点カラーが仕込んであります。
Rチャンネルが横スクロール用、Gチャンネルが縦スクロール用です。
(色に惑わされないで下さい。単なる 0~1 のパラメーターです。)
注意点として、RとGで同じ濃度の頂点カラーを使ってはいけません。
同じにしてしまうと、横スクロールが遅い所では縦スクロールも遅く、
横スクロールが速い所では縦スクロールも速いという様に、
ランダムに振り分けたにもかかわらず、ランダムに見えなくなってしまうからです。
できる限りすべてをランダムにしてカオスを作り上げることが重要です。
R、G それぞれの頂点カラーが 0~1 まで、短冊ごとにランダムに振り分けられているので、
lerp(min, max, interpolation) によって、最少値~最大値の幅で補完されます。
また、横スクロールについては左方向か右方向かもランダムに振り分けたいので
round(IN.color.r) * 2 - 1 を乗算しています。
round は、四捨五入なので 0~1 に振り分けられている値が 0 か 1 のどちらかに丸められます。
そして * 2 - 1 で -1 か 1 になります。つまり左方向か右方向か(正か負か)です。
float2 uvOffset;
uvOffset.x = _Time.x * lerp(_FlowU.x, _FlowU.y, IN.color.r) * (round(IN.color.r) * 2 - 1);
uvOffset.y = _Time.x * lerp(_FlowV.x, _FlowV.y, IN.color.g);
fixed flowTexU = tex2D(_MainTex, IN.uv_MainTex + half2(uvOffset.x, 0)).r;
fixed flowTexV = tex2D(_MainTex, IN.uv_MainTex + half2(0, uvOffset.y)).g;
形状を目立ちを防ぐ
下図の印を付けた部分が少し気になります。
短冊の両端でテクスチャがスパッと切れているのでバナナリーフモデリングが目立ちます。
短冊の端に行くにしたがってグラデーションで滑らかに消してしまいましょう。
使っていなかった頂点カラーのBチャンネルを設定しました。短冊の端が0で中央が1です。
(色に惑わされないで下さい。単なる 0~1 のパラメーターです。)
単純に頂点カラーのBチャンネルも取得して乗算しているだけです。
エッジの部分が目立たなくなりました。
最終調整
最後に、色とコントラストを調整できるようにしてみましょう。
_Color プロパティを単純に乗算しています。
また、 _Gammaプロパティで Photoshop で言う、レベル補正的な事をしています。
o.Emission = pow(flowTexU * flowTexV * IN.color.b, _Gamma) * _Brightness * _Color;
おわりに
UVアニメーションと頂点カラーを利用して、滝を作ってみましたがいかがでしょうか。
さらにここにディスプレイスを足してもっとボリューム感を出したりすることもできますね。
やっていることは単純なので、あとは組み合わせのアイデア次第です。