
マイケル
みなさんこんにちは!
マイケルです!
マイケルです!

エレキベア
こんにちクマ〜〜〜

マイケル
ここ最近はずっとシェーダー周りを触っていてある程度慣れてきたので、
整理も含めてぼちぼち体系的にまとめていこうかと思います!
整理も含めてぼちぼち体系的にまとめていこうかと思います!

エレキベア
いや〜〜〜
シェーダーは楽しいクマね〜〜〜
シェーダーは楽しいクマね〜〜〜

マイケル
まずは基礎となるライティング関連のシェーダー実装について何回かに分けて書いていこうかと思います!
第一回で紹介するのは・・・こちら!
第一回で紹介するのは・・・こちら!
- Lambert拡散反射
- Phong鏡面反射
- HalfLambert拡散反射
- Blinn-Phong鏡面反射
- リムライト

マイケル
シェーダーの学習を始めたら初めに出てくるであろう
Lambert、Phongとその改良版の実装を紹介します。
最後に応用としてリムライトの実装もしてみます!
Lambert、Phongとその改良版の実装を紹介します。
最後に応用としてリムライトの実装もしてみます!

エレキベア
HalfLambertとBlinn-Phong・・・
なんか難しそうクマがいきなり飛ばしすぎではないクマ??
なんか難しそうクマがいきなり飛ばしすぎではないクマ??

マイケル
一見難しそうだけど、どちらも元の考え方を少しアレンジしただけだから
まとめて紹介していくよ!!
決して分けるのが面倒くさかったわけではありません!!
まとめて紹介していくよ!!
決して分けるのが面倒くさかったわけではありません!!

エレキベア
(分けた方がブログPV的にはいいのにクマ・・・)

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

エレキベア
毎度お馴染みクマね
参考書籍

マイケル
シェーダー実装や内容をまとめるにあたり、
下記の書籍を参考にさせていただきました!
下記の書籍を参考にさせていただきました!
HLSL シェーダーの魔導書 シェーディングの基礎からレイトレーシングまで

マイケル
HLSL シェーダーの魔導書 は割と最近発売されたので情報も比較的新しく、ハンズオン形式で一から解説してくれるため初めの一冊におすすめです!!
また、DirectX 9 シェーダプログラミングブック はDirectX9時代に発売されたもので既に廃盤ですが、シェーダー周りの実装について広く解説されている名著のため、見かけたら手に入れたほうがよいです!
僕はメルカリで粘って手に入れました!!
また、DirectX 9 シェーダプログラミングブック はDirectX9時代に発売されたもので既に廃盤ですが、シェーダー周りの実装について広く解説されている名著のため、見かけたら手に入れたほうがよいです!
僕はメルカリで粘って手に入れました!!

エレキベア
日本語で解説されているシェーダー書籍はこの辺りが一番クマ〜〜〜
事前準備:内積を感じよう

マイケル
シェーダー実装の話に入る前にまずはウォーミングアップ!
ライティングで頻繁に出てくる、内積のイメージを掴みましょう!
数学に自信がある方はこの節は飛ばして問題ありません!
ライティングで頻繁に出てくる、内積のイメージを掴みましょう!
数学に自信がある方はこの節は飛ばして問題ありません!

エレキベア
内積・・・
確かに言葉だけ聞いてもあまりピンとこないかもクマ
確かに言葉だけ聞いてもあまりピンとこないかもクマ

マイケル
内積は下記の式で表すことができる式のことで、ベクトル同士の内積を計算する場合、
・ベクトル同士の向きが近いほど1に近づく
・ベクトル同士の向きが直角になるほど0に近づく
・ベクトル同士の向きが90〜180度の場合は負の値になる
といった特性を持っています。
・ベクトル同士の向きが近いほど1に近づく
・ベクトル同士の向きが直角になるほど0に近づく
・ベクトル同士の向きが90〜180度の場合は負の値になる
といった特性を持っています。

エレキベア
正規化したベクトル同士の内積はcosθに対応してるのクマね
だから角度が0度の時には結果が1になるクマ
だから角度が0度の時には結果が1になるクマ

マイケル
こちらの説明だけではあまりピンとこない方もいると思うので、p5.jsで簡単なシミュレータを作ってみました!
下記でドラッグすることでベクトルの向きを変えれるので、内積の値がどのように変化するか感じてみてください!
下記でドラッグすることでベクトルの向きを変えれるので、内積の値がどのように変化するか感じてみてください!

エレキベア
実際に手で触ってみると分かりやすいクマね〜〜

マイケル
実際に内積計算している関数は下記になります!
JavaScriptなので直接実装していますが、シェーダー言語の場合はdot関数として用意されていることが多いです。
JavaScriptなので直接実装していますが、シェーダー言語の場合はdot関数として用意されていることが多いです。

マイケル
触ってみると先ほど説明した特性になっていることが確認できます。

エレキベア
内積はもう充分クマ〜〜〜〜

マイケル
ライティングの基本となるLambert拡散反射の実装では、ライトの向きのベクトルと法線ベクトルの内積を取ることで、どの程度の光の量が面に当たっているかを計算します。

エレキベア
光がまっすぐ面に当たっていると最大の1が返ってくるクマね

マイケル
そして90度を超える角度(負の値)は無視して0で丸めることが多いです。
これはmax関数を使用してもよいですが、シェーダー言語では専用のsaturate関数というものが用意されています。
こちらもよく見かけることになると思うので覚えておきましょう!
これはmax関数を使用してもよいですが、シェーダー言語では専用のsaturate関数というものが用意されています。
こちらもよく見かけることになると思うので覚えておきましょう!

エレキベア
サチュレイト・・・
かっこいいクマ〜〜〜
かっこいいクマ〜〜〜

マイケル
パフォーマンス的にはどちらを使用してもさほど変わりないという噂なので、好きな方を使いましょう!
僕は意味が明確になるのとカッコいいのでsaturateを使います!!
僕は意味が明確になるのとカッコいいのでsaturateを使います!!

エレキベア
クマもサチュレイトを使うクマ〜〜〜
Lambert拡散反射とPhong鏡面反射

マイケル
内積が分かったところでシェーダー実装に移っていきましょう!
今回紹介するシェーダーは内積が分かれば何なく理解できると思います!
今回紹介するシェーダーは内積が分かれば何なく理解できると思います!

エレキベア
楽しみクマ〜〜〜
Lambert拡散反射

マイケル
まずは最も基本となるLambert拡散反射から!
拡散反射という名の通り、全ての面に同じ角度の光が当たっていると仮定して拡散する光の量を計算する、擬似的な反射モデルです!
拡散反射という名の通り、全ての面に同じ角度の光が当たっていると仮定して拡散する光の量を計算する、擬似的な反射モデルです!

エレキベア
本来、光は様々な物質に反射しているクマが
それを簡潔に計算できるようにしたものクマね
それを簡潔に計算できるようにしたものクマね

マイケル
計算は単純で、光源の向きLと面の法線Nの内積を取ることで、
面にどの程度の光が当たるかを計算します。
面にどの程度の光が当たるかを計算します。

エレキベア
ここでさっきの内積の考えが出てきたクマね

マイケル
これに拡散反射色Kdも加えた式が下記になります!

エレキベア
非常にシンプルクマね

マイケル
しかし、これだけでは内積の特性上、角度が90度付近の面がかなり真っ暗になってしまいます。
そのため光の向きに限らず全方位に当たる光として、環境光を加えることが多いです。
こちらの環境色Kaを加えた式が下記になります。
そのため光の向きに限らず全方位に当たる光として、環境光を加えることが多いです。
こちらの環境色Kaを加えた式が下記になります。

エレキベア
環境光+拡散反射光で実装するのクマね

マイケル
こちらをシェーダーで実装したものがこちらになります!

マイケル
計算式に準拠してかなり丁寧に記述しています。
くどいくらいだとは思いますが、気になる方は省略して記述してみてください!
実際に計算を行なっているのは下記部分です!
くどいくらいだとは思いますが、気になる方は省略して記述してみてください!
実際に計算を行なっているのは下記部分です!

エレキベア
さっきの計算式まんまクマね

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

エレキベア
これがライティングの基本になるクマね
Phong鏡面反射

マイケル
そして次はPhong鏡面反射です!
こちらは下記のように光沢のような反射を考慮したモデルになります。
こちらは下記のように光沢のような反射を考慮したモデルになります。

マイケル
考え方としては、Lambert拡散反射の時に加えて、
ライトの反射ベクトルRと面から視線への向きVが追加されます。
こちらの内積を反射量として計算します。
ライトの反射ベクトルRと面から視線への向きVが追加されます。
こちらの内積を反射量として計算します。

エレキベア
Lambertにちょちょいと足した感じクマね

マイケル
鏡面反射色Ksを加えた反射量の計算は下記のようになります。

マイケル
Lambert計算で使用した環境光、拡散反射光に加えてこの鏡面反射光を加えたものを最終的な光の量として計算します。

エレキベア
考え方としては同じようなものクマね
このaって何クマ??
このaって何クマ??

マイケル
aのべき乗している部分は鏡面反射指数といって、いわゆる反射量の絞りを入れるために使用するよ!
実際にライティングした例を出すと下記のような感じです。
実際にライティングした例を出すと下記のような感じです。

エレキベア
なるほどクマ
反射量と範囲が変わるクマね
反射量と範囲が変わるクマね

マイケル
こちらのシェーダー実装は下記になります!

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

エレキベア
基本的な計算の考え方はLambertの時と変わらないクマね
LambertとPhongの改良モデル

マイケル
LambertとPhongの実装を知ったところで、これらを改良した
・HalfLambert拡散反射
・Blinn-Phong鏡面反射
の実装についても見てみましょう!
・HalfLambert拡散反射
・Blinn-Phong鏡面反射
の実装についても見てみましょう!
HalfLambert拡散反射

マイケル
HalfLambert拡散反射は、Lambert拡散反射の時に問題となっていた角度が90度近くになると真っ暗な部分が出来てしまうという問題を解消したものです。
通常のLambertと比べて陰影部分を少し柔らかく付けることができます。
通常のLambertと比べて陰影部分を少し柔らかく付けることができます。

マイケル
拡散反射を計算する際に下記式を使用することで、陰影を柔らかくしています。
具体的には、内積結果を-1.0〜1.0から0.0〜1.0に変換した後に2乗しています。
具体的には、内積結果を-1.0〜1.0から0.0〜1.0に変換した後に2乗しています。
参考:
Half Lambert – Valve Developer Community

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

マイケル
通常のLambertと並べてみた結果が下記になります。
HalfLambertの方が柔らかく、環境光無しでもそれなりに全体は見えていることが確認できますね
HalfLambertの方が柔らかく、環境光無しでもそれなりに全体は見えていることが確認できますね

マイケル
実際の計算部分は下記のようになります!
実装はほぼ同じなので、フラグメントシェーダ部分のみ載せておきます。
実装はほぼ同じなので、フラグメントシェーダ部分のみ載せておきます。

エレキベア
これだけの味付けで綺麗にできるのは便利クマね
Blinn-Phong鏡面反射

マイケル
そして次はBlinn-Phong鏡面反射!
こちらは反射ベクトルRの計算負荷を抑えるために考えられた手法です。
実装時にはreflect関数を使用しましたが、中身は下記のようになっていて、それなりに複雑になっています。
こちらは反射ベクトルRの計算負荷を抑えるために考えられた手法です。
実装時にはreflect関数を使用しましたが、中身は下記のようになっていて、それなりに複雑になっています。

エレキベア
こんな計算を行なっていたクマね

マイケル
Blinn-Phongでは、反射ベクトルRの代わりに、光源の向きと視線の向きの中間であるハーフベクトルHを使用します。
こちらと法線の向きの内積を反射量として使用する考え方です。
こちらと法線の向きの内積を反射量として使用する考え方です。

エレキベア
なるほどクマ
これでも同じように反射量が求められるクマね
これでも同じように反射量が求められるクマね

マイケル
見た目も若干差異はあるけどほとんど変わらないね!
実装は下記のようになります。
こちらもPhongの時とほぼ同じのため、フラグメントシェーダ部分のみ載せておきます。
実装は下記のようになります。
こちらもPhongの時とほぼ同じのため、フラグメントシェーダ部分のみ載せておきます。

エレキベア
ハーフベクトル導出部分が変わったくらいクマね

マイケル
なお、補足ですがSurfaceシェーダではデフォルトで
LambertとBlinn-Phongモデルが用意されているようですね
LambertとBlinn-Phongモデルが用意されているようですね

エレキベア
Blinn-Phongが採用されているのクマね
リムライトの実装

マイケル
それでは最後にライティング応用として、下記のようなリムライトの実装を行なってみます。
こちらもこれまでの考え方を元に実装することができます。
こちらもこれまでの考え方を元に実装することができます。

エレキベア
これはこれまでと違いそうクマがどうやるのクマ?

マイケル
これも視線の向きと法線の向きを描いて考えてみます。
下記のように、周りが明るくなるリムライトの場合は、光が強く出てほしいのはこれらの向きが直角な場合になります。
下記のように、周りが明るくなるリムライトの場合は、光が強く出てほしいのはこれらの向きが直角な場合になります。

エレキベア
なるほどクマ・・・
つまり、角度が90度に近づくほど強く、0度になるほど弱くするクマね
つまり、角度が90度に近づくほど強く、0度になるほど弱くするクマね

マイケル
その通り!!
つまるところ、計算式は下記のようになります!
つまるところ、計算式は下記のようになります!

マイケル
1から内積結果を引くだけ!
これで実装できそうな気がしてきましたね!
実際の実装内容は下記のようになります。
これで実装できそうな気がしてきましたね!
実際の実装内容は下記のようになります。

マイケル
先ほどの計算式を落とし込んでいるだけですね。
あとは鏡面反射の時と同様、絞りも入れることで調整できるようにしています!
あとは鏡面反射の時と同様、絞りも入れることで調整できるようにしています!

エレキベア
合わせ技クマ〜〜〜〜
おわりに

マイケル
というわけで今回はライティング関連のシェーダー実装でした!
どうだったかな??
どうだったかな??

エレキベア
だいぶライティングのやり方が分かった気がするクマ
内積も感じられるようになったクマ〜〜〜
内積も感じられるようになったクマ〜〜〜

マイケル
見た目的には地味だけど、陰影の付け方はシェーダー実装の基礎になるから
しっかり覚えておこう!!
しっかり覚えておこう!!

マイケル
次回以降は、法線マップを絡めたライティングやPBR実装にも挑戦してみようと思います!
それでは今日はこの辺で!
アデューーーー!!
それでは今日はこの辺で!
アデューーーー!!

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