Laboration 3

Virtual world and specular shading

Goal: In this lab, you will expand your view to a richer virtual world, including hierarcical 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:


You will need the "common" 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 (1.50, 4.50) describes GLSL.

1) Hierarcical modelling, the windmill

Among the files, there is a windmill in four parts, three making up the mill’s body (walls, roof, balcony), and one wing. 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.

Hint in case you have trouble: You may have to tweak some numbers to make the placement right. How can you do that without making a lot of small changes in numbers in the code followed by recompilations?


How can you get all four blades to rotate with just one time-dependent rotation matrix?

How do you make the wings follow the body's movements?

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 old 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 by the function glutKeyIsDown, e.g. if (glutKeyIsDown('a')) { (something happens) }.


What kind of control did you implement?

Can you make this kind of control in some other way than manipulating a "look-at" matrix?

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.

Add a "ground" as a fairly large textured polygon.

The manual controls should allow you to move around in the "world"

Several objects should be included, most of them stationary.

Finally, add a "skybox". For this purpose, a skybox model and texture are provided. To implement a skybox, the skybox should follow the camera and seem to be drawn at the back. To do this, you should draw the skybox first, with Z-buffer turned off (glDisable(GL_DEPTH_TEST)) . The skybox should be rotated as the camera, but not translated. You can do this with a copy of the camera matrix where you zero out the translation component. Culling needs to be off; you always want to draw the skybox (and the model is not designed for culling). Finally, don't forget to turn the Z-buffer on again after drawing the skybox.

Note! Your skybox may not look 100% perfect with the model that comes with the lab. Pretty good, but it has a (somewhat minor) problem, when working with a flat floor like we do here. What would you do to fix this?

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


How did you handle the camera matrix for the skybox?

How did you represent the objects? Is this a good way to manage a scene or would you do it differently for a "real" application?

What special considerations are needed when rendering a skybox?

What is the problem with the skybox object used in the lab? (The problem doesn't have to be corrected.)

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

The scene should include one model in the center, and must have a flat floor, both lit. (This is important since it will clearly reveal if the local light sources are correct.) We also recommend to include models in the scene that are round, like the Utah Teapot, since the light on such models is relatively easy to evaluate.

Here are specifications of four light sources:

vec3 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

GLint isDirectional[] = {0,0,1,1};

vec3 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

and the specular exponent (per object):

GLfloat specularExponent[] = {100.0, 200.0, 60.0, 50.0, 300.0, 150.0};

Upload to shader:

glUniform3fv(glGetUniformLocation(program, "lightSourcesDirPosArr"), 4, &lightSourcesDirectionsPositions[0].x);

glUniform3fv(glGetUniformLocation(program, "lightSourcesColorArr"), 4, &lightSourcesColorsArr[0].x);

glUniform1f(glGetUniformLocation(program, "specularExponent"), specularExponent[i]);

glUniform1iv(glGetUniformLocation(program, "isDirectional"), 4, isDirectional);

Thus, I upload as arrays of three-component vectors, one array of booleans and one item form an array of scalars. Declarations in shader:

uniform vec3 lightSourcesDirPosArr[4];

uniform vec3 lightSourcesColorArr[4];

uniform float specularExponent;

uniform bool isDirectional[4];

Note that the specularExponent array is one value for each object, not light sources.

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.

Normalize what needs to be normalized. Remember that a varying vector doesn’t stay normalized under interpolation.

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.


How do you generate a vector from the surface to the eye?

Which vectors need renormalization in the fragment shader?

Extra) Managing transparency

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


How did you remove the errors caused by transparency?

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

This page is maintained by Ingemar Ragnemalm