法線マップをオブジェクト空間へ変換しシェーディング
視差マッピングシェーダを作成する際に、反射光等の計算を行いました。このとき、法線マップは接空間のベクトルを示しているので、vertex shaderでライトベクトル等を接空間へ変換した後に、fragment Shaderで法線マップと変換したライトベクトル等で反射を計算しました。ここでふと、全てのベクトルをオブジェクト空間に変換して計算しても当然同じ結果が得られるはず、と考え新たにシェーダを作成しました。
接空間で計算
shader
vertex shader内でライトベクトルと視線ベクトルを接空間へ変換し、それらをもとにハーフベクトルを求めています。この計算結果をfragment shader内で使用し、陰影や反射光を計算しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
Shader "Unlit/BlinnPhong" { Properties { _MainTex ("Texture", 2D) = "white" {} _NormalTex ("NormalMap", 2D) = "white" {} _Shininess ("Shininess", Range(0 ,1)) = 0.7 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; float4 tangent : TANGENT; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 lightDir : TEXCOORD1; float3 viewDir : TEXCOORD2; float3 halfDir : TEXCOORD3; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _NormalTex; float4 _NormalTex_ST; float4 _LightColor0;; float _Shininess; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); TANGENT_SPACE_ROTATION; o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)); o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)); o.halfDir = normalize(o.lightDir + o.viewDir); return o; } fixed4 frag (v2f i) : SV_Target { i.lightDir = normalize(i.lightDir); i.viewDir = normalize(i.viewDir); float3 normal = UnpackNormal(tex2D(_NormalTex, i.uv)); float4 diff = saturate(dot(normal, i.lightDir)) * _LightColor0; float3 spec = pow(max(0, dot(normal, i.halfDir)), _Shininess * 128) * _LightColor0; fixed4 col = tex2D(_MainTex, i.uv); col.rgb = col.rgb * diff + spec; return col; } ENDCG } } } |
実行結果
ランバート反射とBlinng-Phongの反射が合わさった結果となっています。
オブジェクト空間で計算
shader
vertex shader内で法線マップをオブジェクト空間へ変換するために必要な接ベクトル及び従法線ベクトルを取得しています。また、ライトベクトルと視線ベクトルをオブジェクト空間へ変換しています。そして、fragment shader内でこれらのベクトルから法線マップより得られる法線ベクトルを接空間からオブジェクト空間へ変換し、計算を行っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
Shader "Unlit/BlinnPhong_objectspace" { Properties { _MainTex ("Texture", 2D) = "white" {} _NormalTex ("NormalMap", 2D) = "white" {} _Shininess ("Shininess", Range(0 ,1)) = 0.7 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; float4 tangent : TANGENT; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 lightDir : TEXCOORD1; float3 viewDir : TEXCOORD2; float3 halfDir : TEXCOORD3; float3 normal : TEXCOORD4; float3 tangent : TEXCOORD5; float3 binormal : TEXCOORD6; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _NormalTex; float4 _NormalTex_ST; float4 _LightColor0;; float _Shininess; float4x4 TangentMatrix(float3 tan, float3 bin, float3 nor) { float4x4 mat = float4x4( float4(tan, 0), float4(bin, 0), float4(nor, 0), float4(0, 0, 0, 1) ); return mat; } v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.lightDir = ObjSpaceLightDir(v.vertex); o.viewDir = ObjSpaceViewDir(v.vertex); o.halfDir = normalize(o.lightDir + o.viewDir); o.normal = normalize(v.normal); o.tangent = v.tangent; o.binormal = cross(o.normal, v.tangent.xyz); return o; } fixed4 frag (v2f i) : SV_Target { i.lightDir = normalize(i.lightDir); i.viewDir = normalize(i.viewDir); float3 normal = UnpackNormal(tex2D(_NormalTex, i.uv)); normal = mul(normal, TangentMatrix(i.tangent, i.binormal, i.normal)); float4 diff = saturate(dot(normal, i.lightDir)) * _LightColor0; float3 spec = pow(max(0, dot(normal, i.halfDir)), _Shininess * 128) * _LightColor0; fixed4 col = tex2D(_MainTex, i.uv); col.rgb = col.rgb * diff + spec; return col; } ENDCG } } } |
実行結果
原因と解決方法
右手座標系と左手座標系の違いによるものでした。この座標系の違いにより従法線ベクトルの向きが反対方向となるようです。この座標系の違いを処理するにはどうしたらよいかというと、接ベクトルの四番目の要素(tangent.w)に右手座標系か左手座標系か(1か-1)が入っています。これを、従法線ベクトルに乗算すれば正しいベクトルが得られるようです。つまり、上記シェーダの
1 |
o.binormal = cross(o.normal, v.tangent.xyz); |
を
1 |
o.binormal = cross(o.normal, v.tangent.xyz) * v.tangent.w; |
と変更すれば良いようです。
実行結果
参考サイト
土屋つかさのテクノロジーは今か無しか:UnityのシェーダーでBINORMAL入力セマンティクスが機能しない話と回避する方法の話
-
前の記事
Tessellationを用いた波紋の作成 2018.11.04
-
次の記事
グリッドシェーダ 2018.11.18
コメントを書く