Shaders in Unity — Lambert and Ambient
This is the third article of the series of writing shader scripts in Unity.
From this point we are making shaders to look objects 3D because previous one is a 2D looking one.
Lambert shader is one model of representing a diffuse shader. To learn about how the shader works, we need to know how the light works.
How we can see objects?
We can see the objects due to the light emitting from the objects. But all objects doesn’t emit lights. Only the light sources like Sun, Light bulbs, and even LCD screens.
The other objects only reflects the light which hit on it. If there is no light, we cannot see any objects.
How we can see the colors on objects?
Light sources like sun emits white light. And we have colored LED bulbs and screens. So a source can have a special color for it.
Does a real world object has a color if its not a light source?
We are mostly deal with white light. Light is a electro-magnetic wave and it has a color spectrum. When the white light hit on a object, it can absorb sum colors and reflect the remaining. What we see as the color of the object is the remaining reflected color of the white light.
In the image a white color light hitting on the object and it absorbs green and blue spectrum from light and reflect red which we can see.
What happens if we hit a light with only green and blue colors to the object?
In this scenario, both colors are absorbs by the object and reflects nothing. So we will not be able to see the object.
These fundamentals about light will be really helpful with shading.
What is a Lambert Shader?
In Computer graphics, we have to create the reflecting light. Diffuse shaders are use in non metallic or in rough surfaces. Its using for non illuminating surfaces. Lambert shader defines the general color of the material when light shines on it.
Since light emits everywhere, we can see same color from every side.
In real world objects, the surface areas which falls direct light will reflect that with higher intensity. The areas does not fall direct light, will be get lower intensity. This falloff of intensity is depending on angle of light falling.
The same scenario should replicate for the diffuse shader. So we need to understand what are the things needed to compute the color for each pixel in the above sphere.
This illustrate how we can calculate the intensity on each position of sphere. For better understanding, I used a 2D graphics.
The light is falling directly on top to the sphere. So the top position should have higher intensity. It should decrease until the red line. After red line, no light is falling from the light source.
The intensity level can be calculated from the cosine of the direct light direction and normal direction.
Normal is a vector, perpendicular to the surface
Lets go to Unity and make the shader …
As in the previous article,
- Create a capsule object, material and a shader with name Lambert
- Apply shader to material
- Apply material to the Lambert object.
- If there is no Directional Light, create a directional light object
- Open the Shader in Editor.
Here is the basic snippet for the Lambert Shader
We define a Color property for apply to the object.
Tags {“LightMode” = “ForwardBase”}
This is a new statement that should add to the shader because we are started to dealing with a light source. ForwardBase is generally use in Unity for direct light source. It should change with the light source we are using.
Then we are map vertex and fragment shader function to the functions that we are going to write.
The user defined variable is declared to get the Color from the material configuration.
Unity defined variables
Unity has its own variables which will help for the rendering. The variable use in script _LightColor0 will provide the color of the directional light.
Some of the variables are commented in the script. If you are using Unity 3, these variables should define. In other version above Unity 3, it will provide by the renderer.
The input struct has the position vector as in previous FlatColor shader. But we need the normal to the vector for our calculations. We can get that value from above vertexInput struct.
In the vertexOutput vector, the transformed position is included as previous shader. Here we pass the color of the pixel which needs to applied on pixel. The color should be calculated in the vertex shader function.
To get the intensity we need to do the calculation I described earlier. We need to get the cosine of normal vector and light direction vector.
v.normal provides the normal vector in model space. We need to get it to the world space. So we multiply it with unity_WorldToObject matrix which do the transformation.
Why World To Object Matrix?
This variable put me in to big trouble. We need to convert normal in model space to world space. But this statement seems to do the reverse. To convert position vector in model space to world space, we multiply model to space transformation matrix with position vector. It will work for position vector.
The problem is this transformation matrix has a scale factor. If we multiply it with normal vector, it will change the direction of the vector. So we multiply normal vector with world to object transformation matrix. This has some serious math calculations and proofs.
Here is a link for more details — Glut
mul is doing the matrix multiplication. We cant interchange the argument passing to matrix multiplication. If we interchange it, we need use the transpose of the transform matrix.
v.normal is a float3 vector. So we convert it to float4 and do the multiplication. Again the output is converted float3 vector getting only xyz from the float4.
The result is normalize to get values between -1 to +1. In mathematics, it will give unit vector. Since normal is just a direction, the direction can be given in a unit vector.
_WorldSpaceLightPos0 provide the light direction in the world space. The light vector also normalize and extract xyz components to create a float3.
Now we have light direction and normal direction relative to the world space. Dot product give us the intensity value that we want.
This dot product can have negative values. That means the angle between normal and light direction is higher than 90 degrees. So the light cannot fall on these vertex positions. We’ll use max function to make the intensity level of vertex positions to black which does not fall light.
atten variable provides strength of light that fall on the surface. _LightColor0 will give the color of the direction light. _Color is the user defined color for the object. We can get the diffused reflection color by multiplying attenuation, color of the direct light, color of the object and dot product of normal direction and light direction.
The diffuseReflection as color and transformed position is assigned to the outputVertex struct.
See the Flat Color shader article for transformed position
Finally we are assigning color defined in the vertexOutput to the pixel.
Lets see how it looks like. Change the color of the capsule from Material object.
The surface area where light not falls totally in black color. We can fix that with an Ambient Shader.
The final script for Lambert shader should look like this.
Ambient Shader
Ambient shader can be created by doing a small change to the Lambert shader.
_Color variable, the object color removed from the multiplication. It will give diffuse reflection with light source color. Then environment is added to the diffuseReflection. Environment light is given by UNITY_LIGHTMODEL_AMBIENT in Unity.
This will provide us the light level that should apply to the object with light color. We assign product of _Color with lightFinal to the vertexOutput color.
The complete script should look like this.
Final capsules are below :)
Next we’ll create a Specular shader
For more details refer
https://www.youtube.com/watch?v=zJxxXjoZE30&list=PLV4HCa5XqFT02gZOZ_Jb_A66wqDhZMCkN