夜風のMixedReality

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

MRTK StanderdShaderを読み解く その6 ライトの影響を受けるようにする

本日はMRTKStandardShaderの勉強枠です。

〇DirectionalLightの影響を受けるようにする

f:id:Holomoto-Sumire:20200627191236g:plain

Shader "Custom/MRTKDirectionalLight"
{
    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(_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 _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
                #define _UV

                struct appdata_t
                {
                    float4 vertex : POSITION;
                    // The default UV channel used for texturing.
                    float2 uv : TEXCOORD0;
 
                    fixed3 normal : NORMAL;
   
                    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(_NORMAL)
  
                    fixed3 worldNormal : COLOR3;
    #endif
                    UNITY_VERTEX_OUTPUT_STEREO
                };


                fixed4 _Color;

                sampler2D _MainTex;
                fixed4 _MainTex_ST;

  
                fixed _Metallic;
                fixed _Smoothness;

    #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
                v2f vert(appdata_t v)
                {
                    v2f o;
                    UNITY_SETUP_INSTANCE_ID(v);
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
  
                    float4 vertexPosition = v.vertex;

                    fixed3 localNormal = v.normal;

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

                    o.position = UnityObjectToClipPos(vertexPosition);   
    #if defined(_UV)
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    #endif

    #if defined(_NORMAL)
    
                    o.worldNormal = worldNormal;
    #endif

                    return o;
                }

                fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {           
                    fixed4 albedo = tex2D(_MainTex, i.uv);


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

  
                    worldNormal = normalize(i.worldNormal) * facing;
    #endif

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

    #if defined(_DIRECTIONAL_LIGHT)

                    float4 directionalLightDirection = _WorldSpaceLightPos0;

                    fixed diffuse = max(0.0, dot(worldNormal, directionalLightDirection));
 
                    fixed specular = 0.0;
    #endif

                    // Image based lighting (attempt to mimic the Standard shader).
   
                    fixed3 ibl = unity_IndirectSpecColor.rgb;


                    // Fresnel lighting.
 
                    // 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);
  
                    fixed3 directionalLightColor = _LightColor0.rgb;
                    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

                    return output;
                }

                ENDCG
            }
            }

            Fallback "Hidden/InternalErrorShader"
                    // CustomEditor "Microsoft.MixedReality.Toolkit.Editor.MixedRealityStandardShaderGUI"
}

追加した点は多いですが一つ一つ見ていきます。

プリプロセッサによる条件分岐コンパイル

MRTKStandardShaderには様々な機能が提供されていますが、そのほとんどはプリプロセッサによって条件分岐され、必要ない機能はコンパイルされません。

これによってMRTKStandardShaderは使用したい機能だけのピンポイントなコスト管理で軽量なレンダリングを実現しています。

プリプロセッサの例
    #if defined(_DIRECTIONAL_LIGHT) 
                #define _NORMAL
    #else
                #undef _NORMAL
    #endif

 この部分はもしDIRECTIONAL_LIGHTが定義されているならNORMALが定義され、DIRECTIONAL_LIGHTが定義されていない場合NORMALは未定義として扱う処理をコンパイル前に行います。

〇 Properties

   Properties
    {
   ...
          [Toggle(_DIRECTIONAL_LIGHT)] _DirectionalLight("Directional Light", Float) = 1.0
    }

追加したものは一つです。

[Toggle()]アトリビュートはマテリアルのプロパティでBool型を扱うことができます。1.0の場合チェックがオンで()ないが有効になります。

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

〇SubShader

                    #pragma shader_feature _DIRECTIONAL_LIGHT

これはシェーダーバリアントと呼ばれる仕組みでわずかに細部が異なるshaderをコンパイルする仕組みのようです。

docs.unity3d.com

これによって_DIRECTIONAL_LIGHTが使用されていない部分をコンパイルしないようにしています。

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

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

ここではマテリアル側でDirectionalLightがオンになっていない場合_NORMALは定義されません。

またNORMALが定義されている場合WORLD_POSITIONが定義され、_NORMALが定義されていない場合は未定義となります。

その他_UVが定義されます。

appdata_t構造体では特に追加したものはありません。

〇v2f構造体

               struct v2f
                {
                    float4 position : SV_POSITION;

    #if defined(_UV)
                    float2 uv : TEXCOORD0;
    #endif
 
    #if defined(_WORLD_POSITION)

                    float3 worldPosition : TEXCOORD2;
    #endif
 
    #if defined(_NORMAL)
  
                    fixed3 worldNormal : COLOR3;
    #endif
                    UNITY_VERTEX_OUTPUT_STEREO
                };

ここでもプリプロセッサによる条件分岐コンパイルが行われます。

    #if defined(_UV)
                    float2 uv : TEXCOORD0;
    #endif
 
    #if defined(_WORLD_POSITION)

                    float3 worldPosition : TEXCOORD2;
    #endif
 
    #if defined(_NORMAL)
  
                    fixed3 worldNormal : COLOR3;
    #endif

DirectionalLightのチェックボックスが有効化されている場合 UV、WORLD_POSITION、_NORMALがすべて定義されているためv2f構造体は以下のようになります。

          struct v2f
                {
                    float4 position : SV_POSITION;

                    float2 uv : TEXCOORD0;
                    float3 worldPosition : TEXCOORD2;
  
                    fixed3 worldNormal : COLOR3;
                    UNITY_VERTEX_OUTPUT_STEREO
                };

Positionはシステム上の位置、uvは一番目のUV座標、worldPositionはUV座標、worldNormalは色として扱われます。

〇頂点シェーダー

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

    #if defined(_NORMAL) 
                    fixed3 worldNormal = UnityObjectToWorldNormal(localNormal);
    #endif
                    o.position = UnityObjectToClipPos(vertexPosition);   
    #if defined(_UV)
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    #endif
    #if defined(_NORMAL)
    
                    o.worldNormal = worldNormal;
    #endif

                    return o;
                }

頂点シェーダーも頂点シェーダー同様DirectionalLightが有効の場合次のようになります。

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

                    fixed3 worldNormal = UnityObjectToWorldNormal(localNormal);
                    o.position = UnityObjectToClipPos(vertexPosition);   
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);    
                    o.worldNormal = worldNormal;
                    return o;
                }

worldNormalは頂点の持つ法線をUnity座標に変換したものが代入されます。これはそのままv2fのworldNormalに代入されます。

v2fのpositionには頂点をUnityの座標に変換したものが使用されます。

v2fのuvにはTRANSFORM_TEX()が使用されます。 TRANSFORM_TEX()はTEX2D()と同じように使用されタイリングやOffsetの計算を行う場合に使用します。

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

              fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {           
                    fixed4 albedo = tex2D(_MainTex, i.uv);
    #if defined(_NORMAL)
                    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition.xyz));
                    fixed3 worldNormal;
                    worldNormal = normalize(i.worldNormal) * facing;
    #endif

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

    #if defined(_DIRECTIONAL_LIGHT)

                    float4 directionalLightDirection = _WorldSpaceLightPos0;

                    fixed diffuse = max(0.0, dot(worldNormal, directionalLightDirection));
 
                    fixed specular = 0.0;
    #endif

                    fixed3 ibl = unity_IndirectSpecColor.rgb;

                    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);
  
                    fixed3 directionalLightColor = _LightColor0.rgb;
                    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

                    return output;
                }

フラグメントシェーダーもDirectionalLightを使用している場合次のようになります。

              fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {           
                    fixed4 albedo = tex2D(_MainTex, i.uv);
  
                    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition.xyz));

                    fixed3 worldNormal;
                    worldNormal = normalize(i.worldNormal) * facing;

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

                    float4 directionalLightDirection = _WorldSpaceLightPos0;
                    fixed diffuse = max(0.0, dot(worldNormal, directionalLightDirection));
                    fixed specular = 0.0;

                    fixed3 ibl = unity_IndirectSpecColor.rgb;

                    fixed4 output = albedo;

                    fixed3 ambient = glstate_lightmodel_ambient + fixed3(0.25, 0.25, 0.25);
  
                    fixed minProperty = min(_Smoothness, _Metallic);
                    fixed oneMinusMetallic = (1.0 - _Metallic);
                    output.rgb = lerp(output.rgb, ibl, minProperty);
  
                    fixed3 directionalLightColor = _LightColor0.rgb;
                    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;
                      return output;
                }
         fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition.xyz));

                    fixed3 worldNormal;
                    worldNormal = normalize(i.worldNormal) * facing;

worldViewDirにはUnityのワールド座標での頂点の位置を正規化したものが代入されます。

worldNormalには頂点の法線を正規化した値にfacingを積算したものが代入されます。 facingはメッシュの向きを意味します。

                    float4 directionalLightDirection = _WorldSpaceLightPos0;

directionalLightDirectionにはワールド座標系でのライトの位置が代入されます。

        fixed3 ibl = unity_IndirectSpecColor.rgb;

IndirectSpecColorは調べても情報が見つからなかったのですが、間接光を指すようです。これがiblになります。

    output.rgb = lerp(output.rgb, ibl, minProperty);

出力結果のRGBとシーンの間接光のパラメータをsmoothとmetallicの割合の結果でなだらかに変位させています。

メタリックの金属感やスムースの反射は周りの物体の影響を受けます。つまりこれは周囲の物体による反射光の影響を表します。

directionalLightColorにはUnityのシーン内の照明強度が入ります。

ここを以下のようにすると

                fixed3 directionalLightColor = (0, 0, 0);

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

このように照明の影響を受けることができなくなります。

    output.rgb *= lerp((ambient + directionalLightColor * diffuse + directionalLightColor * specular) * max(oneMinusMetallic, _MinMetallicLightContribution), albedo, minProperty);

影の影響を処理します。(ambient + directionalLightColor * diffuse + directionalLightColor * specular) が周囲の環境の影響を意味し、これにMetallicの影響が積算されます。

この環境の影響とalbedoとの間でなだらかに線形補完された値が出力に積算されます。

以上でDirectionalLightの影響を受けるShaderができました。

冒頭でも説明しましたが、マテリアルからDirectionalLightのチェックボックスを外すことでDirectionalLightに関するコードがコンパイルされない仕組みになっています。