Shaders in Unity — Specular

Deshan Kalupahana
5 min readJun 28, 2019

--

Shaders give life to a material. In previous articles, we have talked about Flat Color and Diffuse shaders.

  1. Shaders in Unity — Flat Color
  2. Shaders in Unity — Lambert and Ambient

Diffuse shader describes how light reflects on rough surfaces. Specular shader describes how light reflects on shiny surfaces. In 3D computer graphics Phong reflection model used for basic material shading. Its a combination of ambient, diffuse and specular reflections.

Visual illustration of the Phong equation: here the light is white, the ambient and diffuse colors are both blue, and the specular color is white, reflecting a small part of the light hitting the surface, but only in very narrow highlights. The intensity of the diffuse component varies with the direction of the surface, and the ambient component is uniform (independent of direction). — link

It is similar to the Ambient shader written in the previous article. In there, we added ambient light to calculated diffuse level to give a color for the shader.

Ambient shader color = materialColor*(diffuceColor+ambientLight)

For the Phong Reflection model, the specular highlights needed to be calculated to integrate with the above equation.

For the calculations in the diffuse shader, we didn’t care about the angle of view. But the shiny spot on the object changes with the camera angle. So we are dealing with attributes of the camera in the specular shader.

Reality behind Specular Highlights

In Diffuse reflection, the light falls on a surface reflects to every direction. But in specular reflection, we consider surface like a mirror.

The light coming from source A reflects with the same angle to direction B. The angle of incident and angle of reflection is equal.

If the object is not flat as we see in this figure, we are not able to see all the reflected light rays. That is why we can see only some areas of the objects are shining.

The figure shows how light reflects on a spherical surface. The light ray L1 hit directly on to the surface with a angle of incident 0. So light reflects on the same direction.

The light reflects to a direction where the camera cannot see. So specular highlights of this area are hidden to the camera even if it has the brightest illumination.

The light ray L2 hit with some angle on to the surface and reflects with the same angle. This ray can be seen with the camera. But not the full amount of reflected light. Projected reflected ray on camera direction gives the amount of light that the camera can see. So its given by the dot product of R2 and Direction of camera.

specular reflection = dot(R2, Cd)

Cd is direction of camera.

So lets see how to implement it

Until now, we did the calculations in the Vertex shader. But for the Phong reflection model, we are doing most of the calculations in the Fragment shader. The best output can get from it. For more information about it, read my article about point lights.

Let’s start with creating the basic capsule, material and shader for model. The detailed description about how to create these things are in Flat Color shader article.

  1. Create a capsule object, material and a shader with name Specular
  2. Apply shader to material
  3. Apply material to the Specular object.
  4. If there is no Directional Light, create a directional light object
  5. Open the Shader in Editor.

Here is the basic snipped for the Specular shader.

We are passing thee parameters for the shader.

  1. Color of the material — _Color
  2. Specular Color -_SpecColor
  3. Shininess — _Shininess

The specular color can give realistic effects for materials like metals. Shininess is a power factor to say how much light we need to the shader.

The LightMode is ForwardBase because we are dealing with only directional light at the moment. _LightColor0 , unity defined variable used to get the color of the directional light.

We need the position vector of the vertex and its normal. This is defined in the vertexInput . The values are assigned to corresponding variables by the renderer.

vertexOutput struct has clipped position vector of the vertex. The pos isreferenced to SV_POSITION, should always in the vertexOutput. Then the vertex relative to the world space (posWorld) and normal direction relative to the world (normalDir) is passed with vertexOutput to the Fragment shader. We are writing these values to place where texture channels should be written. Its not a big deal, because its also a memory which can store float4 values.

We can output to only few positions in Unity as NORMAL, SV_POSITON, COLOR etc. Refer this wiki for more details.

We are not doing much calculations in vertex shader. As mentioned earlier, clipped position is assigned to the o.pos. o.posWorld and o.normalDir contains the vertex relative to world space and normal relative to world space respectively.

Fragment shader

We start with what we have done in Diffuse shader.

View Shaders in Unity — Lambert and Ambient for how Diffuse shader is implemented

Now we need to calculate the specular highlights and add it to lightFinal. For that first, we need the light reflected direction. We have the light direction (lightDirection) from the above snippet. Multiplying it by negative one (-1) gives the opposite direction of light direction vector. So its the reflected direction if the light hit directly on the surface with incident angle zero. But we need to deal with the incident angle. The CG has a separate function to calculate the reflected direction vector when direction vector and normal vector is given.

Next thing is to calculate how much reflected light can the camera see. The view direction should calculate for that. Camera position is in _WorldSpaceCameraPos unity defined variable. The view direction is given by the difference between camera position and vertex position. The result should normalize, because its a direction.

How much reflected light can be seen by the camera given by dot product of light reflected direction and the view direction. max function removes the light which reflects to other side.

We receive a shininess value from material. This helps control how is the intensity of light should reflect or how much highlight should spread.

Get the specular highlight color with fixed attenuation value for now.

The fragment shader function should look like this.

The steps written in code are extended with variables for better understanding. These steps can be written in one line of code. The full code for the shader is here.

--

--

No responses yet