Lab 3: Virtual world and specular shading

Goal: In this lab, you will expand your view to a richer virtual world, including herarcical models and a skybox. You will also implement the Phong lighting model.

This lab can be pretty demanding, parts 3 and 4 in particular.

Lab files:


Old version:

You will need the various helper files from previous labs, as well as using your results as starting points.

If you run into problems, there are several resources. You have your textbook. OpenGL Reference Pages describe all the OpenGL API functions. OpenGL Shading Language Specification (found via, "OpenGL Shading Language Specification") describes GLSL.

1) Hierarcical modelling, the windmill

Among the files, there is a windmill in two parts, the mill and the wings. Build a working windmill from the parts. Four wings should be placed at the appropriate place and rotate around it. Create appropriate rotation and translation matrices to make suitable model-to-world transformations.


2) Manual viewing controls

The "look-at" function is useful for more than placing the camera in some fixed place. You can use glutPassiveMotionFunc to write a control based on mouse movements. The function glutPassiveMotionFunc() takes a callback argument, a pointer to a function that accepts the x and y coordinates of the mouse as parameters. For keyboard controls, there are GLUT functions for accepting keydown and keyup events, but for many purposes you are better off with knowing what keys are presently down. That is provided in the utility module. This overrides the key-up and key-down callbacks so you can not use them. You initializes this with initKeymapManager(), and after that you can check any key with keyIsDown(), e.g. if (keyIsDown('a')) { (something happens) }.


3) Virtual world and skybox

Using the manual controls above, you should expand your virtual universe to a simple "virtual world" with a set of basic features.

Your program is growing now. You may want to look into ways to structure it a bit. There are many ways to do that.


4) Specular shading, external light sources

Now you have a nice scene but you need better light. Implement specular Phong shading in your shaders. Not only should it include a specular component, but it should also do that using light sources that are specified by the CPU. (This is a challenging task, but quite rewarding.)

Here are specifications of four light sources:

Point3D lightSourcesColorsArr[] = { {1.0f, 0.0f, 0.0f}, // Red light
                                 {0.0f, 1.0f, 0.0f}, // Green light
                                 {0.0f, 0.0f, 1.0f}, // Blue light
                                 {1.0f, 1.0f, 1.0f} }; // White light

GLfloat specularExponent[] = {10.0, 20.0, 60.0, 5.0};
GLint isDirectional[] = {0,0,1,1};

Point3D lightSourcesDirectionsPositions[] = { {10.0f, 5.0f, 0.0f}, // Red light, positional
                                       {0.0f, 5.0f, 10.0f}, // Green light, positional
                                       {-1.0f, 0.0f, 0.0f}, // Blue light along X
                                       {0.0f, 0.0f, -1.0f} }; // White light along Z

Upload to shader:

glUniform3fv(glGetUniformLocation(program, "lightSourcesDirPosArr"), 4, &lightSourcesDirectionsPositions[0].x);
glUniform3fv(glGetUniformLocation(program, "lightSourcesColorArr"), 4, &lightSourcesColorsArr[0].x);
glUniform1fv(glGetUniformLocation(program, "specularExponent"), 4, specularExponent);
glUniform1iv(glGetUniformLocation(program, "isDirectional"), 4, isDirectional);

Thus, I upload as arrays of three-component vectors and arrays of scalars. Declarations in shader:

uniform vec3 lightSourcesDirPosArr[4];
uniform vec3 lightSourcesColorArr[4];
uniform float specularExponent[4];
uniform bool isDirectional[4];

Debugging light like this requires some care. Take special care in keeping track on what coordinate system you work in. Model data starts in model coordinates, light sources are given in world coordinates. All lighting calculations take place in any coordinate system, model, world or view (camera) coordinates (not projected though) but you must decide which one. Select one and stick to it. I recommend that you use view coordinates. Make sure normal vectors, light direction and viewing direction are all given in the same coordinate system.

The viewing direction requires that you create a vector from the surface to the camera. For that, you need the position of the surface. You get that by interpolating the vertex positions using varying ("out" in vertex, "in" in fragment) variables.

Start out working on a single light source, diffuse component only. When that works, switch to the specular component. Once that works for one positional and one directional light source, chances are good that everything works. Working on one at a time will help you not to get distracted by too much information.


5) Multitexturing

Using multiple textures on a model can be very useful for many purposes. The difference from the texture mapping introduced in lab 2 is that you must bind textures to specific texture units:
glBindTexture(GL_TEXTURE_2D, textureId);

This enables the textures as texture unit 0.

If the texture is called "tex" in your shader, you can pass the texture unit to that variable like this: Go back to init() again. After the shaderTimeLocation has been located, perform the following operation:

  glUniform1i(getUniformLocation(shaderProgram, "tex"), 0);
Note the "0". That is, again, the texture unit number, here sent to the "tex" variable in the shader.

Load two textures and apply them to an object. Build from your specular shader, and combine lighting and texture.


Extra) Managing transparency

In your virtual world, make at least two object semi-transparent. Move around. You should be able to find locations where the transparency (combined with Z buffering) causes problems. Solve these problems.


That concludes lab 3. Good work! In the next lab, we will make the ground more interesting, a 3D terrain.