C++幼女先輩

プログラミング成分多め

UNITYシェーダ研究 Blinn-Phong

コード
https://github.com/YukiMiyatake/UnityLesson/tree/Shader_4

はじめに

ディフューズを入れて 基本的なLambertモデル実装しました

次はこれにスペキュラを追加します(念のため DirectSpecularのみです)
Blinn-Phong的な事をします(定義間違ってたら教えて下さい)

スペキュラ

鏡面反射です
前回のディフューズは、ライトが面に当たった時のライティングで、View(カメラの位置)とは関係ありません
スペキュラでは、光の反射モデルなので 鏡に光源が写った時= 光の反射を直視した時のみ強烈に光ります
3DCGでは こういう事になります
光の入射角と法線のreflectionベクトル(面法線に対してライトが同じ角度で反射)と Viewベクトルの内積と正の相関関係

スペキュラの場合は、相関は Powでかけます。細かい物理的な理由は調べてないけど
スペキュラのパラメータは今回は、スペキュラ色と Powerにします

property

 Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Spec1Power("Specular Power", Range(0, 30)) = 1
        _Spec1Color("Specular Color", Color) = (0.5,0.5,0.5,1)
    }

今回はスペキュラカラーとPowerが増えた

宣言

         struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv     : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 vertexW: TEXCOORD0;
                float2 uv     : TEXCOORD1;
                float3 normal : TEXCOORD2;
            };

            uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
            uniform float _Spec1Power;
            uniform float4 _Spec1Color;

念のため 今回はフラグメントシェーダで計算します。一部頂点シェーダで計算して補完してもいいものがありますが

スペキュラのパラメータ以外に フラグメントシェーダに vertexWを送ってます
vertexには ビューポート変換まで行うので、World座標での位置情報が必要なためです

頂点シェーダ

         v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.vertexW = mul(unity_ObjectToWorld, v.vertex);

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

vertexWに対して World座標系への変換をかけています

フラグメントシェーダ

         float3 frag (v2f i) : SV_Target
            {
                float3 L = normalize(_WorldSpaceLightPos0.xyz);
                float3 V = normalize(_WorldSpaceCameraPos -i.vertexW.xyz);
                float3 N = i.normal;


                // texture albedo
                float4 tex = tex2D(_MainTex, i.uv);

                // Diffuse(HalfLambert)
                float3 NdotL = dot(N, L);
                float3 diffuse = (NdotL*0.5 + 0.5) * _LightColor0.rgb ;

                // Speculer
                float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz;  // reflection

    
                return diffuse*tex + specular;
            }

Vは ViewVectorです。  _WorldSpaceCameraPosは World座標系でのカメラのポジションで World座標系でのポジションをマイナスすると、ピクセルからカメラに向かうベクトルになります

dot(reflect(-L, N), V) これが、反射ベクトルと Viewベクトルの内積部分
reflectというのは組み込み関数で この場合は 法線を中心として 反射したライトベクトルが取れる
それを maxで 0未満(裏面)の計算を無効にし
スペキュラの強さで累乗しスペキュラ色を乗算

スペキュラは加算なので単純にディフューズに掛け合わせる

f:id:murasame-labo:20170913102938p:plain

カメラを動かすと スペキュラのライティングだけ変わります
ディフューズはカメラ位置とは無関係なのもわかると思います

ハーフベクトル

上記の計算は 正確な反射ベクトルを計算した
具体的には reflect(-L,N)の部分だが、反射ベクトルは多少計算が重い
R = L - (2V・N) N
L - 2 * dot( V*2, N ) * N

スペキュラは ハーフベクトルを使って近似が出来る事が広く知られているのでそれを実装する

ハーフベクトルとは、ライトベクトルとViewベクトルの間のベクトルであり、それと法線の内積で近似出来る

ハーフベクトルは normalize(L+V); で表される
reflectより圧倒的に計算量が低い

float3 H = normalize(L+V);

// Speculer
// float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz;  // reflection
float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz;  // Half vector

f:id:murasame-labo:20170913105709p:plain

計算間違えてるのか、ライトの影響パワーが大きく異なるが、スペキュラはかかった