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


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


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?

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?

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

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.


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

Which vectors need renormalization in the fragment shader?

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.


How did you choose to combine the texture colour and the lighting colour?

How did you choose to combine the two textures?

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.


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