Meta Pass generation of Shader’s indirect light in Unity

Article directory

  • Preface
    • The indirect light of Shader in Unity generates Meta Pass, which is also the content of global illumination GI. It is mainly implemented like in real life, when light shines on a colored object, the object has the effect of reflecting light of that color.
  • 1. Let’s first use Unity’s own Shader to see the indirect light effect.
    • 1. First build a scene according to the following settings
    • 2. Then, set as shown below and make a material ball for the small ball.
    • 3. Finally, set the GI as shown below and bake to see the effect.
    • Effect before baking:
    • Effect after baking: (The wall already has the effect of indirect light)
    • Insert image description here
  • 2. Implement the effect of indirect light in our Shader
    • 1. Define a color attribute in the properties panel
    • 2. In the Shader that comes with Unity, copy the Pass named META to our Shader. This Pass implements the self-illumination function.
    • 3. Because this Pass is usually completed during baking, it does not affect our real-time rendering. We do not need to modify it. Let’s take a look at the implemented functions.
    • 4. As mentioned above in the last sentence of Shader, we have other ways to achieve the same modification.

Foreword

Shader’s indirect light in Unity generates Meta Pass, which is also part of the global illumination GI. It is mainly implemented like in real life, when light shines on a colored object, the object has the effect of reflecting light of that color.

1. Let’s first use Unity’s own Shader to see the indirect light effect

1. First build a scene according to the following settings

2. Then, set as shown below and make a material ball for the small ball

3. Finally, set the GI as shown below and bake to see the effect

The effect before baking:

The effect after baking: (the wall already has the effect of indirect light)

2. Implement the effect of indirect light in our Shader

Continue to complete the previous article Indirect light effects in GI

  • Shader light probe support in Unity

This is the baking effect before indirect light is achieved.

1. Define a color attribute in the properties panel

Properties
{
_Color(“Color”,Color) = (1,1,1,1)
}

2. In the Shader that comes with Unity, copy the Pass named META to our Shader. This Pass implements the self-illumination function

//It will not be used during normal rendering. Generally used in baking to calculate indirect light bounce
        // Extracts information for lightmapping, GI (emission, albedo, ...)
        // This pass it not used during regular rendering.
        Pass
        {
            Name "META"
            Tags
            {
                "LightMode" = "Meta"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragment
            #pragma target 2.0
            #include "UnityCG.cginc"
            #include "UnityMetaPass.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uvMain : TEXCOORD0;
                float2 uvIllum : TEXCOORD1;
                #ifdef EDITOR_VISUALIZATION
            float2 vizUV : TEXCOORD2;
            float4 lightCoord : TEXCOORD3;
                #endif
                UNITY_VERTEX_OUTPUT_STEREO
            };

            float4 _MainTex_ST;
            float4 _Illum_ST;

            v2f vert(appdata_full v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                o.pos = UnityMetaVertexPosition(v.vertex, v.texcoord1.xy, v.texcoord2.xy, unity_LightmapST,
         unity_DynamicLightmapST);
                o.uvMain = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uvIllum = TRANSFORM_TEX(v.texcoord, _Illum);
                #ifdef EDITOR_VISUALIZATION
            o.vizUV = 0;
            o.lightCoord = 0;
            if (unity_VisualizationMode == EDITORVIZ_TEXTURE)
                o.vizUV = UnityMetaVizUV(unity_EditorViz_UVIndex, v.texcoord.xy, v.texcoord1.xy, v.texcoord2.xy, unity_EditorViz_Texture_ST);
            else if (unity_VisualizationMode == EDITORVIZ_SHOWLIGHTMASK)
            {
                o.vizUV = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
                o.lightCoord = mul(unity_EditorViz_WorldToLight, mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)));
            }
                #endif
                return o;
            }

            sampler2D _MainTex;
            sampler2D_Illum;
            fixed4 _Color;
            fixed _Emission;

            half4 frag(v2f i) : SV_Target
            {
                UnityMetaInput metaIN;
                    UNITY_INITIALIZE_OUTPUT(UnityMetaInput, metaIN);

                fixed4 tex = tex2D(_MainTex, i.uvMain);
                fixed4 c = tex * _Color;
                metaIN.Albedo = c.rgb;
                metaIN.Emission = c.rgb * tex2D(_Illum, i.uvIllum).a;
                #if defined(EDITOR_VISUALIZATION)
            metaIN.VizUV = i.vizUV;
            metaIN.LightCoord = i.lightCoord;
                #endif

                return UnityMetaFragment(metaIN);
            }
            ENDCG
        }

Note: If the baked indirect light is not displayed at this time, add this sentence at the end of the Shader

CustomEditor “LegacyIlluminShaderGUI”

3. Because this Pass is usually completed during baking, it does not affect our real-time rendering. We do not need to modify it. Let’s take a look at the implemented functions

1. This is the main function to implement this function. Generally, it is not modified because it does not affect our real-time rendering.

2. The main data in UnityMetaInput
Indirect light generally considers: reflectivity of the object, self-illumination of the object, highlight color of the object


3. Delete some unused functions and adjust them according to your needs.

v2f reserved:

struct v2f
{
float4 pos : SV_POSITION;
};

Vertex shader retains:

v2f vert(appdata_full v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f,o)
o.pos = UnityMetaVertexPosition(v.vertex, v.texcoord1.xy, v.texcoord2.xy, unity_LightmapST,
unity_DynamicLightmapST);
return o;
}

Fragment shaders retain:

half4 frag(v2f i) : SV_Target
{
UnityMetaInput metaIN;
UNITY_INITIALIZE_OUTPUT(UnityMetaInput, metaIN);
metaIN.Albedo = 1;
metaIN.Emission = _Color;
return UnityMetaFragment(metaIN);
}

4. As for the sentence added at the end of Shader above, we have other ways to achieve the same modification

CustomEditor “LegacyIlluminShaderGUI”

1. First, change the Inspect window to Debug mode

2. Modify the value of the shader to achieve the same effect as the above code.

Finally, modify the value to 2 and you can see the final effect.

Final code:

//Use custom cginc here to implement global GI
//Preparation of GI data
//Judgement of baking branch
//Direct light implementation of GI
//GI indirect light implementation
//Rediscuss ATTENUATION
//Light probe support
//Indirect light generation Meta Pass
Shader "MyShader/P1_8_9"
{
    Properties
    {
        _Color("Color",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragment
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"

            #include "CGIncludes/MyGlobalIllumination.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                //Define the second set of UVs. The fixed semantics corresponding to appdata is TEXCOORD1
                #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
                float4 texcoord1 : TEXCOORD1;
                #endif
                half3 normal : NORMAL;
                float4 texcoord2 : TEXCOORD2;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;

                float4 worldPos : TEXCOORD;
                //Define the second set of UVs
                #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
                float4 lightmapUV : TEXCOORD1;
                #endif
                half3 worldNormal : NORMAL;

                half3 sh : TEXCOORD2;
                //1. The first step of using shadow sampling and light attenuation solutions
                //Define light attenuation and the interpolator required for real-time shadow sampling at the same time
                UNITY_LIGHTING_COORDS(3, 4)
                //UNITY_SHADOW_COORDS(2)
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                //Texture sampling for the second set of UVs
                #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
                    o.lightmapUV.xy = v.texcoord1 * unity_LightmapST.xy + unity_LightmapST.zw;
                #endif

                //Realize the calculation of spherical harmonics or environment color and vertex lighting
                //SH/ambient and vertex lights
                #ifndef LIGHTMAP_ON //When static baking is not enabled for this object
                #if UNITY_SHOULD_SAMPLE_SH & amp; & amp; !UNITY_SAMPLE_FULL_SH_PER_PIXEL
                    o.sh = 0;
                    //Approximately simulate the lighting effect of non-important level point lights on a vertex-by-vertex basis
                #ifdef VERTEXLIGHT_ON
                        o.sh + = Shade4PointLights(
                        unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
                        unity_LightColor[0].rgb,unity_LightColor[1].rgb,unity_LightColor[2].rgb,unity_LightColor[3].rgb,
                        unity_4LightAtten0,o.worldPos,o.worldNormal);
                #endif
                    o.sh = ShadeSHPerVertex(o.worldNormal,o.sh);
                #endif
                #endif


                //2. The second step of the scheme using shadow sampling and light attenuation
                UNITY_TRANSFER_LIGHTING(o, v.texcoord2.xy)
                //TRANSFER_SHADOW(o)
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                //1. Prepare SurfaceOutput data
                SurfaceOutput o;
                //Currently initialized to 0, use Unity's own method to initialize the contents of the structure to 0
                    UNITY_INITIALIZE_OUTPUT(SurfaceOutput, o)
                o.Albedo = 1;
                o.Normal = i.worldNormal;

                //1. Represents the attenuation effect of light
                //2. Real-time shadow sampling
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);


                //2. Prepare data for UnityGIInput
                UnityGIInput giInput;
                //initialization
                    UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput);
                //Modify the data used
                giInput.light.color = _LightColor0;
                giInput.light.dir = _WorldSpaceLightPos0;
                giInput.worldPos = i.worldPos;
                giInput.worldViewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
                giInput.atten = atten;
                giInput.ambient = 0;

                #if UNITY_SHOULD_SAMPLE_SH & amp; & amp; !UNITY_SAMPLE_FULL_SH_PER_PIXEL
                    giInput.ambient = i.sh;
                #else
                giInput.ambient = 0.0;
                #endif


                #if defined(DYNAMICLIGHTMAP_ON) || defined(LIGHTMAP_ON)
                giInput.lightmapUV = i.lightmapUV;
                #endif

                //3. Prepare UnityGI data
                UnityGIgi;
                //Direct lighting data (main parallel light)
                gi.light.color = _LightColor0;
                gi.light.dir = _WorldSpaceLightPos0;
                //Indirect lighting data (currently give 0)
                gi.indirect.diffuse = 0;
                gi.indirect.specular = 0;

                //Calculation of GI indirect lighting
                LightingLambert_GI1(o, giInput, gi);
                //Looking at the Unity source code, we can see that the most important function for calculating indirect lighting is
                //inline UnityGI UnityGI_Base1(UnityGIInput data, half occlusion, half3 normalWorld)
                //So we assign value to gi directly without using LightingLambert_GI1
                gi = UnityGI_Base1(giInput, 1, o.Normal);

                //Calculation of GI direct lighting
                //After we get the GI data, we calculate the Lambert lighting model and get the result.
                fixed4 c = LightingLambert1(o, gi);

                return c;
                //return fixed4(gi.indirect.diffuse,1);
                //return 1;
            }
            ENDCG
        }

        //Shadow casting
        Pass
        {
            //1. Set "LightMode" = "ShadowCaster"
            Tags
            {
                "LightMode" = "ShadowCaster"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragment
            //Need to add a Unity variant
            #pragma multi_compile_shadowcaster

            #include "UnityCG.cginc"

            //Declare variables used for ablation
            float _Clip;
            sampler2D _DissolveTex;
            float4 _DissolveTex_ST;

            //2. Declare float4 vertex:POSITION; and half3 normal:NORMAL; in appdata. This is the semantics required to generate shadows.
            //Note: In the appdata part, we almost don't need to modify the name and corresponding type.
            //Because many methods encapsulated in Unity use these standard names.
            struct appdata
            {
                float4 vertex:POSITION;
                half3 normal:NORMAL;
                float4 uv:TEXCOORD;
            };

            //3. Add V2F_SHADOW_CASTER in v2f; used to declare the data that needs to be transferred to the fragment.
            struct v2f
            {
                float4 uv : TEXCOORD;
                V2F_SHADOW_CASTER;
            };

            //4. Add TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) in the vertex shader, mainly to calculate the offset of the shadow to solve the incorrect Shadow Acne and Peter Panning phenomena.
            v2f vert(appdata v)
            {
                v2f o;
                o.uv.zw = TRANSFORM_TEX(v.uv, _DissolveTex);
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
                return o;
            }

            //5. Add SHADOW_CASTER_FRAGMENT(i) in the fragment shader

            fixed4 frag(v2f i) : SV_Target
            {
                //Texture obtained externally needs to be sampled before use
                fixed4 dissolveTex = tex2D(_DissolveTex, i.uv.zw);

                //fragment selection
                clip(dissolveTex.r - _Clip);

                SHADOW_CASTER_FRAGMENT(i);
            }
            ENDCG
        }
        //It will not be used during regular rendering. In general use, it is baking textures
        // Extracts information for lightmapping, GI (emission, albedo, ...)
        // This pass it not used during regular rendering.
        Pass
        {
            Name "META"
            Tags
            {
                "LightMode" = "Meta"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragment
            #pragma target 2.0
            #include "UnityCG.cginc"
            #include "UnityMetaPass.cginc"
            fixed4 _Color;
            
            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            v2f vert(appdata_full v)
            {
                v2f o;
                UNITY_INITIALIZE_OUTPUT(v2f,o)
                
                o.pos = UnityMetaVertexPosition(v.vertex, v.texcoord1.xy, v.texcoord2.xy, unity_LightmapST,
         unity_DynamicLightmapST);
                
                return o;
            }
            
            half4 frag(v2f i) : SV_Target
            {
                UnityMetaInput metaIN;
                UNITY_INITIALIZE_OUTPUT(UnityMetaInput, metaIN);
                metaIN.Albedo = 1;
                metaIN.Emission = _Color;
                return UnityMetaFragment(metaIN);
            }
            ENDCG
        }
    }
    CustomEditor "LegacyIlluminShaderGUI"
}