"Introduction to Unity Shader Essentials" Chapter 6 Basic Lighting in Unity

Chapter 6 Basic Lighting in Unity

6.1 How we see the world

Generally speaking, we need to simulate the real lighting environment to generate an image, and we need to consider three physical phenomena:

  • First, light rays are emitted 光源(light source)from
  • Then, the ray intersects some objects in the scene, some rays are absorbed and some are scattered in other directions
  • Finally, the camera absorbs some light, producing an image

light source

In optics, we use 辐照度(irradiance)to quantify light. For parallel light, its radiance can be obtained by calculating the energy passing through per unit time on a unit area perpendicular to the direction of light.
insert image description here
Since irradiance is inversely proportional to the distance d/cos between the rays hitting the surface, it is directly proportional to cos, which can be obtained using the dot product of the light source direction and the surface normal n.

absorption and scattering

After the light is emitted by the light source, it will intersect with some objects, and there are two results of the intersection: 散射(scattering)and 吸收(absorption). Scattering only changes the direction of light, not the density and color of light; while absorption only changes the density and color of light, but not the direction of light.
After the light is scattered on the surface of the object, there are two directions: one will be scattered into the interior of the object, this phenomenon is called 折射(refraction)or 透射(transmission); the other will be scattered to the outside, this phenomenon is called 反射(reflection). For opaque objects, light rays refracted into the object's interior continue to intersect with the particles inside, some of which are eventually re-emitted out of the object's surface, while others are absorbed by the object. Those rays re-emitted from the object's surface will have a different direction distribution and color than the incident rays.
insert image description here
In order to distinguish between these two different scattering directions, we use different parts in the lighting model to calculate them: the 高光反射(specular)part represents how the surface of the object reflects light, and 漫反射(diffuse)the part represents how much light will be refracted, absorbed and scattered out of the surface . According to the number and direction of incident rays, we usually use 出射度(exitance)to describe it. There is a linear relationship between irradiance and emission, and the ratio between them is the diffuse reflection and specular reflection properties of the material.

coloring

着色(shading)It refers to the process of using an equation to calculate the output degree along a certain viewing direction according to the material properties and light source information. We usually call this equation 光照模型(Lighting Model).

BRDF

BRDF (Bidirectional Reflectance Distribution Function) refers to a function of four real variables that defines how light is reflected on opaque surfaces. When the direction and irradiance of the incident light are given, BRDF can give the light energy distribution in a certain outgoing direction. The BRDFs involved in this chapter are all idealized and simplified models of real scenes, that is, they cannot truly reflect the interaction between objects and light. These lighting models are called empirical models. Nevertheless, these empirical models have been used in real-time rendering for many years. Sometimes we hope to simulate the interaction between light and objects more realistically, and this happens 基于物理的 BRDF 模型, and we will see these more complex lighting models later.

6.2 Standard lighting model

Although there are many types of lighting models, only one lighting model was often used in early game engines, that is, the standard lighting model. Its basic method is to divide the light entering the camera into 4 parts, and each part uses a method to calculate its contribution:

  • 自发光(emissive), which describes how much radiance a surface itself emits in that direction when given a direction. Without global illumination, these self-illuminating surfaces do not actually illuminate surrounding objects, they just make themselves appear brighter.
  • 高光反射(specular), which is used to describe how much radiation is scattered by the surface in the direction of perfect specular reflection when light hits the model surface from the light source.
  • 漫反射(diffuse), which is used to describe how much radiation is scattered in each direction by the surface of the model when light hits it from the light source.
  • 环境光(ambient), this section is used to describe all other indirect lighting.

ambient light

While the standard lighting model focuses on describing direct lighting, in the real world objects can also be 间接光照lit by (indirect light). Indirect lighting refers to the light that bounces between multiple objects and finally enters the camera.
In the standard lighting model, we use something called ambient light to approximate indirect lighting. The calculation of ambient light is very simple, it is usually a global variable, that is, all objects in the scene use this ambient light:
insert image description here

Self-luminous

Light can also be emitted directly from the light source and into the camera. The standard lighting model uses self-illumination to calculate the contribution of this part, and its calculation is also very simple, that is, the self-illumination color of the material is directly used: in
insert image description here
normal real-time rendering, the self-illuminating surface does not illuminate the surrounding surfaces , that is, the object will not be treated as a light source. Introduced by Unity 5 全局光照系统, it is possible to simulate the impact of such self-illuminating objects on surrounding objects.

diffuse reflection

Diffuse lighting is used to model the radiance that is randomly scattered in all directions by the surface of the object. In diffuse reflection, the position of the viewing angle is not important, because the reflection is completely random, so it can be considered that in any reflection The distribution in all directions is the same.
However, the angle of the incoming ray is important, and diffuse lighting follows 兰伯特定律(Lambert's law): the intensity of the reflected ray is proportional to the cosine of the angle between the surface normal and the direction of the light source.
insert image description here
where n^ is the surface normal, l^ is the unit vector pointing to the light source, Mdiffuse is the diffuse color of the material, and Clight is the light source color. It should be noted that we need to prevent the result of the point product of the normal and the direction of the light source from being a negative value. For this, we use the function of taking the maximum value to intercept it to 0, which can prevent the object from being illuminated by the light source from behind .

specular reflection

The specular reflection here is an empirical model, that is, it does not exactly correspond to the specular phenomenon in the real world. To calculate specular reflection, you need to know a lot of information: surface normal n, viewing angle direction v, light source direction l, and reflection direction r.
insert image description here
Among these four vectors, we actually only need to know 3 of them, and the fourth vector, the reflection direction, can be calculated from other information: In this way,
insert image description here
we can use it Phong 模型to calculate the part of the highlight reflection:
insert image description here
where , Mgloss is a material 光泽度(gloss), also known as shininess. It controls how wide the "bright spot" is in the highlight area, the larger the Mgloss, the smaller the highlight. Mspecular is the specular color of the material, it is used to control the strength and color of the material for the specular reflection. Clight is the color and intensity of the light source. Similarly, it is also necessary to prevent the result of v * r from being negative.
Compared with the above Phong model, Blinn proposed a simple modification method to obtain similar effects. Its basic idea is to avoid calculating the reflection direction. To this end, Blinn 模型a new vector h is introduced, which is obtained by averaging v and l and then normalizing.
insert image description here
Then, use the angle between n and h instead of the angle between v and r:
insert image description here
insert image description here
in hardware implementation, the Blinn model will be faster than the Phong model if the camera and light source are far enough away from the model, which is Because, at this time, both v and l can be considered as fixed values, so h will be a constant. However, when v or l is not constant, the Phong model may be faster.
It is required that these two lighting models are empirical models, and they are not completely consistent with the specular reflection phenomenon in the real world.

per vertex or per pixel

We usually have two options when implementing the above lighting model:

  • Computed in the fragment shader, also known as 逐像素光照(per-pixel lighting). In pixel-by-pixel lighting, we will get its normal based on each pixel, and then calculate the lighting model. This technique of interpolating vertex normals between patches is called Phong 着色(Phong shading), also known as Phong interpolation or normal interpolation shading technique.
  • Computed in the vertex shader, called 逐顶点光照(per-vertex lighting), also called 高洛德着色(Gouraud shading). In per-vertex lighting, we calculate the lighting on each vertex, then perform linear interpolation inside the rendering primitive, and finally output the pixel color.
  • Since the number of vertices is often much smaller than the number of pixels, the calculation amount of per-vertex lighting is often less than that of per-pixel lighting. However, since per-vertex lighting relies on linear interpolation to obtain pixel lighting, it can cause problems when there are nonlinear calculations in the lighting model (such as when calculating specular reflections).

6.3 Ambient light and self-illumination in Unity

The ambient light in the Unity scene can be set window -> Rending -> Ligthingin
insert image description here
the specific light settings can directly refer to the official Unity manual.
In addition, it is very simple to achieve self-illumination in Unity. We only need to set the self-illumination color of the material before the fragment shader outputs the final color. Add it to the output color.

6.4 Realize the diffuse reflection lighting model in Unity Shader

per vertex lighting

Shader "Chapter 6/Diffuse Vertex Level"
{
    
    
    Properties
    {
    
    
        // 定义漫反射颜色,默认为白色
        _diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
    
    
        Pass
        {
    
    
            // 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
            Tags {
    
     "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            // 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
            #include "UnityLightingCommon.cginc"

            fixed4 _diffuse;

            struct a2v
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                fixed3 color : COLOR;
            };

            v2f vert (a2v v)
            {
    
    
                v2f o;
                // 坐标转换
                o.vertex = UnityObjectToClipPos(v.vertex);
                
                // 获取环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                // 转换法线向量至世界坐标系
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                // 场景中只有一个光源且是平行光时,光源方向可以由 _WorldSpaceLightPos0 得到,当有多个光源时无法这样使用
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                // saturate 函数用于将结果截取至 [0,1]
                fixed3 diffuse = _LightColor0.rgb * _diffuse.rgb * saturate(dot(worldNormal, worldLight));
                // 将漫反射颜色与环境光叠加,作为返回结果
                o.color = ambient + diffuse;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                return fixed4(i.color, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

Per Pixel Lighting

Shader "Chapter 6/Diffuse Frag Level"
{
    
    
    Properties
    {
    
    
        // 定义漫反射颜色,默认为白色
        _diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
    
    
        Pass
        {
    
    
            // 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
            Tags {
    
     "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            // 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
            #include "UnityLightingCommon.cginc"

            fixed4 _diffuse;

            struct a2v
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
            };

            v2f vert (a2v v)
            {
    
    
                // 顶点着色器只负责进行坐标转换
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                // 颜色计算移动至片元着色器中进行
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _diffuse.rgb * saturate(dot(i.worldNormal, worldLight));
                fixed3 color = ambient + diffuse;
                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

Half Lambert Model

Whether you use vertex-by-vertex or pixel-by-pixel, there will be a problem: in the area where the light cannot reach, the appearance of the model is usually completely black without any light and dark changes, which will make the backlit area of ​​the model look like a plane, Model details are lost. For this reason, an improvement technique has been proposed, which is 半兰伯特(Half Lambert)the lighting model.
insert image description here
It can be seen that compared with the original Lambert model, the semi-Lambert lighting model does not use the max operation to prevent the dot product of the sum from being negative, but scales the result by a factor of one and adds a size of offset. In most cases, the value of the sum is 0.5, that is, the formula is:
insert image description here
In this way, we can map the result range of n*l from [−1, 1] to [0, 1]. That is to say, for the backlit surface of the model, the dot product result in the original Lambert lighting model will be mapped to the same value, that is, the value of 0; while in the half-Lambert model, the backlit surface can also have light and dark changes, different The dot product results of will map to different values. It should be noted that semi-Lambert has no physical basis, it is only a visual enhancement technique.
Here is a vertex-by-vertex implementation of the semi-Lambert model:

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

Shader "Chapter 6/Diffuse Half Lambert"
{
    
    
    Properties
    {
    
    
        _diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
    
    
        Pass
        {
    
    
            Tags {
    
     "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            fixed4 _diffuse;

            struct a2v
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                fixed3 color : COLOR;
            };

            v2f vert (a2v v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                // 半兰伯特模型算法
                fixed3 halfLambert = dot(worldNormal, worldLight) * 0.5 + 0.5;
                fixed3 diffuse = _LightColor0.rgb * _diffuse.rgb * halfLambert;
                o.color = ambient + diffuse;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                return fixed4(i.color, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

Here is a comparison of per-vertex diffuse lighting, per-pixel diffuse lighting, and half-Lambert lighting:
insert image description here

6.5 Unity Shader implements specular reflection lighting model

per vertex lighting

Shader "Chapter 6/Specular Vertex Level"
{
    
    
    Properties
    {
    
    
        // 定义漫反射颜色,默认为白色
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        // 高光反射颜色
        _Specular ("Specular", Color) = (1, 1, 1, 1)
        // 用于控制高光区域大小
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }

    SubShader
    {
    
    
        Pass
        {
    
    
            // 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
            Tags {
    
     "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            // 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
            #include "UnityLightingCommon.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                fixed3 color : COLOR;
            };

            v2f vert (a2v v)
            {
    
    
                v2f o;
                // 坐标转换
                o.vertex = UnityObjectToClipPos(v.vertex);
                
                // 环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                /*漫反射*/
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

                /*高光反射*/
                // 函数 reflect 可直接根据入射光角度与法线向量计算出反射方向
                fixed3 reflectDir = normalize(reflect(-worldLight, worldNormal));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

                // 叠加环境光、漫反射、高光反射,作为返回结果
                o.color = ambient + diffuse + specular;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                return fixed4(i.color, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Specular"
}

Per Pixel Lighting

Shader "Chapter 6/Specular Frag Level"
{
    
    
    Properties
    {
    
    
        // 定义漫反射颜色,默认为白色
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        // 高光反射颜色
        _Specular ("Specular", Color) = (1, 1, 1, 1)
        // 用于控制高光区域大小
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }

    SubShader
    {
    
    
        Pass
        {
    
    
            // 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
            Tags {
    
     "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            // 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
            #include "UnityLightingCommon.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                fixed3 worldPos : TEXCOORD1;
            };

            v2f vert (a2v v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                // 环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                /*漫反射*/
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLight));

                /*高光反射*/
                // 函数 reflect 可直接根据入射光角度与法线向量计算出反射方向
                fixed3 reflectDir = normalize(reflect(-worldLight, i.worldNormal));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

                // 叠加环境光、漫反射、高光反射,作为返回结果
                fixed3 color = ambient + diffuse + specular;
                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Specular"
}

Pixel-by-Pixel Realization of Blinn-Phong Illumination Model

Shader "Chapter 6/Specular Blinn-Phong"
{
    
    
    Properties
    {
    
    
        // 定义漫反射颜色,默认为白色
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        // 高光反射颜色
        _Specular ("Specular", Color) = (1, 1, 1, 1)
        // 用于控制高光区域大小
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }

    SubShader
    {
    
    
        Pass
        {
    
    
            // 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
            Tags {
    
     "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            // 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
            #include "UnityLightingCommon.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                fixed3 worldPos : TEXCOORD1;
            };

            v2f vert (a2v v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                // 环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                /*漫反射*/
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLight));

                /*高光反射*/
                // 函数 reflect 可直接根据入射光角度与法线向量计算出反射方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                // Blinn-Phong 模型算法
                fixed3 halfDir = normalize(worldLight + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(halfDir, i.worldNormal)), _Gloss);

                // 叠加环境光、漫反射、高光反射,作为返回结果
                fixed3 color = ambient + diffuse + specular;
                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Specular"
}

The following is a comparison of the above three implementations (from left to right):
insert image description here

  • Processing lighting on a per-pixel basis results in smoother highlights than per-vertex
  • The specular part of the Blinn-Phong lighting model looks bigger and brighter. In actual rendering, we will choose the Blinn-Phong lighting model in most cases.

6.6 Using Unity built-in functions

See the source code of the function UnityCG.cgincfor details, which is omitted here.

Guess you like

Origin blog.csdn.net/m0_37979033/article/details/128739661