ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

Unityで鏡面反射成分を入れるとなぜか黒くなる

UnityでBlinn-Phongの陰影付モデルを自分で実装したところ予期しないところが黒くなるという現象に出くわしました. その時に試したことのメモ.

実際のコードと実行結果

実際のコードは次になります. 拡散反射光と鏡面反射光を考慮しています.

// リスト1

Shader "Custom/Blinn-Phong"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _NormalTex("Normal", 2D) = "white" {}
        _Kshi("Kshi", Range(0,100)) = 50
        _Color("Color", Color) = (1,1,1,1)
        _F("Frenel", float) = 0.02
    }

    SubShader
    {
        Tags { 
            "Queue"     = "Transparent"
            "RenderType"= "Transparent" 
        }

        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha
        
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag          

            #include "UnityCG.cginc"


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

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 lightDir : TEXCOORD1;
                float3 viewDir : TEXCOORD2;
            };

            sampler2D _MainTex;
            sampler2D _NormalTex;
            float _Kshi;
            float4 _Color;
            float _F;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                TANGENT_SPACE_ROTATION;
                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));

                o.uv = v.uv;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                // 正規化
                i.lightDir = normalize(i.lightDir);
                i.viewDir = normalize(i.viewDir);
                float3 H = normalize(i.lightDir + i.viewDir);
                
                // ノーマルマップからタンジェントスペースのVector3を取得
                float3 tangent = UnpackNormal(
                    tex2D(_NormalTex, i.uv)
                );

                tangent = normalize(tangent);

                float4 diff = max(
                    0, dot(tangent, i.lightDir)
                );

                float3 spec = pow(
                    max(0, dot(tangent, H)),
                    _Kshi
                );

                // Albedoサンプリング
                float4 albedo = tex2D(_MainTex, i.uv);
                float4 col;
                col.rgb = albedo * _Color;

                // Diffuse
                col.rgb = col.rgb * diff;

                // Specular
                col.rgb = col.rgb  + spec;

                // フレネル効果
                float fresnel = _F + (1 - _F) * pow(1 - dot(i.viewDir, tangent), 5);
                col.a = fresnel;
     
                return col;
            }
            ENDCG
        }
    }
}

マテリアルの設定

リスト1の実行結果

やけに左下が黒いのは拡散反射光の影響かなと思い、 diffを乗算する部分をコメントアウト したところ、次のようになりました.

diffの影響をコメントアウトした結果

うーん、あまり変わってない. 一応 specを加算する部分をコメントアウト したところ、次のようになりました.

specの影響をコメントアウトした結果

なんでや、左下も白くなってるやん!? ということはつまり、 specの値が負になっとるやん!?.

いやいやいや、だってspecの定義は次のようにしてるんですよ.....

 float3 spec = pow(max(0, dot(tangent, H)), _Kshi );

非負数の実数乗ですよね?なんで負の数になってるんですか...

どうやら0の0乗が原因らしい

再びspecの加算をコメントイン させて今度はパラメーターをいじってみたところ、どうも _Ksiを0以外の値 にすると左下が黒くなる現象が消えることが分かりました. つまり、 0の0乗 が悪さをしていたようです.

パラメーターをいじった結果

まとめ

シェーダーに限らないですが各変数がとりうる値の範囲というのは常に気にした方がいいんでしょうね. それとやっぱり0が出てくるところは怖いですね.