夜風のMixedReality

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

MRTK StanderdShaderを読み解く その8 ノーマルマップ後半

本日はMRTKStandardShaderの勉強枠です。

ノーマルマップを勉強していますが、内容が濃く昨日だけで終わらなかったので引き続き見ていきます。

redhologerbera.hatenablog.com

〇これまでのMRTKStandardShaderの記事

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

〇頂点シェーダーまでのおさらい

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

                fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {

            ...
    #if defined(_NORMAL)
                    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition.xyz));
                    fixed3 worldNormal;

    #if defined(_NORMAL_MAP)
 
                    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

 ...
  
                    // Final lighting mix.
                    fixed4 output = albedo;  
                    fixed3 ambient = glstate_lightmodel_ambient + fixed3(0.25, 0.25, 0.25);
                    fixed minProperty = min(_Smoothness, _Metallic);
    #if defined(_DIRECTIONAL_LIGHT)
                    fixed oneMinusMetallic = (1.0 - _Metallic);
                    output.rgb = lerp(output.rgb, ibl, minProperty);
    #if defined(_LIGHTWEIGHT_RENDER_PIPELINE)
                    fixed3 directionalLightColor = _MainLightColor.rgb;
    #else
                    fixed3 directionalLightColor = _LightColor0.rgb;
    #endif
                    output.rgb *= lerp((ambient + directionalLightColor * diffuse + directionalLightColor * specular) * max(oneMinusMetallic, _MinMetallicLightContribution), albedo, minProperty);
                    output.rgb += (directionalLightColor * albedo * specular) + (directionalLightColor * specular * _Smoothness);
                    output.rgb += ibl * oneMinusMetallic * _IblContribution;
    #endif

    #if defined(_FRESNEL)
                    output.rgb += fresnelColor * (1.0 - minProperty);
    #endif
                    return output;
                }
〇解説(解読)
    #if defined(_NORMAL)
                    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition.xyz));
                    fixed3 worldNormal;

    #if defined(_NORMAL_MAP)
 
                    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

worldViewDirはピクセルごとのビューの方向(カメラに対しての向き)を取得しています。

tangentNormalはNormalMapのuvから法線を取得します。NormalMapScaleはその強さになります。 UnpackScaleNormal()はビルトインで定義されている関数で法線を取得する際に使用します。 法線の強さを指定しない場合はUnpackNormal()を使用します。

worldNormalのxyz成分はv2fのtangentとtangentNormal(直前に定義したノーマルマップの法線)との内積が代入されます。tangentはworldTangent成分(接線)です。

worldNormalはworldNormalを正規化したものと面の向きを積算しています。

このfacingは面の表は正の値、裏面は負の値になります。

つまりworldNormalは面の表裏で正負が決まります。

  worldNormal = normalize(i.worldNormal) * facing;

もしノーマルマップを使用しない場合はworldNormalにはv2fのworldNormalの正規化したものに面の向きを積算しています。

これらの情報がLightingに加算されます。

Lightingに加算される理由はノーマルマップは光の当たり方を法線に対応して変化させることで凹凸を表現しているためです。

Lightingに加算された情報がカラー情報にかかわり最終的なレンダリング結果として出力されています。

以上がノーマルマップになります。

〇MRTK StandardShader(Normal)

Shader "Custom/MRTKNormalMap"
{
    Properties
    {
        // Main maps.
        _Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex("Albedo", 2D) = "white" {}
        _Metallic("Metallic", Range(0.0, 1.0)) = 0.0
        _Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5
     
        [Toggle(_NORMAL_MAP)] _EnableNormalMap("Enable Normal Map", Float) = 0.0
        [NoScaleOffset] _NormalMap("Normal Map", 2D) = "bump" {}
        _NormalMapScale("Scale", Float) = 1.0
      

            // Rendering options.
            [Toggle(_DIRECTIONAL_LIGHT)] _DirectionalLight("Directional Light", Float) = 1.0
           
    }

        SubShader
            {
                Pass
                {
                    Name "Main"
                    Tags{ "RenderType" = "Opaque" "LightMode" = "ForwardBase" }
                    LOD 100
                  
                    CGPROGRAM

                    #pragma vertex vert
                    #pragma fragment frag
                  
                    #pragma shader_feature _NORMAL_MAP
          
                    #pragma shader_feature _DIRECTIONAL_LIGHT
              
                    #include "UnityCG.cginc"
                    #include "UnityStandardConfig.cginc"
                    #include "UnityStandardUtils.cginc"
                    #include "MixedRealityShaderUtils.cginc"

    #if defined(_DIRECTIONAL_LIGHT)
                #define _NORMAL
    #else
                #undef _NORMAL
    #endif

    #if defined(_NORMAL)
                #define _WORLD_POSITION
    #else
                #undef _WORLD_POSITION
    #endif
 
    #if defined(_DIRECTIONAL_LIGHT)
                #define _FRESNEL
    #else
                #undef _FRESNEL
    #endif
                #define _UV

                struct appdata_t
                {
                    float4 vertex : POSITION;
                    // The default UV channel used for texturing.
                    float2 uv : TEXCOORD0;
                    // Used for smooth normal data (or UGUI scaling data).
                    float4 uv2 : TEXCOORD2;
                    // Used for UGUI scaling data.
                    float2 uv3 : TEXCOORD3;

                    fixed3 normal : NORMAL;
    #if defined(_NORMAL_MAP)
                    fixed4 tangent : TANGENT;
    #endif
                    UNITY_VERTEX_INPUT_INSTANCE_ID
                };

                struct v2f
                {
                    float4 position : SV_POSITION;

    #if defined(_UV)
                    float2 uv : TEXCOORD0;
    #endif
 
    #if defined(_WORLD_POSITION)
                    float3 worldPosition : TEXCOORD2;
    #endif
    #if defined(_SCALE)
                    float3 scale : TEXCOORD3;
    #endif
    #if defined(_NORMAL)
 
    #if defined(_NORMAL_MAP)
                    fixed3 tangentX : COLOR3;
                    fixed3 tangentY : COLOR4;
                    fixed3 tangentZ : COLOR5;
    #else
                    fixed3 worldNormal : COLOR3;
    #endif
    #endif
                    UNITY_VERTEX_OUTPUT_STEREO
                };

 
                fixed4 _Color;
                sampler2D _MainTex;
                fixed4 _MainTex_ST;


                fixed _Metallic;
                fixed _Smoothness;



    #if defined(_NORMAL_MAP)
                sampler2D _NormalMap;
                float _NormalMapScale;
    #endif



#if defined(_DIRECTIONAL_LIGHT)
#if defined(_LIGHTWEIGHT_RENDER_PIPELINE)
                CBUFFER_START(_LightBuffer)
                    float4 _MainLightPosition;
                half4 _MainLightColor;
                CBUFFER_END
#else
                fixed4 _LightColor0;
#endif
#endif


    #if defined(_DIRECTIONAL_LIGHT)
                static const fixed _MinMetallicLightContribution = 0.7;
                static const fixed _IblContribution = 0.1;
    #endif

    #if defined(_FRESNEL)
                static const float _FresnelPower = 8.0;
    #endif


                v2f vert(appdata_t v)
                {
                    v2f o;
                    UNITY_SETUP_INSTANCE_ID(v);
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
 
                    float4 vertexPosition = v.vertex;

    #if defined(_WORLD_POSITION) 
                    float3 worldVertexPosition = mul(unity_ObjectToWorld, vertexPosition).xyz;
    #endif

    #if defined(_SCALE)
                    o.scale.x = length(mul(unity_ObjectToWorld, float4(1.0, 0.0, 0.0, 0.0)));
                    o.scale.y = length(mul(unity_ObjectToWorld, float4(0.0, 1.0, 0.0, 0.0)));
  
                    o.scale.z = length(mul(unity_ObjectToWorld, float4(0.0, 0.0, 1.0, 0.0)));

                    if (v.uv3.y < 0.0)
                    {
                        o.scale.x *= v.uv2.x;
                        o.scale.y *= v.uv2.y;
                        o.scale.z *= v.uv3.x;
                    }
    #endif

                    fixed3 localNormal = v.normal;

    #if defined(_NORMAL) 
                    fixed3 worldNormal = UnityObjectToWorldNormal(localNormal);
    #endif

                    o.position = UnityObjectToClipPos(vertexPosition);

    #if defined(_WORLD_POSITION)
                    o.worldPosition.xyz = worldVertexPosition;
    #endif
  
    #if defined(_UV)
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    #endif

 
    #if defined(_NORMAL)
   
    #if defined(_NORMAL_MAP)
                    fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
                    fixed tangentSign = v.tangent.w * unity_WorldTransformParams.w;
                    fixed3 worldBitangent = cross(worldNormal, worldTangent) * tangentSign;
                    o.tangentX = fixed3(worldTangent.x, worldBitangent.x, worldNormal.x);
                    o.tangentY = fixed3(worldTangent.y, worldBitangent.y, worldNormal.y);
                    o.tangentZ = fixed3(worldTangent.z, worldBitangent.z, worldNormal.z);
    #else
                    o.worldNormal = worldNormal;
    #endif
    #endif
                    return o;
                }
                fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {

                // Texturing.
                  fixed4 albedo = tex2D(_MainTex, i.uv);

                    // Normal calculation.
    #if defined(_NORMAL)
                    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition.xyz));
                    fixed3 worldNormal;

    #if defined(_NORMAL_MAP)
 
                    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

                    fixed pointToLight = 1.0;
                    fixed3 fluentLightColor = fixed3(0.0, 0.0, 0.0);

                    // Blinn phong lighting.
    #if defined(_DIRECTIONAL_LIGHT)
    #if defined(_LIGHTWEIGHT_RENDER_PIPELINE)
                    float4 directionalLightDirection = _MainLightPosition;
    #else
                    float4 directionalLightDirection = _WorldSpaceLightPos0;
    #endif
                    fixed diffuse = max(0.0, dot(worldNormal, directionalLightDirection)); 
                    fixed specular = 0.0;
    #endif

                    fixed3 ibl = unity_IndirectSpecColor.rgb;
    #if defined(_FRESNEL)
                    fixed fresnel = 1.0 - saturate(abs(dot(worldViewDir, worldNormal)));
                    fixed3 fresnelColor = unity_IndirectSpecColor.rgb * (pow(fresnel, _FresnelPower) * max(_Smoothness, 0.5));
    #endif
                    // Final lighting mix.
                    fixed4 output = albedo;  
                    fixed3 ambient = glstate_lightmodel_ambient + fixed3(0.25, 0.25, 0.25);
                    fixed minProperty = min(_Smoothness, _Metallic);
    #if defined(_DIRECTIONAL_LIGHT)
                    fixed oneMinusMetallic = (1.0 - _Metallic);
                    output.rgb = lerp(output.rgb, ibl, minProperty);
    #if defined(_LIGHTWEIGHT_RENDER_PIPELINE)
                    fixed3 directionalLightColor = _MainLightColor.rgb;
    #else
                    fixed3 directionalLightColor = _LightColor0.rgb;
    #endif
                    output.rgb *= lerp((ambient + directionalLightColor * diffuse + directionalLightColor * specular) * max(oneMinusMetallic, _MinMetallicLightContribution), albedo, minProperty);
                    output.rgb += (directionalLightColor * albedo * specular) + (directionalLightColor * specular * _Smoothness);
                    output.rgb += ibl * oneMinusMetallic * _IblContribution;
    #endif

    #if defined(_FRESNEL)
                    output.rgb += fresnelColor * (1.0 - minProperty);
    #endif
                    return output;
                }
                ENDCG
            }
            }
                Fallback "Hidden/InternalErrorShader"
 }