Laboration 4

Interactive terrain

Goal: In this lab, you will render a 3D terrain with objects that interact with it. The terrain is given as a heightmap image.

Since this is the last lab, it is more flexible than usual. There are several interesting non-mandatory tasks at the end.

1) Load and inspect the heightmap.

Lab files:

2023 version is in the download in lab 1!

lab4.tar.gz (Slightly updated 22-05-13, I hope it is correct)

Lab files (old, backup):

lab4.tar.gz

A "bare bones" program is provided as lab4-1.c, with two basic shaders, terrain.vert and terrain.frag. 

Two of the included images are intended as heightmaps.

You will also need files from earlier labs (textures, helpers). The makefile is similar to old ones.

The initial lab shell loads the small terrain "terrain-44.tga" and draws it with texture. There is, however, no light and no normal vectors.

To compile this assignment, perform make lab4-1 on the command line.

Run the assignment by performing ./lab4-1.

Questions:

What kind of data does the heightmap image data hold? How many different levels are possible?

The terrain is 4x4. How many polygons does it draw?

Note: The following line is printed out by the program:

printf("Note: The call to DrawModel will report warnings about inNormal not existing. This is because inNormal is not used in the shader yet so it is optimized away.\n");

This is a warning about an error message that is likely to happen at this stage, not an error! You can remove the line any time.

2) Navigating the heightmap

Goal: To move around a large heightmap.

Copy lab4-1.c to lab4-2.c. Make this section's changes to lab4-2.c.

Load the larger heightmap "fft-terrain.tga". You may need to scale the map differently.

Use your code from earlier labs for placing the camera so you can move around it nicely. (If your camera movement is good, this is easy.)

Questions:

Did you need to extend your camera movement code? How?


3) Calculate normal vectors and apply lighting

Goal: To make the heightmap look better by using lighting.

Copy lab4-2.c to lab4-3.c. Make this section's changes to lab4-3.c.

Lighting should be familiar to you by now. Generate a normal vector for every vertex in the surface to make a decent lighting of the scene.

Apply lighting on the terrain by using a shader from earlier labs.

The triangle positions must be used for finding the normal vector. You can choose from three methods:

1) High precision version. For a vertex, calculate normal vectors for all neighbor triangles. Then calculate a weighted average of these. For maximum precision, this should depend on the angle of the triangle. This is fairly easy for a terrain since the angle, projected on Y, is always 45 or 90 degrees.

2) Good, fast approximation, "the triangle". Pick three neighbor vertices, on all sides of the vertex, and use these three for calculating a normal vector.

3) Another good, fast approximation, “the cross". Pick four neighbor vertices, on all four sides of the vertex, and use these four for calculating a normal vector.

In all cases, you must check for edges and use some special case there. If you are significantly behind schedule, it is OK to leave the normals at the edge unchanged. (You don't do that in "real life" though, where the terrain is often a patch out of many.)

Questions:

Which method did you use to find the normal vector?


4) Calculating map height for a point

Goal: To find the height of the heightmap for any given point and place objects in the scene using that information

Copy lab4-3.c to lab4-4.c. Make this section's changes to lab4-4.c.

Your height map is drawn as a set of triangles. For a given point (x, z), find the y value.

TSBK07: You need to make the object move smoothly over the surface.

This is done in three steps:

1) Calculate what quad the point falls into.

2) The quad is built from two triangles. Figure out which one to use.

3) Calculate the height value. There are several ways to do it. You can interpolate over the surface, or you can use the plane equation, or use barycentric coordinates (line-triangle intersections). I recommend interpolation.

Using this function, place objects on the surface. You may use anything, but the models "groundsphere.obj" and "octagon.obj" can be nice.

groundsphere.obj

octagon.obj

Make the object move over the surface. The object should slide smoothly along the surface without jumps, and must not go between exact vertices but interpolate nicely. (Note: Interpolate on the CPU and use the height for translating the object. This is not a shader interpolation problem.) You can use the low-res terrain for testing; it can be pretty revealing.

Hint: When debugging, it can be worthwhile to return to the small terrain, possibly scaling it up so it is easy to see what happens. Otherwise you may have so many polygons that you can't really tell why the object moves in a certain way.

You only need to check height for the bottom point of the object. This means that it might overlap the surface in other places. We accept this error for now.

TSBK11: You only need to match the height to the nearest vertex. (Change added for 2025 in order to simplify the lab.)

Questions:

How did you figure out what surface to use? 

How did you calculate the height from the surface?


5) Multitexturing

Goal: To make the terrain look even nicer through multitexturing.

Using at least two textures and a shader, blend the textures in some appropriate way (height, slope) to make the terrain more realistic, interesting and/or beautiful.

Copy lab4-4.c to lab4-5.c. Make this section's changes to lab4-5.c.

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:

  glActiveTexture(GL_TEXTURE0);

  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 the terrain. You can base it on height, slope, or something else.

Questions:

What kind of multitexturing function did you implement?


6) Pick one of the "extras" below as final part of the lab.

(Optional for TSBK11 and ETE378 - no longer optional from 2025)

If you get here quickly, well within the lab time, I expect you to make two of these. If you are short of time, you only have to do one.

Remember that some nice features can be part of your project even if you don't finish them here, in case there are several features below that you feel are desirable (and there surely are).

Copy lab4-5.c to lab4-6.c. Make this section's changes to lab4-6.c.

6a) Multiple objects on terrain [easy]

Goal: To put many objects in the "world"

Introduce many objects, either many of the same or different objects. In any case, you may use spheres for collision detection. Handle collisions between the objects and between all objects and the surface.

Note: This is not just putting many objects into the scene (we did that earlier) but to make them interact.

Questions:

How did you handle collisions? 

How did you represent the objects to make collision detection easy/managable?


6b) Objects with terrain dependent slope [medium]

Goal: To measure the slope of the terrain and apply it on objects

Every polygon has a slope, that is its normal vector. Use that information to draw objects that are appropriately tilted to match the slope. Test using the low-res terrain.

Questions:

How did you apply the slope on objects?


6c) The lake [easy (with optional hard extensions)]

Goal: To handle a part of the terrain separately to make a lake

One of the terrains, "fft-terrain.ppm", has a big flat area in the middle of the map. This area could be considered a "lake". Identify the appropriate "water surface" polygons and paint them blue.

Luxury variants (teasers for advanced students, but generally more interesting for your projects):

Reflect the surrounding terrain in the surface.

Make the water surface transparent and put a bottom below it.

Waves, moving water.

Environment mapping, reflecting the terrain dependent on waves.

Refraction effects through the surface. (Similar to environment mapping.)

Again, this list is just a "teaser" list. The basic assignment can be very easy.

Questions:

How did you identify the water surface?


6d) Frustum culling for objects [fairly hard]

Goal: To optimize model drawing by only drawing them if visible

In large worlds, models will often be outside the viewing frustum. Then drawing can be speeded up significantly by excluded out-of-view models. Several ways to handle this are outlined in the book.

For doing this, you need to find normal vectors for all frustum planes. You can safely forget the near plane, though.

To use this for a model you should use a bounding sphere.

Questions:

How did you decide whether to draw the model or not?

Why don't you have to test the near plane?


6e) Frustum culling for large world terrain [hard]

Goal: To optimize terrain drawing by only drawing visible parts

When using large terrains, large parts of the terrain will often be outside the viewing frustum. Then drawing can be speeded up significantly by excluded out-of-view polygons. Several ways to handle this are outlined in the book.

To use this for a terrain, you must split the terrain into at least four sections, or (better) extend the terrain to a much bigger world. The frustum culling will limit drawing to at most four sections (granted that your frustum isn't too long).

For the lab, note that the provided terrain is wrap-around so you can just reuse it for additional sections.

Questions:

How did you decide which sections to draw?


6f) Even better multitexuring using texture splatting [medium or fairly hard]

Goal: To design multitexturing using a splatmap

Splatting means using a texture not as texture but as a map for blending between two or more textures.

Questions:

How did you make your splatmap?

How many texture do you blend between?


6g) Make the world seem infinite [easy to hard]

Goal: To give the user an illusion of a infinite world

There are some tricks to make the world appear very big even if it isn’t.

• Add fog, that is, make geometry beyond a certain distance fade away.

• Limit the user’s movement so that the user is never near the border (so the user never sees that there is a limit).

• Make the terrain fade out to flat, making the world into an island surrounded by a big sea.

• Draw four patches or more instead of one (as suggested in 6e above).

• Move the user in order to stay within 4x4 patches (without noticing).

• Draw more patches in front of the user no matter how far the user travels

As noted above, "fft-terrain.tga” is wrap-around so you can draw it several times.

Questions:

Which kind of “infinity” did you make?


That concludes lab 4. We hope that this gives you a start in programming geometry yourself as needed, and maybe gives you tools and ideas to use in your projects.

This page is maintained by Ingemar Ragnemalm