Lesson 3: Gouraud Shading

This lesson deals with a simple yet effective lighting method that is used in 3D environments called Gouraud Shading. We will explain the math this technique is based on (don't worry, we'll keep it short :) and demonstrate, in brief, how OpenGL materials are used in OpenGL shaders. Eventually, you will implement Gouraud Shading in a vertex shader.


 1. Gouraud Shading

Gouraud Shading is a special way to render 3D objects. Gouraud Shading only calculates the color of each vertex. This color depends on various parameters, e.g. the material and the light position. After the verticies are projected, each fragment is colored. Their color is detemined by interpolating the colors of the polygon's vertices. OpenGL supports this method within its fixed functionality. It can be switched on by setting glShadeModel to GL_SMOOTH. However, we are going to implement this on our own in the course of this lesson.


 Important GLSL variables

The color that an object seems to have for a spectator, is influenced by a lot of factors. In order to keep things simple, we are going to contrain ourselves to just a few factors:

Factor GLSL equivalent
 The color of the object  gl_FrontMaterial.diffuse
 The color of the light  gl_LightSource[0].diffuse
 The position of the light  gl_LightSource[0].position
Table #1: Factors of color calculation and their GLSL equivalents


Table 1 shows that one can access the current material in a GLSL shader with the predefined uniform variable, gl_FrontMaterial. To be exact, it contains the material that is on the front of the faces, the material that covers the back faces can be accessed with gl_BackMaterial. As we are going to render front faces only, we can ignore this for now.

By using these material variables, we can access all of the properties a OpenGL material has:

Name  Datatype  Explanation
 gl_FrontMaterial.diffuse  vec4  The part of diffuse light that is reflected by the material
 gl_FrontMaterial.ambient  vec4  The part of ambient light that is reflected by the material 
 gl_FrontMaterial.specular  vec4  The color of the specular highlights of the material
 gl_FrontMaterial.emission  vec4  The light that is emitted by the material
 gl_FrontMaterial.shininess  float  Determines the shininess of the material
Table #2: The elements of gl_FrontMaterial and their explanation

All light sources can be accessed through the predefined array, gl_LightSource[]. In addition to diffuse, that was mentioned earlier, every property of the OpenGL light sources are accessible.

This information is here for your reference only, in this lesson we will only use the factors that are listed in Table 1.


 The math of light

To determine the color of a plane that is illuminated by a light source, we assume that it is proportional to the diffuse component of the material and the diffuse component of the light. Furthermore, the intensity is proportional to the angle between the light vector and the normal of the plane. Using this information yields the following formula:


Formula #1: Calculating the diffuse part of the light

This calculation has to be performed for every vertex in a Gouraud Shader. Determining the first two components is trivial: Ld is gl_LightSource[0].diffuse and Md is gl_FrontMaterial.diffuse.

To get the third component, one has to calculate the cosine of the angle between the light vector and the normal of the vertex. Fortunately, OpenGL helps us a lot, because it allows us to acces the normal in a shader by means of a variable (gl_Normal in vertex shaders). We can determine the angle by calculating the dot product between the normal vector and the light vector. For this calculation to be correct, both vectors are required to have a length of 1. Another term for this is normalized. In GLSL, the function that calculates the dot product is called dot(vec1, vec2).

The figure below illustrates the described procedure:


Figure #1: Illustration of the dot product

 The gl_NormalMatrix

A few things have to be considered regarding this operation. The normal vector and light vector have to be in the same coordinate system, otherwise the dot product will produce unusuable results. The light vector is in eye space, that is the coordinate system that views the scene from the cameras point of view. However, the normals are in the local coordinate system of their model. This means we have to transform one vector in the coordinate system of the other. It would be wasteful to transform the light vector into all kinds of local model coordinate systems, so we opt to transform the normals. A shader has to do everything on its own, thats why we also have to take care of this.

One could think that the solution is to transform the normal with gl_ModelViewMatrix. After all, this is the matrix that transforms the models vertices into eye space. Unfortunately, this cannot work, as the normal has three components and the gl_ModelViewMatrix is a 4x4 matrix. Reducing the matrix to a 3x3 matrix is a solution that works in most cases but fails in a few (due to reasons we will not explain here). To solve this dilemma, GLSL comes to save the day once again and presents us with the gl_NormalMatrix, a 3x3 matrix that transforms a normal into eye space. We have to normalize the result of this transformation, as it can yield a vector that is not normalized. The GLSL function for that is normalize(). The complete code to transform the normal is the following:


	vec3 Normal = normalize(gl_NormalMatrix * gl_Normal);


 2. The shader

As mentioned earlier, you will program a Gouraud shader now.


 Task

The described Gouraud shading technique is to be translated into a shader. You only have to consider the diffuse components of the light and material. This implies that the vertex shader has to calculate a color value in addition to the transformation we learned about in lesson 2.

The pixel shader is already complete, you don't have to change anything. However a little peek does no harm. It just passes on the color that was calculated by the vertex shader. The interpolation needed is performed automatically by the hardware.


Figure #2: A possible output of the Gouraud Shader


 Tips