|
|
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.
|
|
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.
|
|
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 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 |
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.
|
|
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:

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:

|
|
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);
|
|
As mentioned earlier, you will program a Gouraud shader now.
|
|
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.

|
|