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 } } }
やけに左下が黒いのは拡散反射光の影響かなと思い、 diffを乗算する部分をコメントアウト したところ、次のようになりました.
うーん、あまり変わってない. 一応 specを加算する部分をコメントアウト したところ、次のようになりました.
なんでや、左下も白くなってるやん!? ということはつまり、 specの値が負になっとるやん!?.
いやいやいや、だってspecの定義は次のようにしてるんですよ.....
float3 spec = pow(max(0, dot(tangent, H)), _Kshi );
非負数の実数乗ですよね?なんで負の数になってるんですか...
どうやら0の0乗が原因らしい
再びspecの加算をコメントイン させて今度はパラメーターをいじってみたところ、どうも _Ksiを0以外の値 にすると左下が黒くなる現象が消えることが分かりました. つまり、 0の0乗 が悪さをしていたようです.
まとめ
シェーダーに限らないですが各変数がとりうる値の範囲というのは常に気にした方がいいんでしょうね. それとやっぱり0が出てくるところは怖いですね.