UnityのshaderでPhong反射モデルを実装する。
Phong反射モデルは以下の3つを合成する。
- 拡散反射光
- 鏡面反射光
- 環境光
ここではディレクショナルライトのPhong反射モデルを実装する。
拡散反射光
ここではLambert拡散反射モデルを使用する。これは、光が当たっている部分は明るく、そうでない部分は暗くなる、直感的でわかりやすいモデル。
Lambert.shader
Shader "Boo/Lambert"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// _LightColor0を利用するのに必要
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// ローカル空間座標なので、ワールド空間座標に変換する。
o.normal = UnityObjectToWorldNormal(v.normal);
return o ;
}
fixed4 frag (v2f i) : SV_Target
{
// サーフェイスからライトへの向き
float3 toLightDir = _WorldSpaceLightPos0.xyz;
float t = dot(i.normal, toLightDir);
// 0以下の値は必要ないので0~1にクランプする
t = saturate(t);
fixed4 diffuseColor = _LightColor0 * t;
fixed4 col = tex2D(_MainTex, i.uv);
// テクスチャの色に拡散反射光を乗算して最終的な出力とする
col *= diffuseColor;
return col;
}
ENDCG
}
}
}
- Lambert拡散反射モデルでは法線が必要なので頂点シェーダーへの入力(appdata)で受け取るようにする。
-
UnityObjectToWorldNormal
を利用して法線をローカル座標からワールド座標に変換できる。 - サーフェイスから光源への向きは
_WorldSpaceLightPos0.xyz
で取得できる。(並行光源の場合) - 光源色は
#include "Lighting.cginc"
を追加した上で、_LightColor0
で取得できる。
鏡面反射光
ここではPhong鏡面反射モデルを用いる。これは金属のような反射も表現するモデルである。以下の二つのベクトルの向きが同じである程強い反射光が得られる。
- 光源から発せられた光がサーフェイスで反射したベクトル
- サーフェイスから視線(実際にはカメラ)方向へのベクトル
Phong.shader
Shader "Boo/Phong"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float4 vertex : SV_POSITION;
// ワールド座標系での頂点座標
float3 vertexW : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.normal = UnityObjectToWorldNormal(v.normal);
o.vertexW = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 光源からサーフェイスへのベクトル
float3 lightToSurface = -_WorldSpaceLightPos0.xyz;
// サーフェイスに入射した光の反射光を求める
float3 r = reflect(lightToSurface, i.normal);
r = normalize(r);
// サーフェイスから視線方向へのベクトル
float3 toEye = normalize(_WorldSpaceCameraPos - i.vertexW);
float t = dot(r, toEye);
t = saturate(t);
// 適当に絞る
t = pow(t, 10.0f);
float4 specular = _LightColor0 * t;
fixed4 col = tex2D(_MainTex, i.uv);
return col * specular;
}
ENDCG
}
}
}
-
unity_ObjectToWorld
はローカル座標系からワールド座標系への変換行列。 -
reflect
を使って反射ベクトルを求めることができる。 - カメラの位置は
_WorldSpaceCameraPos
で取得できる。
環境光
現実世界では光は何度も反射を繰り返すことで、光源の影になる面でも多少の光をうけとることになる。これを計算することは困難なので、すべてのサーフェイスを一律で明るくする。
拡散反射光 + 鏡面反射光 + 環境光
これまでの3つを足し算してPhong反射モデルが求まる。
Phongの反射モデル.shader
Shader "Boo/PhongLightModel"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
float3 vertexW : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.normal = UnityObjectToWorldNormal(v.normal);
o.vertexW = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// Lambert拡散反射
float3 toLightDir = _WorldSpaceLightPos0.xyz;
float t = dot(i.normal, toLightDir);
t = saturate(t);
fixed4 diffuseColor = _LightColor0 * t;
// Phong鏡面反射
float3 lightToSurface = -_WorldSpaceLightPos0.xyz;
float3 r = reflect(lightToSurface, i.normal);
r = normalize(r);
float3 toEye = normalize(_WorldSpaceCameraPos - i.vertexW);
t = saturate(dot(r, toEye));
t = pow(t, 10.0f);
float4 specular = _LightColor0 * t;
float4 light = diffuseColor + specular;
// 環境光(一律で底上げ)
light.xyz += 0.2f;
fixed4 col = tex2D(_MainTex, i.uv);
col *= light;
return col;
}
ENDCG
}
}
}