Shaders in Unity are not hard. Part 3 – adding shine


Let’s understand shaders

Hello everyone! I am grateful to everyone for the remarks and comments on the previous articles. Thanks to all of us, we are filling the Internet with accessible knowledge and this is really cool.
Today we continue to deal with shaders, namely working with lighting. In the last part, we discussed the type of Lambert lighting. Today we will add highlights to our shader. The result of the work will be a shader that implements Phong lighting.

This time the article came out short, since most of the material will be taken from the previous part, so if you have not read it, I advise you to read it. Well, let’s get started.

Theory

Let’s take a look at how Phong lighting works. First of all, it must be said that in Unity the Phong lighting model is achieved by a combination of three components – background lighting (ambient), diffuse lighting (diffuse) and glossy lighting (specular). Typically, these shaders are called ADS – Ambient Diffuse Specular.

So, the main difference from the diffuse shader is the presence of glossy lighting or glossy sheen. Glossy highlights are required to create a realistic display of some objects. If the direction of the light source was important to us for the diffuse shader, then the direction of the camera will be important to us for the specular shader. By combining these two vectors, we can achieve a beautiful realistic picture.

Combination of Phong lighting components
Combination of Phong lighting components

The implementation of gloss in the Phong lighting model is one of the simplest and low-performance gloss implementations. The idea is to calculate the direction of the reflected light based on the direction of the camera. This lighting model has earned great popularity due to its ease of implementation and is widely used in both the gaming and film industries. Ease of implementation is about minimization and simplicity of computation. Of the disadvantages of the implementation, it is worth noting the most realistic flare effect, but, as a first approximation, it is quite suitable.

With diffuse reflection, light falling on an object is reflected evenly in all directions. When creating a glossy flare, the surface of the object must be viewed as a mirror – that is, the angle of incidence of the light and the angle of reflection will be the same.

Types of reflected light
Types of reflected light

The Phong Lighting Model is a local lighting model that takes only light sources and point material parameters into account. All other effects, such as scattering, reflection from neighboring objects, lensing and others, are not taken into account. Due to such ignoring and rough approximation of some effects, high performance of this type of lighting is achieved.

To calculate the Phong lighting model, it is necessary to calculate the intensity of the three constituent lighting components – ambient, diffuse, and specular.

Calculations are made according to the following formula:

PhongLight = AmbientLight + DiffuseLight + SpecularLight;

We know from past parts that:

Thus, all that remains is to calculate only the glossy lighting component, which will be calculated using the following formula:

SpecularLight = RdotV^SpecularIntensity * LightColor * NdotL * SpecularColor;

  • RdotV is the dot product of the reflection vector and the direction of the camera.

  • NdotL – dot product of the normal vector and the direction of illumination.

  • SpecularIntensity – the intensity of the flare.

  • LightColor – the color of the lighting.

  • SpecularColor – color of the specular highlight.

Practice

Well, having dealt with the glare, let’s start changing the shader.

As already mentioned, we will use the shader from the previous part, expanding and complementing it.

Complete shader code
Shader "Chernov/Phong"
    {
    Properties
    {
        _MainTex ("Main Texture", 2D) = "white" {}
 
        [Header(Ambient)]
        _Ambient ("Intensity", Range(0., 1.)) = 0.1
        _AmbColor ("Color", color) = (1., 1., 1., 1.)
 
        [Header(Diffuse)]
        _Diffuse ("Val", Range(0., 1.)) = 1.
        _DifColor ("Color", color) = (1., 1., 1., 1.)

        [Header(Specular)]
        _SpecIntensity ("Shininess", Range(0.1, 10)) = 1.
        _SpecColor ("Specular color", color) = (1., 1., 1., 1.)
     }
 
    SubShader
    {
        Pass
        {
            Tags { "RenderType"="Transparent" "Queue"="Geometry" "LightMode"="ForwardBase" }
 
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
  
            #include "UnityCG.cginc"
 
            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                fixed4 light : COLOR0;
            };
 
            fixed4 _LightColor0;
            fixed _Diffuse;
            fixed4 _DifColor;
            fixed _Ambient;
            fixed4 _AmbColor;
            fixed _SpecIntensity;
            fixed4 _SpecColor;
 
            v2f vert(appdata_base v)
            {
                v2f o;
 
                // Clip position
                o.pos = UnityObjectToClipPos(v.vertex);
 
                // Light direction
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
 
                // Normal in WorldSpace
                float3 worldNormal = UnityObjectToWorldNormal(v.normal.xyz);
 
                 // World position
                float4 worldPos = mul(unity_ObjectToWorld, v.vertex);

                // Camera direction
                float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos.xyz));
 
                // Compute ambient lighting
                fixed4 amb = _Ambient * _AmbColor;
 
                // Compute the diffuse lighting
                fixed4 lightTemp = max(0., dot(worldNormal, lightDir) * _LightColor0);
                fixed4 diffuse = lightTemp * _Diffuse * _LightColor0 * _DifColor;
 
                 // Compute the specular lighting
                float3 refl = reflect(-lightDir, worldNormal);
                float RdotV = max(0., dot(refl, viewDir));
                fixed4 spec = pow(RdotV, _SpecIntensity) * _LightColor0 * ceil(lightTemp) * _SpecColor;
 
                o.light = diffuse + amb + spec;
               
                o.uv = v.texcoord;
                return o;
            }
 
            sampler2D _MainTex;

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 c = tex2D(_MainTex, i.uv);
                c.rgb *= i.light;
                return c;
            }
 
            ENDCG
        }
    }
}

So, most of the shader is already known to us from the first parts. Let’s take a look at the new code only.

To add highlights, let’s add two new variables. The first – will be responsible for the intensity of the flare, the second – for the color.

  • _SpecIntensity ("Shininess", Range(0.1, 10)) = 1. – glare intensity (in the range of 0.1 – 10);

  • _SpecColor ("Specular color", color) = (1., 1., 1., 1.) – flare color (default – white);

Also, let’s not forget to add variables to the variables section inside CGPROGRAM.

fixed _SpecIntensity;
fixed4 _SpecColor;

Now let’s add the lens flare computation to the vertex shader. Let’s change the function as follows:

// Compute the specular lighting
float3 refl = reflect(-lightDir, worldNormal);
float RdotV = max(0., dot(refl, viewDir));
fixed4 spec = pow(RdotV, _SpecIntensity) * _LightColor0 * ceil(lightTemp) * _SpecColor;
 
o.light = diffuse + amb + spec;

So, after the block for calculating diffuse lighting, we will add a block for calculating highlights. The resulting light will be calculated by adding three lighting components.

Let’s take a closer look at what we have added: float3 refl = reflect(-lightDir, worldNormal); – calculate the vector of the reflected light. reflect() – built-in HLSL function, returns the reflection vector.

float RdotV = max(0., dot(refl, viewDir)); is the dot product of the reflection vectors and the direction of the camera. Function max() returns the larger of the values.

fixed4 spec = pow(RdotV, _SpecIntensity) * _LightColor0 * ceil(lightTemp) * _SpecColor; – calculation of the flare according to the formula. ceil(lightTemp) – the scalar product of the normal vector and the direction of illumination is necessary for rounding.

o.light = diffuse + amb + spec; – addition of three lighting components.

That’s all the changes. So by adding just a couple of lines of code, you can add highlights to an existing shader.

Two balls: on one diffuse shader from the previous chapter, on the second updated with highlights.
Two balls: on one diffuse shader from the previous chapter, on the second updated with highlights.
Editing Material Properties - Flare Intensity and Color
Editing Material Properties – Flare Intensity and Color

That’s all. This is how you can write a beautiful and interesting shader in a couple of lines of code. The following articles are on the way. As usual, I read all your comments, I am glad for objective criticism and comments.

Alexey Chernov

Team Lead at Program-Ace

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *