Shader programming—Ambient and specular light

In this step, we will add the ambient and specular light to our script as well as create our custom lighting models.

Note

The custom lighting model is basically the function that will be used to calculate our surface shader, which is the output of (surf() function) interaction with the lights.

surf() function is the function that will take any UVs or data we need as input, and fill in the output structure SurfaceOutput (the predefined structure, such as Albedo, Normal, Emission, Specular, Gloss, and Alpha).

Engage Thrusters

  1. Go to MonoDevelop, open MyShader.shader file, and go to the Properties section and add the highlighted script as follows:
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _BumpMap ("Bumpmap", 2D) = "bump" {}
        _AmbientColor ("Ambient Color", Color) = (0.1, 0.1, 0.1, 1.0)
        _SpecularColor ("Specular Color", Color) = (0.12, 0.31, 0.47, 1.0)
        _Glossiness ("Gloss", Range(1.0,512.0)) = 80.0
      }
  2. Next, go to the SubShader section, modify, and add the following highlighted code:
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 400
        CGPROGRAM
        // Custom lighting function that uses a texture ramp based on angle between light direction and normal
        #pragma surface surf RampSpecular
        sampler2D _MainTex;
        sampler2D _BumpMap;
        
        fixed4 _AmbientColor;
        fixed4 _SpecularColor;
        half _Glossiness;
    
       struct Input {
          float2 uv_MainTex;
          float2 uv_BumpMap;
        };
  3. We set LOD to 400, and set #pragma surface surf to RampSpecular instead of Lambert, and get the other three properties for Ambient and Specular light. Now, we will need the custom lighting models function. Let's add the following highlighted code under the surf() function:
        void surf (Input IN, inout SurfaceOutput o) {
          fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
          o.Albedo = c.rgb;
          o.Alpha = c.a;
          o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
        }
    
       inline fixed4 LightingRampSpecular (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten) {
          //Ambient Light
          fixed3 ambient = s.Albedo * _AmbientColor.rgb;
    
          //Diffuse
          fixed NdotL = saturate(dot (s.Normal, lightDir)); //Get the direction of the light source related to the normal of character
          fixed3 diffuse = s.Albedo * _LightColor0.rgb * NdotL;
    
            //Specular - Gloss
          fixed3 h = normalize (lightDir + viewDir); // Get the Normalize of the lighting direction and view direction
                float nh = saturate(dot (s.Normal, h)); //Make sure that the return number isn't lower than 0 or greater than 1
                float specPower = pow (nh, _Glossiness);
    
                fixed3 specular = _LightColor0.rgb * specPower * _SpecularColor.rgb;
    
            //Result
            fixed4 c;
            c.rgb = (ambient + diffuse + specular) * (atten * 2);
            c.a = s.Alpha + (_LightColor0.a * _SpecularColor.a * specPower  * atten);
    
            return c;
        }
    
        ENDCG
      }

We have finished this step. We can now go back to Unity, and click Play to see our result with the specular reflection, as shown in the following screenshot:

Engage Thrusters

Objective Complete - Mini Debriefing

In this step, we first added the new properties _AmbientColor, e, and _Glossiness, which will be used to calculate in our custom lighting models function to get the specular reflection.

Next, we increased the LOD to 400 because we wanted to increase the Level of Detail for our custom lighting model that will calculate the specular lighting. Then we changed #pragma surface surf from Lambert to RampSpecular, which means that we changed our lighting calculated from the Lambert built-in to RampSpecular (our custom lighting function, LightingRampSpecular).

In the surf() function, we have changed the first line from half4 c = tex2D (_MainTex, IN.uv_MainTex); to fixed4 c = tex2D (_MainTex, IN.uv_MainTex); to increase the performance of our shader. Also, since the return value from the tex2D() function is the color value (R,G,B,A), which has a range from 0 to 1, it will be expensive to use half or float.

Tip

What are half and fixed parameters for? When we are writing a shader in Cg/HLSL, there are three types of the parameter that we can use, which are fixed, half, and float. These parameters determine the precision of computations. The parameter fixed is low precision (11 bits, the range of -2.0 to +2.0 and 1/256th precision), half is medium precision (16 bits, the range of -60000 to +60000 and 3.3 decimal digits of precision), and float is high precision (32 bits, similar to the float in regular programming language).

Reference from:

http://unity3d.com/support/documentation/Components/SL-ShaderPerformance.html

However, it follows a trend wherein the more precision we have, the more calculation we need. If we use all float for our shader, it will cause the game to slow down. So, if we want to improve the performance of our game, we should use the lowest precision as possible.

Then, we created our custom lighting function, which is inline half4 LightingRampSpecular (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten). This function passes four parameters, SurfaceOutput, light Direction, view direction, and light attenuation that we will use to calculate the output for our shader.

Note

Why is the name of this function not RampSpecular? First, we call this function by using #pragma surface surf RampSpecular, but to have this function working properly, we need to add Lighting in front of the name of our custom lighting function, so that Unity will know that this function is a custom lighting function. This is the way that the surface shaders are set up in Unity. You can find out more details from the following website:

http://unity3d.com/support/documentation/Components/SL-SurfaceShaderLighting.html

In this function, we first get the ambient color value by getting s.Albedo, which is the parameter from the surf() function o.Albedo, and then multiply the s.Albedo by _AmbientColor.rgb, where _AmbientColor is the color information from the Properties section at the beginning of our code.

Note

The fixed, half, and float parameters in Cg/HLSL can contain one, two, three, or four values of floating number such as 1.0, (1.0, 1.0), (1.0, 1.0, 1.0), or (1.0, 1.0, 1.0, 1.0) by calling it fixed, fixed2, fixed3, fixed4, half, half2, half3, half4, float, float2, float3, float4. We can also access the value in these parameters by using (x, y, z, w) or (r, g, b, a). For example, if you have fixed4 color = (1.0, 0.5, 0.3, 0.8); and you want to create another parameter, which will contain only three values (1.0, 0.5, 0.3) from the fixed4 color, you can do it like this: fixed3 newColor = color.rgb;. However, if we want the newColor value equal to (0.5, 1.0, 0.3), you can do it like this: fixed3 newColor = color.grb;.

Then, we calculate the diffuse color by getting the dot product of the surface normal of the object (s.Normal) that we pass out from the surf() function (o.Normal), and the light direction (fixed NdotL = dot (s.Normal, lightDir);). Then, we use that value to multiply with the object diffuse texture (s.Albedo) and light color (_LightColor0.rgb), which is similar to the Lambert model.

Next, we calculate the specular color by first getting the normalize vector of light direction and view direction (fixed3 h = normalize (lightDir + viewDir);). In float nh = saturate(dot (s.Normal, h));, we calculate the dot product of the surface normal and normalize vector, and make sure that the return number isn't greater than 1 or lower than 0 by using saturate(). Then, we use nh to calculate the specular power by powering it with the _Glossiness properties (float specPower = pow (nh, _Glossiness);), and we get the specular color from multiplying the light color, specular power, and the specular color properties (_LightColor0.rgb * specPower * _SpecularColor.rgb;), which is similar to the Blinn-Phong model.

In the last step, we add ambient, diffuse, and specular together, and multiply the lighting attenuation value doubled, to get the smooth specular effect (c.rgb = (ambient + diffuse + specular) * (atten * 2);).

Note

A major part of the code is in the Cg/HLSL language, so you might not be familiar with it. However, you can still get an idea of how it works by trying to see more examples and taking a look at the Cg/HLSL language:

http://http.developer.nvidia.com/CgTutorial/cg_tutorial_appendix_e.html

We can also see an example of the custom lighting model from the following Unity website:

http://unity3d.com/support/documentation/Components/SL-SurfaceShaderExamples.html

Classified Intel

How exactly do the surface shaders work?

First, we get the parameters from the Input struct , and these parameters will get passed to the SurfaceOutput struct inside the surf function. Then, the return of the SurfaceOutput struct will go to the lighting model function to calculate both the vertex and pixel (fragment) shader. Lastly, the result from the lighting model function will be passed to the frame buffer, as shown in the following diagram:

Classified Intel
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.147.77