ディフューズ反射を実装してみる
ディフューズ反射とは、
- Fragment処理するサーフェイス点から光源への方向ベクトル s
- Fragment処理するサーフェイス点のNormalベクトル n
- 光源の強度(入射光の強度) (0 ~ 1なのかな) Ld
- Fragment処理するサーフェイス点のディフューズ反射率(どのくらい光を反射するか0 ~ 1) Kd
を使って
反射光の強度を求めることができる。
反射光の強度 = Ld * Kd * (s ・ n)
(s ・ n)の部分はsとnの内積。(sとnはそれぞれ正規化しておく)。内積は|s||n|cosθで計算されるので計算結果は-1 ~ 1の間をとる。
ディフューズ反射によって求められる反射光の強度は0 ~ 1の間をとり、0の場合は光が全く当たっていない=真っ暗となる。(s ・ n)の内積計算結果によっては結果がマイナス値になってしまうことがあるため、
Max関数を使ってMax((s・n),0)のようにして内積計算結果が0未満にならないようにする必要がある。
最終的な反射光の強度の計算式はこうなる。
反射光の強度 = Ld * Kd * Max((s ・ n),0)
ディフューズ反射のコード
Shader "Custom/TestShader" {
Properties {
_Reflectance("反射率",Range(0,1)) = 0.5
_MainColor("メインカラー",Color) = (1,1,1,1)
}
SubShader {
//"LightMode" = "ForwardBase"の記述がないと_WorldSpaceLightPos0の値が(0,0,0)?になる。
Tags { "Queue" = "Geometry" "RenderType" = "Opaque" "LightMode" = "ForwardBase"}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//UnityObjectToWorldNormal関数を使うためinclude
#include "UnityCG.cginc"
uniform float _Reflectance;
uniform float4 _MainColor;
//_LightColor0はuniformで定義してあげないとエラーになる
uniform float4 _LightColor0;
struct v2f {
float4 wPos : SV_POSITION;
float3 wNormal : NORMAL;
//fragmentシェーダーで色算出に使いたかっただけなんだけどsemantices?がないとエラーになるのでとりあえず:Colorをつけた
float4 surfaceLightColor : Color;
};
v2f vert(float4 pos : POSITION ,float3 normal : NORMAL) {
v2f output;
output.wPos = mul(UNITY_MATRIX_MVP,pos);
//vert関数の引数で手に入るnormalはlocalのNormalなのでワールド座標に変換している
output.wNormal = UnityObjectToWorldNormal(normal);
//ディフューズ反射を求める式 = 入射光 * 反射率 * max(ライトポジションとNormalの内積,0);
//ちなみに_WorldSpaceLightPos0はDirectional Light専用なのかPointライトでは動作しなかった
//実際には_WorldSpaceLightPos0はDirectional Lightのワールド座標ではない。Directional Lightの光源の向きが入っている
output.surfaceLightColor = _LightColor0 * _Reflectance * max(dot(_WorldSpaceLightPos0 ,output.wNormal),0);
return output;
}
float4 frag(v2f input) : SV_TARGET {
//vert関数で計算したサーフェイス上でのディフューズ反射率をメインカラーにかけて光の強さによってサーフェイスの色を算出している
return _MainColor * input.surfaceLightColor;
}
ENDCG
}
}
}
結果:Directional Lightの向きに応じて表面色が変わることがわかる
uniform float4 _LightColor0;によってUnityから自動的に?渡される_LightColor0はおそらくDirectional Light のColorプロパティが入る
Lightの色を変えたら表面色も変わった
ライトの影になる部分が真っ黒になっているので環境光?を取り入れてみる
環境光?(アンビエントライト)を取り込む
ここら辺の値をShaderからアクセスして表面色を算出すればいいのかな?
公式サイトを見たらそれっぽいパラメータを発見
unity_AmbientSky fixed4 Sky ambient lighting color in gradient ambient lighting case.
unity_AmbientSkyを使えばいいのかな?
float4 frag(v2f input) : SV_TARGET {
//vert関数で計算したサーフェイス上でのディフューズ反射率をメインカラーにかけて光の強さによってサーフェイスの色を算出している
return _MainColor * input.surfaceLightColor * unity_AmbientSky;
}
たぶんunity_AmbientSkyが(0,0,0,0)になっている?
おそらくUnity側からunity_AmbientSkyに値を入れてもらうためには何か指定する必要があるのだろう。
uniform fixed4 unity_AmbientSky;
uniformじゃないのか。_LightColor0みたいなノリでいけると思ったんだけど。
しばらく調べていたらunity_AmbientSkyは正常に動いていたことがわかりました。
Lighting Windowを調べてみたところ、Ambient SourceをColorにした時に設定できるAmbient Colorの値が反映されていたようです。
Ambient Colorの初期値がBlackなのでunity_AmbientSkyに(0,0,0,0)が入っていた模様。
Ambient ColorをWhiteにしてみた。もっと真っ暗にならない感じになって欲しかった。。。
そもそも
_MainColor * input.surfaceLightColor
にして反射率が低いところを強制的に黒色にしているのが悪いのかな?
ambient colorを加算して調整してみた。
float4 frag(v2f input) : SV_TARGET {
//vert関数で計算したサーフェイス上でのディフューズ反射率をメインカラーにかけて光の強さによってサーフェイスの色を算出している
return _MainColor * (input.surfaceLightColor + unity_AmbientSky);
}
うーん。
とりあえず次はPoint Lightに対応させてみる。