
マイケルです!


整理も含めてぼちぼち体系的にまとめていこうかと思います!

シェーダーは楽しいクマね〜〜〜

第一回で紹介するのは・・・こちら!
- Lambert拡散反射
- Phong鏡面反射
- HalfLambert拡散反射
- Blinn-Phong鏡面反射
- リムライト

Lambert、Phongとその改良版の実装を紹介します。
最後に応用としてリムライトの実装もしてみます!

なんか難しそうクマがいきなり飛ばしすぎではないクマ??

まとめて紹介していくよ!!
決して分けるのが面倒くさかったわけではありません!!


実装した内容はGitHubにも上げています!
こちらも是非参照してください!!
[使用したバージョン]
| Unity | 2021.3.1f1 |
GitHub – unity-lighting-shader-sample / masarito617

参考書籍

下記の書籍を参考にさせていただきました!
HLSL シェーダーの魔導書 シェーディングの基礎からレイトレーシングまで

また、DirectX 9 シェーダプログラミングブック はDirectX9時代に発売されたもので既に廃盤ですが、シェーダー周りの実装について広く解説されている名著のため、見かけたら手に入れたほうがよいです!
僕はメルカリで粘って手に入れました!!

事前準備:内積を感じよう

ライティングで頻繁に出てくる、内積のイメージを掴みましょう!
数学に自信がある方はこの節は飛ばして問題ありません!

確かに言葉だけ聞いてもあまりピンとこないかもクマ

・ベクトル同士の向きが近いほど1に近づく
・ベクトル同士の向きが直角になるほど0に近づく
・ベクトル同士の向きが90〜180度の場合は負の値になる
といった特性を持っています。
$$ cos\theta = \frac{a \times b}{|a||b|} = \hat{a} \cdot \hat{b} = a_x b_x + a_y b_y $$

だから角度が0度の時には結果が1になるクマ

下記でドラッグすることでベクトルの向きを変えれるので、内積の値がどのように変化するか感じてみてください!


JavaScriptなので直接実装していますが、シェーダー言語の場合はdot関数として用意されていることが多いです。





これはmax関数を使用してもよいですが、シェーダー言語では専用のsaturate関数というものが用意されています。
こちらもよく見かけることになると思うので覚えておきましょう!
$$ max(0, (\hat{V_a} \cdot \hat{V_b})) = saturate(\hat{V_a} \cdot \hat{V_b}) $$

かっこいいクマ〜〜〜

僕は意味が明確になるのとカッコいいのでsaturateを使います!!

Lambert拡散反射とPhong鏡面反射

今回紹介するシェーダーは内積が分かれば何なく理解できると思います!

Lambert拡散反射

拡散反射という名の通り、全ての面に同じ角度の光が当たっていると仮定して拡散する光の量を計算する、擬似的な反射モデルです!

それを簡潔に計算できるようにしたものクマね

面にどの程度の光が当たるかを計算します。


$$ I_d = k_d cos \theta_a = k_d (\hat{N} \cdot \hat{L}) \\ k_d: 拡散反射色 $$


そのため光の向きに限らず全方位に当たる光として、環境光を加えることが多いです。
こちらの環境色Kaを加えた式が下記になります。
$$ I = k_a + k_d (\hat{N} \cdot \hat{L}) \\ k_a: 環境色 \\ k_d: 拡散反射色$$



くどいくらいだとは思いますが、気になる方は省略して記述してみてください!
実際に計算を行なっているのは下記部分です!


また、拡散反射色について今回はプロパティで設定できるようにしてみましたが、Unityシーン上のライトの色を使用する場合は_LightColor0で取得することができます。
その場合はUnityLightingCommon.cgincを追加でインクルードする必要があるため注意しましょう!

Phong鏡面反射

こちらは下記のように光沢のような反射を考慮したモデルになります。

ライトの反射ベクトルRと面から視線への向きVが追加されます。
こちらの内積を反射量として計算します。


$$ I_s = k_s cos \theta_r = k_s (\hat{V} \cdot \hat{R}) \\ k_s: 鏡面反射色$$

$$ I = k_a + k_d (\hat{N} \cdot \hat{L}) + k_s (\hat{V} \cdot \hat{R})^a \\ k_a: 環境色 \\ k_d: 拡散反射色 \\ k_s: 鏡面反射色 \\ a: 鏡面反射指数$$

このaって何クマ??

実際にライティングした例を出すと下記のような感じです。

反射量と範囲が変わるクマね


反射ベクトルはreflect関数を使用して求めることができます。
視線への向きに関しては、頂点のワールド座標を使用して計算を行う必要がある点には注意しましょう!

LambertとPhongの改良モデル

・HalfLambert拡散反射
・Blinn-Phong鏡面反射
の実装についても見てみましょう!
HalfLambert拡散反射

通常のLambertと比べて陰影部分を少し柔らかく付けることができます。

具体的には、内積結果を-1.0〜1.0から0.0〜1.0に変換した後に2乗しています。
$$ I_d = k_d ((\hat{N} \cdot \hat{L}) \times 0.5 + 0.5)^2 $$
参考:
Half Lambert – Valve Developer Community

範囲を半分にしていることからハーフランバートなのクマね(恐らく)

HalfLambertの方が柔らかく、環境光無しでもそれなりに全体は見えていることが確認できますね

実装はほぼ同じなので、フラグメントシェーダ部分のみ載せておきます。

Blinn-Phong鏡面反射

こちらは反射ベクトルRの計算負荷を抑えるために考えられた手法です。
実装時にはreflect関数を使用しましたが、中身は下記のようになっていて、それなりに複雑になっています。
$$ R=F+2(-F \cdot N)N $$


こちらと法線の向きの内積を反射量として使用する考え方です。
$$ I_s = k_s (\hat{N} \cdot \hat{H})^a $$

これでも同じように反射量が求められるクマね

実装は下記のようになります。
こちらもPhongの時とほぼ同じのため、フラグメントシェーダ部分のみ載せておきます。


LambertとBlinn-Phongモデルが用意されているようですね

リムライトの実装

こちらもこれまでの考え方を元に実装することができます。


下記のように、周りが明るくなるリムライトの場合は、光が強く出てほしいのはこれらの向きが直角な場合になります。

つまり、角度が90度に近づくほど強く、0度になるほど弱くするクマね

つまるところ、計算式は下記のようになります!
$$ I_r = 1 – max(0, (\hat{N} \cdot \hat{V})) $$

これで実装できそうな気がしてきましたね!
実際の実装内容は下記のようになります。

あとは鏡面反射の時と同様、絞りも入れることで調整できるようにしています!

おわりに

どうだったかな??

内積も感じられるようになったクマ〜〜〜

しっかり覚えておこう!!

それでは今日はこの辺で!
アデューーーー!!

【Unity】第一回 シェーダーライティング入門 〜基本のライティング〜(Lambert、Phong、HalfLambert、Blinn-Phong、リムライト)【シェーダー】〜完〜
※↓続きはこちら!












