夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

MRTK StanderdShaderを読み解く その13 TriplanarMapping 後編

本日はMRTKのShader調査枠です。

今回はTriplanarMappingの処理を追って調査しています。

前編ではTriplanarMappingというものが何かを調べ、中編で頂点シェーダーを学び、今回フラグメントシェーダーを学びます。

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

〇フラグメントシェーダー

       fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
            {
#if defined(_TRIPLANAR_MAPPING)
                // Calculate triplanar uvs and apply texture scale and offset values like TRANSFORM_TEX.
                fixed3 triplanarBlend = pow(abs(i.triplanarNormal), _TriplanarMappingBlendSharpness);
                triplanarBlend /= dot(triplanarBlend, fixed3(1.0, 1.0, 1.0));
                float2 uvX = i.triplanarPosition.zy * _MainTex_ST.xy + _MainTex_ST.zw;
                float2 uvY = i.triplanarPosition.xz * _MainTex_ST.xy + _MainTex_ST.zw;
                float2 uvZ = i.triplanarPosition.xy * _MainTex_ST.xy + _MainTex_ST.zw;

                // Ternary operator is 2 instructions faster than sign() when we don't care about zero returning a zero sign.
                float3 axisSign = i.triplanarNormal < 0 ? -1 : 1;
                uvX.x *= axisSign.x;
                uvY.x *= axisSign.y;
                uvZ.x *= -axisSign.z;
#endif

            // Texturing.
#if defined(_TRIPLANAR_MAPPING)
                fixed4 albedo = tex2D(_MainTex, uvX) * triplanarBlend.x + 
                                tex2D(_MainTex, uvY) * triplanarBlend.y + 
                                tex2D(_MainTex, uvZ) * triplanarBlend.z;
#else
                fixed4 albedo = tex2D(_MainTex, i.uv);
#endif

     ...

#if defined(_NORMAL_MAP)
#if defined(_TRIPLANAR_MAPPING)
                fixed3 tangentNormalX = UnpackScaleNormal(tex2D(_NormalMap, uvX), _NormalMapScale);
                fixed3 tangentNormalY = UnpackScaleNormal(tex2D(_NormalMap, uvY), _NormalMapScale);
                fixed3 tangentNormalZ = UnpackScaleNormal(tex2D(_NormalMap, uvZ), _NormalMapScale);
                tangentNormalX.x *= axisSign.x;
                tangentNormalY.x *= axisSign.y;
                tangentNormalZ.x *= -axisSign.z;

                // Swizzle world normals to match tangent space and apply Whiteout normal blend.
                tangentNormalX = fixed3(tangentNormalX.xy + i.worldNormal.zy, tangentNormalX.z * i.worldNormal.x);
                tangentNormalY = fixed3(tangentNormalY.xy + i.worldNormal.xz, tangentNormalY.z * i.worldNormal.y);
                tangentNormalZ = fixed3(tangentNormalZ.xy + i.worldNormal.xy, tangentNormalZ.z * i.worldNormal.z);

                // Swizzle tangent normals to match world normal and blend together.
                worldNormal = normalize(tangentNormalX.zyx * triplanarBlend.x +
                                        tangentNormalY.xzy * triplanarBlend.y +
                                        tangentNormalZ.xyz * triplanarBlend.z);
#else
                fixed3 tangentNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv), _NormalMapScale);
                worldNormal.x = dot(i.tangentX, tangentNormal);
                worldNormal.y = dot(i.tangentY, tangentNormal);
                worldNormal.z = dot(i.tangentZ, tangentNormal);
                worldNormal = normalize(worldNormal) * facing;
#endif
#else
                worldNormal = normalize(i.worldNormal) * facing;
#endif
#endif

   ...
                return output;
            }
〇uv、メッシュの処理
#if defined(_TRIPLANAR_MAPPING)
                // Calculate triplanar uvs and apply texture scale and offset values like TRANSFORM_TEX.
                fixed3 triplanarBlend = pow(abs(i.triplanarNormal), _TriplanarMappingBlendSharpness);
                triplanarBlend /= dot(triplanarBlend, fixed3(1.0, 1.0, 1.0));
                float2 uvX = i.triplanarPosition.zy * _MainTex_ST.xy + _MainTex_ST.zw;
                float2 uvY = i.triplanarPosition.xz * _MainTex_ST.xy + _MainTex_ST.zw;
                float2 uvZ = i.triplanarPosition.xy * _MainTex_ST.xy + _MainTex_ST.zw;

                // Ternary operator is 2 instructions faster than sign() when we don't care about zero returning a zero sign.
                float3 axisSign = i.triplanarNormal < 0 ? -1 : 1;
                uvX.x *= axisSign.x;
                uvY.x *= axisSign.y;
                uvZ.x *= -axisSign.z;
#endif

 最初の_TRIPLANAR_MAPPINGの分岐ではtripplanar uvスクロールを計算し、TRANSFORM_TEXのようなテクスチャのスケールとオフセット値を適用させます。

 TRANSFORM_TEXはマテリアルで扱うOffset、Tilingなどのことを指します。 TriplanarMappingでは3DモデルのUVを無視してマッピングしますが、ここでOffset、Tilingを扱えるようにしているようです。

triplanarBlendはv2fのtriplanarNormalの絶対値の_TriplanarMappingBlendSharpness乗の値が代入され舞う。

 _TriplanarMappingBlendSharpnessはPropertiesで定義されています。この値は1~16の間で指定されています。

 _TriplanarMappingBlendSharpness("Blend Sharpness", Range(1.0, 16.0)) = 4.0

 triplanarNormalは頂点の持つ情報のUnity座標での法線が、[LocalSpace]が有効化されているなら頂点の持つ法線情報そのものが入ります。

 つまりtriplanarBlendは頂点の法線情報に係数がかけられている値になります。

triplanarBlend /= dot(triplanarBlend, fixed3(1.0, 1.0, 1.0));

これは次の行でさらに処理されます。

triplanarBlendと単ベクトルを内積したものでtriplanarBlendを割ります。

                float2 uvX = i.triplanarPosition.zy * _MainTex_ST.xy + _MainTex_ST.zw;
                float2 uvY = i.triplanarPosition.xz * _MainTex_ST.xy + _MainTex_ST.zw;
                float2 uvZ = i.triplanarPosition.xy * _MainTex_ST.xy + _MainTex_ST.zw;

 uvXはv2fのtriplanarPosition.zyにMainTex_ST.xyが積算された値にMainTex_ST.zwが加算されています。

 v2fのtriplanarPositionはUnity座標での頂点の位置、[LocalScale]の場合頂点自身の持つ位置になります。triplanarPosition.zyは頂点のzy成分になります。

 _MainTex_STはテクスチャのfloat4型変数を表します。xyがテクスチャスケール、zwが平行移動(オフセット)を表します。これはUnityCG.cgincで提供されます。

 つまりuvXでは頂点位置のzy成分にテクスチャスケールを積算し、オフセットを加算しています。

 uvY,uvZに関しても同様です。

 ここがuvスクロールを計算し、TRANSFORM_TEXのようなテクスチャのスケールとオフセット値を適用させる計算です。

                // Ternary operator is 2 instructions faster than sign() when we don't care about zero returning a zero sign.
                float3 axisSign = i.triplanarNormal < 0 ? -1 : 1;
                uvX.x *= axisSign.x;
                uvY.x *= axisSign.y;
                uvZ.x *= -axisSign.z;

uvX、uvY、uvZのx成分にaxisSignが積算されています。

 このaxisSignはv2fのtriplanarNormalが負の値か正の値かで負の値の場合は-1,正の値の場合は1が代入されます。

 triplanarNormalは各頂点の法線なので、この値が負の場合はつまりメッシュが裏面であることを指します。

 面の表と裏の処理を行っています。

〇テクスチャ

 次の_TRIPLANAR_MAPPINGの分岐では主にテクスチャの処理が行われます。

#if defined(_TRIPLANAR_MAPPING)
                fixed4 albedo = tex2D(_MainTex, uvX) * triplanarBlend.x + 
                                tex2D(_MainTex, uvY) * triplanarBlend.y + 
                                tex2D(_MainTex, uvZ) * triplanarBlend.z;
#else
                fixed4 albedo = tex2D(_MainTex, i.uv);
#endif

 albedoに_MainTexをuvX、uvY、uvZ(法線)のそれぞれにtriplanarBlendが加算されたものが代入されます。

 この処理が3つの軸から画像を投影する処理になります。

f:id:Holomoto-Sumire:20200802114144j:plain

_TriplanarMappingBlendSharpnessが大きくなるとtriplaarBlendが大きくなり、各投影の範囲が変わります。

重なり合う部分は色の加算処理が行われているため、 _TriplanarMappingBlendSharpnessの値が小さくなると3つの軸から投影されたテクスチャが混ざり合うようになります。

以上が画像に関する処理です。

〇ノーマルマップ
#if defined(_TRIPLANAR_MAPPING)
                fixed3 tangentNormalX = UnpackScaleNormal(tex2D(_NormalMap, uvX), _NormalMapScale);
                fixed3 tangentNormalY = UnpackScaleNormal(tex2D(_NormalMap, uvY), _NormalMapScale);
                fixed3 tangentNormalZ = UnpackScaleNormal(tex2D(_NormalMap, uvZ), _NormalMapScale);
                tangentNormalX.x *= axisSign.x;
                tangentNormalY.x *= axisSign.y;
                tangentNormalZ.x *= -axisSign.z;

                // Swizzle world normals to match tangent space and apply Whiteout normal blend.
                tangentNormalX = fixed3(tangentNormalX.xy + i.worldNormal.zy, tangentNormalX.z * i.worldNormal.x);
                tangentNormalY = fixed3(tangentNormalY.xy + i.worldNormal.xz, tangentNormalY.z * i.worldNormal.y);
                tangentNormalZ = fixed3(tangentNormalZ.xy + i.worldNormal.xy, tangentNormalZ.z * i.worldNormal.z);

                // Swizzle tangent normals to match world normal and blend together.
                worldNormal = normalize(tangentNormalX.zyx * triplanarBlend.x +
                                        tangentNormalY.xzy * triplanarBlend.y +
                                        tangentNormalZ.xyz * triplanarBlend.z);
#else
                fixed3 tangentNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv), _NormalMapScale);
                worldNormal.x = dot(i.tangentX, tangentNormal);
                worldNormal.y = dot(i.tangentY, tangentNormal);
                worldNormal.z = dot(i.tangentZ, tangentNormal);
                worldNormal = normalize(worldNormal) * facing;
#endif

画像の次にノーマルマップに関する処理が行われます。

    fixed3 tangentNormalX = UnpackScaleNormal(tex2D(_NormalMap, uvX), _NormalMapScale);
                fixed3 tangentNormalY = UnpackScaleNormal(tex2D(_NormalMap, uvY), _NormalMapScale);
                fixed3 tangentNormalZ = UnpackScaleNormal(tex2D(_NormalMap, uvZ), _NormalMapScale);
                tangentNormalX.x *= axisSign.x;
                tangentNormalY.x *= axisSign.y;
                tangentNormalZ.x *= -axisSign.z;

tangentNormalにはUnityCG.cgincで提供されるUnpackScaleNormal()が使用されてノーマルマップを処理します。

 UnpackScaleNormal()関数以外の処理は画像と特に変わりません。

 以上がTriplanarMappingの処理です。

 3つの面から投影するため処理が複雑にも見えますが、一つ一つは頂点の座標や法線を使用してテクスチャリングしています。