この記事は、シェーダーアドベントカレンダー Advent Calendar 2019の8日目の記事です。
ローポリでアンビエントオクルージョンが効かないような形だとのぺっとしてしまいます。頂点色を手動で設定するのは面倒だし、その程度のシェーダーなら自作できるかな?と思って作ってみたので公開します。
2019/12/9 シェーダー言語はCgは廃止の方向であり、HLSLの方を調べるのがよいでしょうと、さやちゃんぐbotさんから教えていただきましたので追記しました。
2019/12/12 Cg/GLSLについて、さやちゃんぐbotさんから追加で教えていただいたことについても追記しました。
目次
高さグラデーションシェーダーHeightGradationShaderの使い方
まずは作成したシェーダーの使い方をご紹介します。
シェーダーの作成
- UnityのProjectウィンドウから、Create > Shader > Unlitを選択して、Unlitシェーダーを作成します
- 名前は
HeightGradationUnlitShader
などでOKです - 作成したシェーダーをダブルクリックしてエディターで開きます
- 元のスクリプトを消して、以下のスクリプトを貼り付けて保存してください
以上でシェーダーの準備完了です。あとはマテリアルに設定すれば動作します。
マテリアルの作成とオブジェクトへのアタッチ
- マテリアルを作成して、シェーダーをUnlit > HeightGradationShaderに変更します
パラメーター
設定できるのは、テクスチャー、上端の高さと色、下端の高さと色です。
以上です。
このシェーダーはUnlitなので光の影響は受けません。フォグは有効です。
ざっくりと考え方
シェーダーは時々気が向くと触る程度で、記憶には何も残っていない初心者です。天神いなさんのこちらのブログを読みつつ作成しました。基本的な知識はほぼこれで得られました。
やりたかったことは以下の通りです。
- オブジェクトの上端と下端の高さと色を設定する
- オブジェクトの各頂点の高さから、設定に合わせた頂点色を計算して頂点カラーを設定する
以上から、必要な仕様は以下の通り。
- float型の値を2つと、色を2つ、マテリアルから設定できるようにする
- 頂点の高さから、上端と下端に対する内分率を求める
- 上端の色と下端の色と求めた内分率から、頂点の色を決定
- 求めた頂点の色を適用する
実装の解説
ブログに従って、Unlitシェーダーから作成しました。
パラメーターの設定
マテリアルで設定するパラメーターはProperties
で宣言します。高さはFloat
、色はColor
で宣言すればよいので、以下の通りです。
// 3: Properties { _MainTex("Texture", 2D) = "white" {} _TopY("TopY", float) = 0.5 _TopColor("TopColor", Color) = (1, 1, 1, 1) _BottomY("BottomY", float) = -0.5 _BottomColor("BottomColor", Color) = (0, 0, 0, 1) }
MainTex
はもともとあったテクスチャの宣言で、それ以外が追加したパラメーターです。これでマテリアルから欲しいパラメーターを設定できるようになります。ここで使える変数型はこちら。
処理に必要なデータの定義
頂点シェーダーからフラグメントシェーダーに渡すデータを定義します。v2f
というのがそれです。頂点色を渡したいので、vert_color
という名前のfloat4
型のパラメーターを追加しました。
// 32: struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vert_color : TEXCOORD2; float4 vertex : SV_POSITION; };
プロパティの値を受け取るための変数を定義します。プロパティの最初に書いたのと同じ名前の変数を定義すればOKです。高さの値は制度はそれほど必要ないのでhalf
にしました。float
、half
、fixed
のどれを使うかの基準はこちらにあります。
// 42:
half _TopY;
half _BottomY;
float4 _TopColor;
float4 _BottomColor;
頂点シェーダー
今回の肝はここです。追加したのは以下の52,53行目の2行です。
// 47: v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); half t = (v.vertex.y - _BottomY) / (_TopY - _BottomY); o.vert_color = lerp(_BottomColor, _TopColor, saturate(t)); UNITY_TRANSFER_FOG(o,o.vertex); return o; }
頂点の座標はv.vertex
で渡されます。これはモデル座標系、つまり親のオブジェクトを原点とした座標です。今回やりたかったのは、正にこの座標系での高さでの判定だったのでそのままこの値が使えます。
与えられた高さを、下端なら0、上端なら1になるように変換します。
- y座標から下端を引いて、下端の高さが0になるようにする
- 求めた値を、上端-下端で割って、上端の高さが1になるようにする
saturate
を使って、0未満は0に、1以上は1に丸め込むlerp
に下端の色、上端の色、求めた内分率を渡して、頂点に対応する色を求める
以上で求めた色を、宣言しておいたvert_color
に放り込んで頂点シェーダーは完了です。
saturateについて
saturate
は組み込み関数です。最初はmax
とmin
で求めましたが、Clamp
ぐらいあるだろうと調べて見つけました。UnityのシェーダーはCg/HLSLと名乗っているnVIDIAのCg言語とMicrosoftのHLSLを混ぜた怪しいやつなのですが、とりあえず同じようなものらしいので、どちらかのリファレンスに載ってるやつは使えるだろうということで試したら的中でした。
HLSLのリファレンスはこちら。
Cgの組み込みライブラリのリファレンスはこちら。
追記
CgとHLSLについて、さやちゃんぐbotさんからコメントいただきました!
ちょっぴり記事の内容を補足すると、現状Unityでシェーダーを扱うならもうCg言語は無視してもほぼ問題ないでしょう。
— さやちゃんぐbot (@songofsaya_) 2019年12月9日
マイクロソフトが公開しているHLSLのリファレンスだけ見てればOKです。今後のSRP環境ではCGPROGRAMがHLSLPROGRAMに代わっていくことでしょう!
HLSLの方で調べるのがよさそうです。この辺もやもやしてたのでスッキリしました^^
フラグメントシェーダー
フラグメントシェーダーは簡単です。受け取った色を、テクスチャーの色に掛けるだけです。以下の63行目がそれです。
// 58: fixed4 frag(v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); // gradation col = col * i.vert_color; // apply fog UNITY_APPLY_FOG(i.fogCoord, col); return col; }
これで出来上がり!!
Cgか、HLSLか、GLSLか(12/12追記)
今から始めるならどれだ、というのが悩みどころです。Unityは当初はCg言語を採用していたようですが、開発元のnVIDIAがCg言語の開発から手を引くと宣言してしまいました。Cgのリファレンスに「これからWindows向けにシェーダーをやるならHLSLがいいよ~」的に書いてあります。
この辺りの流れについて、さやちゃんぐbotさんの以下のスライドの11~14ページあたりで触れられています。
しかし、ターゲットがスマホの場合はどうなんだ?となります。AndroidはGLSL、iOSは古いのはGLSLで最近のはMetalです。この辺りについては、Cg/HLSLでも問題が起きることがあるそうです。
モバイルを考えると話がややこしくなりますよね。開発時はHLSLで書いて、UnityがHLSLをGLSLにトランスパイルしてくれる。ただ、例えばOculusGoとかQuestでトラブった時に追いかけていくと、GLSLの仕様にぶつかったり…みたいな。
— さやちゃんぐbot (@songofsaya_) 2019年12月9日
裏で何が起きていたか~みたいなの、今年WebGLスクールにいってわかった
UnityはGLSLには対応はしてはいるけど、HLSLの方を主に据えているのでGLSLを使うには注意点があるっぽい。iOSはMetalだからGLSLで書いたとしても安泰ではありません。結局、オールラウンドな選択肢はないというのが現状のようです。開発リソースが小さい個人開発の場合は、Unityさんの仕組みに乗っかってHLSLベースで開発して、不具合が発生したらGLSLやらMetalやらと仕様を突き合わせて対応する、というのが現実的っぽいです。
まとめ
高さ限定という用途が制限されまくりの習作シェーダーの完成です。
- 必要なプロパティを定義
- 利用する機能を指定
- 受け渡したいデータを定義
- 頂点とフラグメントシェーダーを書く
という流れがおおよそ把握できたので、簡単なシェーダーなら自作できそうです。画面中心から同心円状にグラデーションをかけるのも作りたかったので、それもおおよそ見当つきました。
今回のは高さ限定でしたのでとてもシンプルでしたが、高さ以外の方向を基準にする場合は、基準となる向きベクトルをプロパティに追加 > 向きベクトルを単位ベクトルにする > 単位向きベクトルと座標の内積を求める > 求めた内積の値に対して、今回と同じ処理をする、でいけると思います。
何かのお役に立てば幸いです!