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


Specular Result

環境

  • Unity 2021.2.11f1
  • Build-in Rendering Pipline

鏡面反射 (Specular)とは?

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

specular image

Phong 鏡面反射モデル

反射ベクトルは法線ベクトルとライトベクトルを使って求めます。

R=L2N(NL)R = L - 2N(N \cdot L)
ハイライトの強さを求めるには、視線ベクトルと反射ベクトルを内積します。

Specular(RV)nSpecular \approx (R \cdot V)^n

パラメータ 説明
nn 光沢度
RR 反射ベクトル
VV 視線ベクトル
LL ライトベクトル
NN 法線ベクトル

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


©️ 2026 went5. blog icon by

icons8