ShaderTips

シェーダーTips

主にUnityシェーダーについての記事を書いています。

ブリン・フォン反射モデル

フォンの反射モデルでは反射光の計算を内積や乗算をフラグメントシェーダーで行なっていたので、負荷が高いという懸念点がありました。

ny-program.hatenablog.com

 

そこで今回はスペキュラ反射を簡易的な計算処理で行うブリン・フォン反射モデルを紹介します。

 

ブリン・フォン反射モデルとは

光源方向ベクトルと視点方向ベクトルの中間に位置するハーフベクトルと法線ベクトルの角度で反射光を求めます。

ハーフベクトルは二つのベクトルを加算することで求めることができます。

 

 

>||
Shader "Hidden/BlinnPhong"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" { }
        //アンビエント光
        _Ambient ("Ambient", Range(0, 1)) = 1
        //スペキュラー
        _SpecColor ("Specular Color", Color) = (1, 1, 1, 1)
    }
    
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "ForwardBase" }
            
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            
            sampler2D _MainTex;
            //ライトカラー
            float4 _LightColor0;
            //アンビエント光
            float _Ambient;
            //スペキュラ色
            float4 _SpecColor;
            
            struct appdata
            {
                float4 vertex: POSITION;
                float2 uv: TEXCOORD0;
                float4 normal: NORMAL;
            };
            
            struct v2f
            {
                float2 uv: TEXCOORD0;
                float4 vertex: SV_POSITION;
                //ワールド空間の法線ベクトル
                float3 worldNoraml: TEXCOORD1;
                //ワールド空間の頂点座標
                float3 worldPos: TEXCOORD2;
            };
            
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                
                //法線ベクトルをワールド空間に変換
                float3 worldNoraml = UnityObjectToWorldNormal(v.normal);
                o.worldNoraml = worldNoraml;
                
                // 頂点をワールド空間座標に変換
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldPos = worldPos;
                
                return o;
            }
            
            
            fixed4 frag(v2f i): SV_Target
            {
                //法線ベクトル
                float3 normal = normalize(i.worldNoraml);
                //光源方向ベクトル
                float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                //法線 - ライトの角度量
                float NdotL = dot(normal, lightDir);
                
                //カメラ方向ベクトル
                float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                
                // テクスチャマップからカラー値をサンプリング
                float4 tex = tex2D(_MainTex, i.uv) ;
                
                // 拡散色の決定
                float diffusePower = max(_Ambient, NdotL);
                float4 diffuse = diffusePower * tex * _LightColor0;
                
                // 光源方向ベクトルと視点方向ベクトルのハーフベクトル
                float3 halfDir = normalize(lightDir + viewDir);
                
                //Blinn によるスペキュラ近似式
                float NdotH = dot(normal, halfDir);
                float3 specularPower = pow(max(0, NdotH), 10.0);
                
                //反射色の決定
                float4 specular = float4(specularPower, 1.0) * _SpecColor * _LightColor0;
                // 拡散色と反射色を合算
                fixed4 color = diffuse + specular;
                return color;
            }
            ENDCG    
        }
    }
}
||<

 

// 光源方向ベクトルと視点方向ベクトルのハーフベクトル float3 halfDir = normalize(lightDir + viewDir);

 光源方向と視点方向ベクトルを加算することでハーフベクトルを求めています。

知りたいのは方向のみなので正規化してます。

 

//Blinn によるスペキュラ近似式 float NdotH = dot(normal, halfDir); float3 specularPower = pow(max(0, NdotH), 10.0);

法線とハーフベクトルの内積に光沢度を累乗してスペキュラー反射を求めます。

最後に反射色と拡散色を足してピクセルの色を決定します。

 

 

内積の計算1回、累乗の計算1回なので、フォンの反射より処理負荷が少ないのは見てとれますね。

今回はこれで終了になります。