Shader light probe support in Unity

Article directory

  • Preface
  • 1. Where and how to use light probes
    • 1. Application scenarios of light probes
    • 2. We build an identical environment in Unity according to the above conditions.
    • 3. Create light probes
  • 2. Implement support for light probes in our own Shader
    • 1. Use the commonly used cginc
    • 2. In v2f, prepare the following variables
    • 3. In the vertex shader, after converting the vertex and normal world space, use the following code
    • 4. In the fragment shader, use the following code to calculate
    • final code

Foreword

Mainly writing about the support of light probes in global illumination

1. Where and how to use light probes

1. Application scenarios of light probes

In a scene with only Backed mode lights, there is a non-static object
That is, when baking, the object will not be baked and will not be affected by baking lights.

However, we cannot modify the mode of the light at this time, nor can we modify the object to be a static object.
But the dynamic object needs to be affected by baked lights
At this point you need to use a light probe

2. We build an identical environment in Unity according to the above conditions


We will find that the small balls are not affected by the baking light after baking.
Please add image description

3. Create light probe

You can add a Light Probe Group directly to an empty object, or add a light probe directly as shown below

After adding, set the range of the light probe to the range where the dynamic ball is affected by the baked light.
In the light probe, the denser the yellow dots are in the space, the more delicate the baked light received by the dynamic object

Then, after we bake it, we can see the effect of the ball receiving the baking light.
Please add image description

2. Implement support for light probes in our own Shader

We continue to use the previous article as a test

  • Shader in Unity discusses ATTENUATION again

We will find that our Shader is completely black after use
Because we turned off the main parallel light, the two point light sources are of the Backed class.

1. Use commonly used cginc

#include “AutoLight.cginc”
#include “Lighting.cginc”

2. In v2f, prepare the following variables

float4 worldPos : TEXCOORD;
half3 worldNormal : NORMAL;
half3 sh : TEXCOORD2;

3. In the vertex shader, after converting the vertex and normal world space, use the following code

//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

4. In the fragment shader, use the following code to calculate

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

Then, we can see that our Shader also has the effect of the light probe.

Please add image description

At the same time, there is also the effect of vertex-by-vertex lighting.
Please add image description

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
Shader "MyShader/P1_8_8"
{
    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;
            };

            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.texcoord1.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
        }
    }
}