Laboration 3 (TSBK07 version)

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, again models and textures:

News for 2025:

Future plan: Remove the lab skybox. We will work directly from a more standard skybox, the “full skybox".

News for 2024 (april): Uploaded some extra skyboxes, fixed for avoiding bleeding in edges. (These will go into the lab material in the future.)

new-skyboxes.zip

News for 2023:

New “full” skybox files. All skybox files were missing in original lab upload, please update!

The current version is “v3”.

NEWS for 2022:

• Better windmill walls. The old version had some bad normal vectors.

• New models (in separate folder): Corona, Godzilla, octopus, Tinkercar.

• Part 4 is revised to give a more detailed guide.

• Added code for making the ground (part 3) to save some time.

See lab 1 for lab files!

Old files (2022):

lab3.tar.gz

Older lab files (2021 version):

lab3.tar.gz

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?

Questions:

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. Write controls for moving the camera. You can use keyboard only, or mouse and keyboard.

If you want to use the mouse, 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, we recommend the function glutKeyIsDown, e.g. if (glutKeyIsDown('a')) { (something happens) }.

Questions:

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. You can do that with a few arrays and using 

(C syntax:)

#define kGroundSize 100.0f
vec3 vertices[] =
{
 { -kGroundSize,0.0f,-kGroundSize},
 {-kGroundSize,0.0f,kGroundSize},
 {kGroundSize,-0.0f,-kGroundSize},
 {kGroundSize,-0.0f,kGroundSize}
};

vec3 vertex_normals[] =
{
  {0.0f,1.0f,0.0f},
  {0.0f,1.0f,0.0f},
  {0.0f,1.0f,0.0f},
  {0.0f,1.0f,0.0f}
};

vec2 tex_coords[] =
{
  {0.0f,0.0f},
  {0.0f,20.0f},
  {20.0f,0.0f}, 
  {20.0f,20.0f}
};
GLuint indices[] =
{
  0, 1, 2, 1, 3, 2
};

C++ syntax:

vec3 vertices[] =
{
 vec3(-kGroundSize,0.0f,-kGroundSize),
 vec3(-kGroundSize,0.0f,kGroundSize),
 vec3(kGroundSize,-0.0f,-kGroundSize),
 vec3(kGroundSize,-0.0f,kGroundSize)
};

vec3 vertex_normals[] =
{
  vec3(0.0f,1.0f,0.0f),
  vec3(0.0f,1.0f,0.0f),
  vec3(0.0f,1.0f,0.0f),
  vec3(0.0f,1.0f,0.0f)
};

vec2 tex_coords[] =
{
  vec2(0.0f,0.0f),
  vec2(0.0f,20.0f),
  vec2(20.0f,0.0f), 
  vec2(20.0f,20.0f)
};
GLuint indices[] =
{
  0, 1, 2, 1, 3, 2
};

and pass this data to LoadDataToModel (in LittleOBJLoader).

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 (skybox.obj) and skybox textures (e.g. Daylight-Box.tga) are provided in the “skybox” folder. 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.

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

Questions:

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?

4) Specular shading, external light sources

Now you have a nice scene but you need better light. Implement Phong shading in your shaders (moving light calculations to fragment) and also implement the Phong model for a specular component. 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 I find it quite rewarding.) We are omitting some parts, like most materials parameters, light attenuation and make no attempts to do any shadow effects.

This part has been revised for 2022. My goal was to make it easier, by dividing it is several steps, illustrating intermediate results, and using a precisely defined scene. Feedback is welcome!

The scene

The scene should have the windmill in the origin of the scene. There should be a flat ground, and this time we do not want texture on it. Both the windmill and the ground should be lit in later stages, so use the same shader for both. Keep the skybox from earlier parts.

Furthermore, put the Utah Teapot at coordinates 20.0f, 0.0f, 20.0f. This places it with the positional light sources between the teapot and the windmill.

Skärmavbild 2022-02-03 kl. 11.12.38

The scene, with fake light only

The light sources

Here are specifications of four light sources:

C syntax:

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

C++ syntax:

vec3 lightSourcesColorsArr[] = { vec3(1.0f, 0.0f, 0.0f), // Red light

                                 vec3(0.0f, 1.0f, 0.0f), // Green light

                                 vec3(0.0f, 0.0f, 1.0f), // Blue light

                                 vec3(1.0f, 1.0f, 1.0f) }; // White light

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


vec3 lightSourcesDirectionsPositions[] = { vec3(10.0f, 5.0f, 0.0f), // Red light, positional

                                       vec3(0.0f, 5.0f, 10.0f), // Green light, positional

                                       vec3(-1.0f, 0.0f, 0.0f), // Blue light along X

                                       vec3(0.0f, 0.0f, -1.0f) }; // White light along Z

and the specular exponent (per object):

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

Apply these to the parts of the scene like this:

• specularExponent[0]: Floor

• specularExponent[1]: Windmill

• specularExponent[2]: Teapot

Upload to shader:

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

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

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

and separately, for each part:

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

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.


Step 1: Convert data to view coordinates.

I assume that you calculate the lighting in view coordinates. It is vital that we do not mix up the coordinate systems, so ensure that you are in the right coordinate system for every step.

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.

You will need to have the following data in view coordinates:

• Camera position.

Question: Where is the camera in view coordinates?

• Surface position.

• Normal vectors.

• Light direction.

For each of these, apply the model-to-view or world-to-view as appropriate. The light direction is a question for the fragment shader. Normal vectors and surface positions must be passed as varyings (out from vertex, in to fragment).

How do you know that they are correct? Put them on the surfaces! Do this for the normal vectors and the surface position. The normal vectors should be blue for surfaces heading in your direction, the ground should be green. For the surface position, you should get a cross in the middle of the screen.

Skärmavbild 2022-02-03 kl. 09.53.16

The scene visualizing normal vectors in world coordinates. This is not what we want.

Skärmavbild 2022-02-03 kl. 09.53.45

The scene with normal vectors in view coordinates. We will now always get blue facing the camera.

Skärmavbild 2022-02-03 kl. 09.57.37

The scene visualizing surface positions. We should get this cross in the middle, and it will stay there as we move.

Skärmavbild 2022-02-03 kl. 10.59.51

This is what you get if you put the surface positions in world coordinates. This is not what we want.

Step 2: Directional diffuse light.

Start with light source 2, blue directional light along the X axis. If it is correct, you get a diffuse light on the X side of both objects, and none on the floor since the light it is parallell to the floor. You get the direction towards the light.

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

Add light source 3. If #2 was correct, this should pose no problem.

The picture below was made with 0.5 times each light.

Skärmavbild 2022-02-03 kl. 10.03.10

Both directional lights, diffuse only.

Step 3: Positional diffuse light.

For a positional light, you need to create a local vector from the surface to the light source.

The positional lights are red and green and should produce diffuse spots between the windmill and the teapot. They do not move as you move the camera.

Skärmavbild 2022-02-03 kl. 10.05.11

Diffuse positional light. Note the spots on the ground!

Step 4: Specular light.

For the moment, only use light source 3 (white directional).

Now you need to use the direction towards the viewer and mirror the surface-to-light vector over the normal vector.

You should get some highlights in the models, but also a big lit area on the ground, in the distance.


Skärmavbild 2022-02-03 kl. 10.08.55

Specular light from light source 3.

Skärmavbild 2022-02-03 kl. 10.09.23

Directional specular light will give a lit area in the distance.

Now turn on all lights. You should get two sharper spots on the ground, red and green. They move as you move the camera. Note: I used 0.5 times diffuse and 0.8 times the specular.

Skärmavbild 2022-02-03 kl. 11.06.53

This is what we want to see!

We can note that these separate spots do not feel very realistic. This is due to diffuse light and specular light being two separate components and a better light model could model this better. It is also a question of how sharp the highlights should be, and the lack of ambient light, and of light attenuation. Let’s not worry, we have a pretty decent light with full control from the main program.

Interesting side note on the realism of the separate spots: This is actually not unrealistic, this does happen for some materials!

Questions:

Why was blue facing the camera when visualizing the normal vectors?

For light source 3, why did we get a white area in the distance for the specular light but not for the diffuse?

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.

Questions:

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