下の記事で解説したランバート反射モデルではバーテックスシェーダーで法線と光源方向ベクトルの内積を拡散反射の反射量としました。頂点単位なのでピクセルシェーダー に渡った時点で反射量が補間されるため、精度がイマイチです。
ny-program.hatenablog.com
そこで、フラグメントシェーダーで反射量を計算することでピクセル単位の計算になりますので、精度が高まります。
このようにピクセル単位で内積計算を行うランバート拡散反射を「パーピクセルフォンシェーディング」と言います。
逆に頂点単位で内積計算を行うランバート拡散反射を「グローシェーディング」と言います。
ソース
Shader "Hidden/PhongShading"
{
Properties
{
_MainTex ("Texture", 2D) = "white" { }
}
SubShader
{
Pass
{
tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex: POSITION;
float2 uv: TEXCOORD0;
float3 normal: NORMAL;
};
struct v2f
{
float2 uv: TEXCOORD0;
float4 vertex: SV_POSITION;
//ワールド座標系の法線ベクトル
float worldNormal: TEXCOORD1;
};
v2f vert(appdata v)
{
v2f o;
//頂点をクリップ空間座標に変換
o.vertex = UnityObjectToClipPos(v.vertex);
//uv座標
o.uv = v.uv;
//法線ベクトルをワールド座標に変換
o.worldNormal = UnityObjectToWorldNormal(v.normal);
return o;
}
sampler2D _MainTex;
fixed4 frag(v2f i): SV_Target
{
//法線ベクトル
float3 nNormal = normalize(i.worldNormal);
//ライトベクトル
float3 nLightDir = normalize(_WorldSpaceLightPos0.xyz);
//法線ベクトル-ライトベクトルの角度量
float NdotL = dot(nNormal, nLightDir);
//拡散係数の決定
float diffuse = max(0, NdotL);
fixed4 texCol = tex2D(_MainTex, i.uv);
//サンプリングしたカラー値に拡散反射量を乗算する
return diffuse * texCol;
}
ENDCG
}
}
}
解説
バーテックスシェーダー
//法線ベクトルをワールド座標に変換
o.worldNormal = UnityObjectToWorldNormal(v.normal);
法線ベクトルをフラグメントシェーダー渡したいのでワールド座標系に変換して戻り値用変数に代入してます。
フラグメントシェーダー
//法線ベクトル
float3 nNormal = normalize(i.worldNormal);
バーテックスシェーダーから受け取ったi.worldNormalhはプリミティブの3頂点
の法線ベクトルから線形補間されているので単位ベクトルではありません。
よって正規化してます。
//ライトベクトル
float3 nLightDir = normalize(_WorldSpaceLightPos0.xyz);
_WorldSpaceLightPosθはUnityの組み込み変数でワールド座標系のライト方向ベクトルが格納されています。
こちらも単位ベクトルでないので正規化します。
//法線ベクトル-ライトベクトルの角度量
float NdotL = dot(nNormal, nLightDir);
//拡散係数の決定
float diffuse = max(0, NdotL);
グローシェーディングではバーテックスシェーダーで行なっていた内積計算をフラグメントシェーダーで行なっています。
法線ベクトルをフラグメントシェーダーにまで運んだのはこの為です。