unity shader complex lighting (forward rendering)

Forward Rendering

In previous shader writings, only one light source, parallel light, corresponded to only one pass

Forward rendering calculates pass for each light source when there are multiple light sources, both parallel and spot, so it wastes performance when there are more objects

There's always such a piece of code in the previous shader

This tells unity that the pass uses a ForwardBase path for forward rendering, which also includes a path called ForwardAdd
ForwardBase is used to calculate ambient light, autoluminescence, and most important parallel light (only once, unless rendered on both sides)
ForwardAdd calculates the extra per-pixel illumination, one light source per pass (as many times as there are lights)

About which lights are processed pixel by pixel?
The brightest parallel light in a scene is processed pixel by pixel
The light source whose rendering mode is set to Important is also pixel-by-pixel
If set to Not Important, it will be processed vertex by vertex or SH

Principle:

To write a forward rendering
The code was written by someone else, but it was written well
Someone else's code is here

Shader "Custom/ForwardRendering"
{
    Properties{
        _MainTex("Main Texture",2D) = "white"{}
        _BaseColor("Base Color",Color) = (1.0,1.0,1.0,1.0)
        _Gloss("Gloss",Range(8.0,200.0)) = 20.0
    }
    SubShader{
        Tags{"Queue"="Geometry"}
        Pass{
            Tags{"LightMode"="ForwardBase"}
            CGPROGRAM
            #include "Lighting.cginc"
            #include "UnityCG.cginc"
            #pragma vertex Vertex
            #pragma fragment Pixel
            #pragma multi_compile_fwdbase //This compilation instruction is required

            struct vertexInput{

                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct vertexOutput{

                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };

            sampler2D _MainTex;
            fixed4 _BaseColor;
            float _Gloss;

            vertexOutput Vertex(vertexInput v){

                vertexOutput o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.uv = v.texcoord;

                return o;
            }
            fixed4 Pixel(vertexOutput i):SV_TARGET{

                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

                //BasePass does not differ from previous calculated illumination

                fixed3 albedo = tex2D(_MainTex,i.uv).xyz * _BaseColor.xyz;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                //Ambient light only needs to be calculated once

                fixed3 diffuse = _LightColor0.xyz * albedo * saturate(dot(lightDir,worldNormal));

                fixed3 halfDir = normalize(viewDir + lightDir);
                fixed3 specular = _LightColor0.xyz * pow(saturate(dot(halfDir,worldNormal)),_Gloss);

                fixed atten = 1.0;
                //Since parallel light has no light attenuation, the light attenuation is 1

                return fixed4(ambient + (specular + diffuse) * atten,1.0);
                //Calculating light attenuation
            }
            ENDCG
        }
        
        Pass{
            Tags{"LightMode"="ForwardAdd"}

            Blend One One
            //Always turn on mixing and set up linear mixing, otherwise other light sources cannot be attached
            CGPROGRAM
            #include "Lighting.cginc"
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #pragma vertex Vertex1
            #pragma fragment Pixel1
            #pragma multi_compile_fwdadd //This compilation instruction is required

            struct vertexInput{

                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct vertexOutput{

                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };

            sampler2D _MainTex;
            fixed4 _BaseColor;
            float _Gloss;

            vertexOutput Vertex1(vertexInput v){
                //The vertex shader stays the same because it has nothing to do with the shape of the model

                vertexOutput o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.uv = v.texcoord;

                return o;
            }
            fixed4 Pixel1(vertexOutput i):SV_TARGET{

                fixed3 worldNormal = normalize(i.worldNormal);
                //The first difference may be the direction of the light, because the direction of the parallel light can be directly replaced by the position, however
                //No other light source.

                #ifdef USING_DIRECTIONAL_LIGHT
                    //This macro instruction means that if the light source illuminating the object is a parallel light
                    fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                #else
                    //If it is not a parallel light, then the light direction is the light source position minus the model position
                    fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
                #endif
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                //Perspective direction is constant

                //BasePass does not differ from previous calculated illumination

                fixed3 albedo = tex2D(_MainTex,i.uv).xyz * _BaseColor.xyz;

                fixed3 diffuse = _LightColor0.xyz * albedo * saturate(dot(lightDir,worldNormal));

                fixed3 halfDir = normalize(viewDir + lightDir);
                fixed3 specular = _LightColor0.xyz * pow(saturate(dot(halfDir,worldNormal)),_Gloss);

                #ifdef USING_DIRECTIONAL_LIGHT
                    //This macro instruction means that if the light source illuminating the object is a parallel light

                    fixed atten = 1.0;   // Parallel light has no light attenuation
                #else
                    //If it is not a parallel light, the light attenuation can be calculated in the following way.

                    #if defined(POINT)
                        //If it is a point light source
                        float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                        fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
                    #elif defined(SPOT)
                        //If it's a spotlight
                        float4 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1));
                        fixed atten=(lightCoord.z>0)*tex2D(_LightTexture0,lightCoord.xy/lightCoord.w+0.5).w*tex2D(_LightTextureB0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
                    #endif
                #endif

                return fixed4((specular + diffuse) * atten,1.0);
                //Calculating light attenuation
            }

            ENDCG
        }
    }
}

The effect is as follows:

About Shadows

  • Let objects cast shadows:
    Open Cast Shadows as On in the MeshRender component
  • Let objects receive shadows
    1. First turn on the receive switch (there is a receive switch in the meshrender)
    2. Then the shader script needs three macro definitions
    SHADOW_COORDS: This macro is used to declare a shadow sampling coordinate, that is, to determine whether our calculated vertices or blocks of pixels fall into shadow by sampling ShadowMap.
    TRANSFER_SHADOW: This macro is used to convert shadow sampling coordinates. This macro does not need a semicolon.
    SHADOW_ATTENUATION: This macro is used to calculate the shadow value. If a pixel block falls in the shadow, the shadow value of the pixel block is set to the normal shadow value, otherwise it is set to 1 (i.e. no shadow).
Shader "Custom/Shadow"
{
    Properties
    {
        _BaseColor ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        //_Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Gloss ("_Gloss", Range(1,256)) = 128
    }
    SubShader
    {
       pass{
           Tags{"LightMode"="ForwardBase"}  //Compute Parallel Light
           CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           #pragma multi_compile_fwdbase
           #include "Lighting.cginc"
           #include "AutoLight.cginc"

           struct a2v{
               float4 vertex : POSITION;
                float3 normal : NORMAL;
                float3 texcoord : TEXCOORD0;
           };
           struct v2f{
               float4 pos : SV_POSITION;
               float3 worldPos : TEXCOORD0;
               float3 worldNormal : TEXCOORD1;
               float2 uv : TEXCOORD2;
               SHADOW_COORDS(3) //Shadow_Coords declare an uv coordinate, but simply sample ShadowMap.
           };
            fixed4 _BaseColor;
            sampler2D _MainTex;
            float _Gloss;

            v2f vert(a2v i){
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);
                o.worldPos = mul(unity_ObjectToWorld,i.vertex).xyz;
                o.worldNormal = UnityObjectToWorldNormal(i.normal);
                o.uv = i.texcoord;
                TRANSFER_SHADOW(o)    //Convert the uv coordinates of the shadow
                return o;
            }
            fixed4 frag(v2f i):SV_TARGET{
                fixed shadow = SHADOW_ATTENUATION(i);//Get shadows, mix this value with diffuse and highlight reflections
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir = normalize(lightDir+viewDir);
                fixed3 specular = _LightColor0.xyz * pow( saturate( dot(halfDir,worldNormal) ) , _Gloss );
                fixed3 albedo = tex2D(_MainTex,i.uv).xyz * _BaseColor.xyz;
                fixed3 diffuse = _LightColor0.xyz *  albedo * max(0,dot(lightDir,worldNormal));
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                return fixed4( ambient + (diffuse+specular) * shadow , 1);
            }
           ENDCG
       } 
    }
    FallBack "Diffuse"
}

You can see that the object successfully receives the shadow

UNITY_LIGHT_ATTENUATION
A function encapsulated by unit

UNITY_LIGHT_ATTENUATION(atten, i, worldPos);

The atten you get is the product of the attenuation of light and the shadows, and this macro is contained in AutoLight. Inside cginc header file
The first parameter atten passed in does not need to be defined, because it will help us define it. The second parameter is the input structure of the sheet shader, and the third parameter is the coordinates of the world space.
The same code for handling shadows and attenuations in two pass in forward rendering

This allows us to change our forward rendering to include code that receives projected shadows:

Shader "Custom/ForwardRendering"
{
    Properties{
        _MainTex("Main Texture",2D) = "white"{}
        _BaseColor("Base Color",Color) = (1.0,1.0,1.0,1.0)
        _Gloss("Gloss",Range(8.0,200.0)) = 20.0
    }
    SubShader{
        Tags{"Queue"="Geometry"}
        Pass{
            Tags{"LightMode"="ForwardBase"}
            CGPROGRAM
            #include "Lighting.cginc"
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #pragma vertex Vertex
            #pragma fragment Pixel
            #pragma multi_compile_fwdbase //This compilation instruction is required

            struct vertexInput{

                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };
            struct vertexOutput{

                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
                SHADOW_COORDS(3)
            };
            sampler2D _MainTex;
            fixed4 _BaseColor;
            float _Gloss;

            vertexOutput Vertex(vertexInput v){
                vertexOutput o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.uv = v.texcoord;
                TRANSFER_SHADOW(o);
                return o;
            }
            fixed4 Pixel(vertexOutput i):SV_TARGET{
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                //BasePass does not differ from previous calculated illumination
                fixed3 albedo = tex2D(_MainTex,i.uv).xyz * _BaseColor.xyz;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                //Ambient light only needs to be calculated once
                fixed3 diffuse = _LightColor0.xyz * albedo * saturate(dot(lightDir,worldNormal));
                fixed3 halfDir = normalize(viewDir + lightDir);
                fixed3 specular = _LightColor0.xyz * pow(saturate(dot(halfDir,worldNormal)),_Gloss);
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                return fixed4(ambient + (specular + diffuse) * atten,1.0);
                //Calculating light attenuation
            }
            ENDCG
        }
        
        Pass{
            Tags{"LightMode"="ForwardAdd"}
            Blend One One
            //Always turn on mixing and set up linear mixing, otherwise other light sources cannot be attached
            CGPROGRAM
            #include "Lighting.cginc"
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #pragma vertex Vertex1
            #pragma fragment Pixel1
            #pragma multi_compile_fwdadd //This compilation instruction is required
            struct vertexInput{
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };
            struct vertexOutput{
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };

            sampler2D _MainTex;
            fixed4 _BaseColor;
            float _Gloss;

            vertexOutput Vertex1(vertexInput v){
                //The vertex shader stays the same because it has nothing to do with the shape of the model
                vertexOutput o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.uv = v.texcoord;
                return o;
            }
            fixed4 Pixel1(vertexOutput i):SV_TARGET{
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                //Perspective direction is constant
                //BasePass does not differ from previous calculated illumination
                fixed3 albedo = tex2D(_MainTex,i.uv).xyz * _BaseColor.xyz;
                fixed3 diffuse = _LightColor0.xyz * albedo * saturate(dot(lightDir,worldNormal));
                fixed3 halfDir = normalize(viewDir + lightDir);
                fixed3 specular = _LightColor0.xyz * pow(saturate(dot(halfDir,worldNormal)),_Gloss);
               UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                return fixed4( (specular + diffuse) * atten,1.0);
                //Calculating light attenuation
            }

            ENDCG
        }
    }
}

Deferred Shading

Delayed rendering mainly consists of two pass. The first pass calculates only those slices that are visible without any illumination calculation, mainly through deep buffer technology. When a normal slice is visible, its related information is stored in the G buffer, and the second pass calculates the illumination using the information of each slice in the G buffer.
Therefore, delayed rendering does not depend on the number of light sources in the scene, and the efficiency of delayed rendering does not depend on scene complexity but only on the size of screen space.

Disadvantages of delayed rendering:
No anti-aliasing, no translucency

Let's start here and have time to learn delayed rendering

Keywords: Unity

Added by ale8oneboy on Sat, 29 Jan 2022 13:45:13 +0200