【Unity】鏡面反射シェーダー

環境
- Unity 2021.2.11f1
- Build-in Rendering Pipline
鏡面反射 (Specular)とは?
入射角と反射角が等しい反射のことをいいます。
鏡や金属類は特に鏡面反射をしやすくなります。
この記事では、鏡のような周囲の景色の映り込みに関しては実装せず、ハイライトを表示させるための実装を行います。

Phong 鏡面反射モデル
反射ベクトルは法線ベクトルとライトベクトルを使って求めます。
ハイライトの強さを求めるには、視線ベクトルと反射ベクトルを内積します。
| パラメータ | 説明 |
|---|---|
| 光沢度 | |
| 反射ベクトル | |
| 視線ベクトル | |
| ライトベクトル | |
| 法線ベクトル |
ShaderLab で鏡面反射を実装する
Specular.shader
https://gist.github.com/went5/19260aef5934923167bdd47c5d99dc37
頂点と法線のワールド座標を求める
頂点シェーダーでは、座標空間をワールド座標に合わせるため、頂点と法線のワールド座標を求めます。
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldVertex = mul(unity_ObjectToWorld, v.vertex);
return o;
}
視線と反射のベクトルを使って鏡面反射の強さを求める
fixed4 frag(v2f i) : SV_Target
{
half3 normal = normalize(i.worldNormal);
// ライト方向
float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
// 視線方向
float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
// 反射方向
float3 reflectDir = reflect(-lightDir, normal);
// 鏡面反射の強さ
half speqularLength = saturate(dot(viewDir, reflectDir));
fixed3 lightColor = _LightColor0 * pow(speqularLength, _Shininess) * _SpecularColor;
fixed4 color = _Color * float4(lightColor, 1.0);
return color;
}
視線ベクトル
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
視線ベクトルは頂点からカメラに向かうベクトルです。
UnityWorldSpaceViewDir 関数の中に、 worldPos を渡しても同じ結果になります。
反射ベクトル
float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
float NdotL = dot(lightDir, normal);
float3 reflectDir = 2 * NdotL * normal - lightDir;
ライトの座標は _WorldSpaceLightPos0 から取得できます。
ライトベクトルと法線を求めたあとは、公式に則って反射の計算を行います。
HLSL の組み込み関数の reflect でも同じ結果になります。
鏡面反射の強さ
half speqular = pow(saturate(dot(viewDir, reflectDir)), _Shininess);
視線ベクトルと反射ベクトルの向きが同じになるほど、鏡面反射は強くなります。内積を使えばその強さを求めることができます。
内積の結果を 0~1 に収めるために、 saturate 関数を使います。
上記の結果を光沢度で累乗することで、ハイライトの大きさを変更することができます。
最大値が 1 のため、累乗しても 1 以上になることはありません。
_Shiness の値にいれる値によって以下の画像のような違いがでます。

Table of contents