前回はライティングの基本となるランバート拡散照明モデルを紹介しました。今回は、ランバート拡散照明モデルを応用した、フォン(Phong)鏡面反射モデルをUnityを使いながら紹介します。フォン鏡面反射は反射光を再現した照明モデルで、金属のような質感の表現に向いています。
今回のコンテンツは次のとおりです。
- フォン鏡面反射モデルとは
- 反射光の計算方法
- Unityでフォン反射を作成する下準備をする
- シェーダとマテリアルを作る
- フォン鏡面反射モデルのシェーダを書く
- ブリンフォン(Blinn-Phong)鏡面反射モデル
- 参考文献
フォン鏡面反射モデルとは
金属など、表面がつるつるした物体に強い光が当たると、一箇所が「キラーン」と強く光ります。これをハイライトと呼び、このハイライトを再現したライティングモデルをフォン鏡面反射モデルと呼びます。
このフォン鏡面反射モデルはランバート拡散照明モデルに、ハイライトの色を単純に足し合わせたものです。
このハイライトの反射光をどのようにして再現するかが、この記事のポイントです。
反射光の計算方法
物体に光が当たると物体の表面で光が反射します。光が反射する方向は、物体の法線を挟んで反対方向になります。この反射ベクトルをR(reflection)とします。
視線がこの反射ベクトルの方向に近ければ近いほど、反射光も強くなります。言い換えれば、反射ベクトルRと視線ベクトルviewDirのなす角度θが小さければ小さいほど、反射光は強くなります。
反射ベクトルと視線ベクトルのなす角度は2つのベクトルの内積をとることで、次式のように求められます。両者のなす角度が0のときには、反射光の強度はcos0=1になり、反射ベクトルと視線ベクトルが離れるに従って反射光の強度も小さくなります。
R * viewDir = |R||viewDir| cosθ = cosθ
このように、反射光の強さをもとめるためには、光の反射ベクトルと視線ベクトルの内積をとれば良いことがわかりました。
では、実際にUnityを使ってフォン鏡面反射モデルを実現してみましょう。オブジェクトの配置〜シェーダファイルの作成までは前回の記事とほぼ同様です。
Unityでフォン反射を作成する下準備をする
Unityのヒエラルキービューから「Create」→「3D Object」→「Sphere」を選択します。インスペクタから、作成した球のScaleを(5, 5, 5)にします。
ヒエラルキービューから「Main Camera」を選択し、インスペクタから「Clear Flags」 を「Solid Color」にし、「Background」を黒色にします。
まだ、球に空の色が反映されているので、それも切りましょう。メニューバーから「Window」 →「Lighting」を選択し、「Skybox」 を「None」に変更、「Ambient Source」を「Color」、「Ambient Color」を黒色、「Reflection Intensity」も「0」に設定します。
これで、純粋にライトからだけの影響を受けるステージができました。
シェーダとマテリアルを作る
続いてフォン鏡面反射用のシェーダを作ります。プロジェクトビューで「右クリック」→「Create」→「Shader」→「Standard Surface Shader」を選択し、作成したファイル名をSimplePhongに変更します。続いてこのシェーダファイルをアタッチするマテリアルも作成しましょう。SimplePhongを選択した状態で「右クリック」→「Create」→「Material」を選択すると、SimplePhongというマテリアルが作成されます。
このマテリアルを球にアタッチします。プロジェクトビューからSimpleLambertのマテリアルを選択し、シーンビューのShpereにドラッグしてください。見た目は変わりませんが、Sphereのマテリアルが「Custom/SimplePhong」になっていることを確認して下さい。
フォン鏡面反射モデルのシェーダを書く
では、SimpleLambertのシェーダファイルを開いて次のプログラムを入力してください。
Shader "Custom/Phong" { SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf SimplePhong #pragma target 3.0 struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = fixed4(1,1,1,1); } half4 LightingSimplePhong(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) { half NdotL = max(0, dot (s.Normal, lightDir)); float3 R = normalize( - lightDir + 2.0 * s.Normal * NdotL ); float3 spec = pow(max(0, dot(R, viewDir)), 10.0); half4 c; c.rgb = s.Albedo * _LightColor0.rgb * NdotL + spec + fixed4(0.1f, 0.1f, 0.1f, 1); c.a = s.Alpha; return c; } ENDCG } FallBack "Diffuse" }
Unityでは#pragmaの3つ目(SimplePhong)の単語の先頭に”Lighting”をつけたものがライティング工程のメソッド名になるのでした。フォン鏡面反射モデルはランバート拡散照明モデルを拡張しているので、詳しい内容は、前回の記事を参考にして下さい。
LightingSimplePhongメソッドの中で、フォン鏡面反射モデルのライティングの処理を書いています。反射ベクトルRは、次の図のように、ライトの進行方向のベクトルに2*nを足せば求められます。
nを求めるためには、法線ベクトルNにnの長さをかければ良さそうです。nの長さはL*cosθで求まり、N・L=|N||L|cosθ=cosθであることを利用して、最終的には次のように書けます。
n= N * (N・L)
これを上の式に代入すると、反射ベクトルは
R = L + 2 * N * (N ・L)
と書くことが出来ます。
反射ベクトルRと視線ベクトルviewDirがどれだけ近いかを調べるためにRとviewDirの内積をとり、この値を10乗しています。R・ViewDirは0〜1の間の値なので、指数が大きくなればなるほど、小さい値になります。
言い換えると、指数を大きくすればするほどハイライトの減衰が速くなり、ピンポイントで光るようになります。次の例は指数を2〜30まで変化させた場合の例です。
最後に、ランバート拡散光と環境光に、いま計算したスペキュラの値を足せば、フォン鏡面反射モデルの出力になります。
ブリンフォン(Blinn-Phong)鏡面反射モデル
フォン鏡面反射モデルでは、反射ベクトルを計算しなければいけませんでした。反射ベクトルの計算は少しコストが高いので、反射ベクトルを使わずに反射光の計算ができるブリンフォン鏡面反射モデルが考えだされました。
フォンの鏡面反射ベクトルは反射ベクトルと視線ベクトルがどれだけ近いかで、反射光の強度を計算しましたが、ブリンフォン鏡面反射モデルでは、ハーフベクトルと法線ベクトルがどれだけ近いかで、反射光の強度を計算します。
ハーフベクトルは、視線ベクトルとライトの方向ベクトルの中間にあるベクトルで、次のように求めることが出来ます。
H=(ViewDir + L)/|ViewDir+L|
フォン鏡面反射モデルとブリンフォン鏡面反射モデルの見栄えを比べたものが次の図です。ブリンフォンのほうが少し荒くなっていることがわかると思います。
参考文献
シェーダと言ったらこの本!Unityではなく、DirectXとHLSLを使って書かれていますが、理論部分は一般論なのでUnityでも応用できます。
OpenGLとGLSLを使ったシェーダではこの本がオススメです。