UNITYシェーダ研究 Blinn-Phong
コード
https://github.com/YukiMiyatake/UnityLesson/tree/Shader_4
はじめに
ディフューズを入れて 基本的なLambertモデル実装しました
次はこれにスペキュラを追加します(念のため DirectSpecularのみです)
Blinn-Phong的な事をします(定義間違ってたら教えて下さい)
スペキュラ
鏡面反射です
前回のディフューズは、ライトが面に当たった時のライティングで、View(カメラの位置)とは関係ありません
スペキュラでは、光の反射モデルなので 鏡に光源が写った時= 光の反射を直視した時のみ強烈に光ります
3DCGでは こういう事になります
光の入射角と法線のreflectionベクトル(面法線に対してライトが同じ角度で反射)と Viewベクトルの内積と正の相関関係
スペキュラの場合は、相関は Powでかけます。細かい物理的な理由は調べてないけど
スペキュラのパラメータは今回は、スペキュラ色と Powerにします
property
Properties { _MainTex ("Texture", 2D) = "white" {} _Spec1Power("Specular Power", Range(0, 30)) = 1 _Spec1Color("Specular Color", Color) = (0.5,0.5,0.5,1) }
今回はスペキュラカラーとPowerが増えた
宣言
struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float4 vertexW: TEXCOORD0; float2 uv : TEXCOORD1; float3 normal : TEXCOORD2; }; uniform sampler2D _MainTex; uniform float4 _MainTex_ST; uniform float _Spec1Power; uniform float4 _Spec1Color;
念のため 今回はフラグメントシェーダで計算します。一部頂点シェーダで計算して補完してもいいものがありますが
スペキュラのパラメータ以外に フラグメントシェーダに vertexWを送ってます
vertexには ビューポート変換まで行うので、World座標での位置情報が必要なためです
頂点シェーダ
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.vertexW = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
vertexWに対して World座標系への変換をかけています
フラグメントシェーダ
float3 frag (v2f i) : SV_Target { float3 L = normalize(_WorldSpaceLightPos0.xyz); float3 V = normalize(_WorldSpaceCameraPos -i.vertexW.xyz); float3 N = i.normal; // texture albedo float4 tex = tex2D(_MainTex, i.uv); // Diffuse(HalfLambert) float3 NdotL = dot(N, L); float3 diffuse = (NdotL*0.5 + 0.5) * _LightColor0.rgb ; // Speculer float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz; // reflection return diffuse*tex + specular; }
Vは ViewVectorです。 _WorldSpaceCameraPosは World座標系でのカメラのポジションで World座標系でのポジションをマイナスすると、ピクセルからカメラに向かうベクトルになります
dot(reflect(-L, N), V) これが、反射ベクトルと Viewベクトルの内積部分
reflectというのは組み込み関数で この場合は 法線を中心として 反射したライトベクトルが取れる
それを maxで 0未満(裏面)の計算を無効にし
スペキュラの強さで累乗しスペキュラ色を乗算
スペキュラは加算なので単純にディフューズに掛け合わせる
カメラを動かすと スペキュラのライティングだけ変わります
ディフューズはカメラ位置とは無関係なのもわかると思います
ハーフベクトル
上記の計算は 正確な反射ベクトルを計算した
具体的には reflect(-L,N)の部分だが、反射ベクトルは多少計算が重い
R = L - (2V・N) N
L - 2 * dot( V*2, N ) * N
スペキュラは ハーフベクトルを使って近似が出来る事が広く知られているのでそれを実装する
ハーフベクトルとは、ライトベクトルとViewベクトルの間のベクトルであり、それと法線の内積で近似出来る
ハーフベクトルは normalize(L+V); で表される
reflectより圧倒的に計算量が低い
float3 H = normalize(L+V); // Speculer // float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz; // reflection float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) * _Spec1Color.xyz; // Half vector
計算間違えてるのか、ライトの影響パワーが大きく異なるが、スペキュラはかかった