You will submit a webpage that has

  • One canvas
  • Keyboard response to move the camera
  • One or more texture-mapped objects

As with the WebGL warmup, you’ll submit an HTML file and any number of js, glsl, json, and image files. Also submit a file named implemented.txt listing any optional parts you believe you completed.

As with most assignments, this is divided into required and optional parts. For full credit, you need all the required parts of each assignment and an accumulated average of 50 optional points per assignment. Excess optional points at the end of the semester award extra credit at 3% of their raw point value.

This assignment build off of the terrain assignment; if you didn’t get the required parts of that assignment working you’ll need to return to it and finish it first.

This assignment, like all other assignments, is governed by the common components of MPs.

1 Required (50 points)

Auto-generate a terrain
Separate the terrain generation and display from the rest of the terrain assignment. As soon as the page loads it should show a randomly generated terrain. The terrain should be lit, like it was in MP3 (at least diffuse; specular is OK but not required).
User-controlled camera movement

Add a keyboard listener that handles user input and moves the camera location around the scene. To handle held-down keys you’ll need both a keydown and keyup listener.

The recommended way to listen to keys for this assignment is

window.keysBeingPressed = {}
window.addEventListener('keydown', event => keysBeingPressed[event.key] = true)
window.addEventListener('keyup', event => keysBeingPressed[event.key] = false)

and then in your animation function (whatever calls itself with requestAnimationFrame) use keysBeingPressed to decide how to move the camera.

For the required part, your camera needs to respond to the at least four keys:

  • if (keysBeingPressed['w']) move the camera forward
  • if (keysBeingPressed['s']) move the camera backward
  • if (keysBeingPressed['a']) move the camera to its left (move, not turn)
  • if (keysBeingPressed['d']) move the camera to its right (move, not turn)

These keys are common in video games and are commonly called WASD.

Holding down a key should result in smooth motion in the given direction until the key is released.

Texture mapped terrain

Instead of a constant color or height-based color ramp, use a texture map for the color of the terrain. The image you use must be one of

  • an image you took and own yourself
  • an image in the public domain
  • an image licensed for sharing with attribution displayed on the screen1

A useful source of images with a given license is Duck Duck Go’s license-filtered image search. The photo should be in good taste (nothing offensive) and not primarily black (otherwise we won’t be able to see the lighting on the terrain).

Use texture coordinates based on the (x,y) positions of the terrain, scaled such that the image covers the entire terrain once and at approximately the right aspect ratio (i.e. if you have a square terrain and rectangular image, only map a square part of the image to the terrain).

See our page on textures for the technical details of how to get a texture map set up.

We provide an example public-domain image you may use if you wish.

2 Optional

Rotating camera (20 points)

Add four more keyboard controls:

  • ArrowUp and ArrowDown pitches the camera up and down
  • ArrowLeft and ArrowRight turns the camera to the left and right

The WASD keys should work as defined by the current camera view: W means forward in the current view, not in some global world view.

Vehicular movement (15 points)

Add a key that acts like a toggle instead of a held-down control. Pressing G should toggle between flight mode (default) and on-the-ground mode. When in on-the-ground mode, the altitude of the camera should be fixed at 2 terrain grid cell lengths above the current terrain.

Suppose your terrain goes from -1 to 1 in x and y and has 50 points on along each axis. Then the terrain grid cell length is \frac{1--1}{50} = \frac{2}{50} = 0.04, meaning the camera should be kept 0.08 above the ground in on-the-ground mode.

In on-the-ground mode camera movement speed should be slow enough to have several frames moving across one grid cell, and heights should adjust smoothly when moving between cells of different height.

If the camera is driven off the edge of the terrain, you should do something sensible: keep the same height, revert to some globally-defined ground height, switch into flight mode, or prevent the motion by snapping the camera back to the nearest over-ground position.

OBJ loading (15–45 points)

Load an OBJ file and place it on top of the center of your terrain.

See the OBJ parsing page for tips on how to load an OBJ file. Several sample files are available:

Basic (15 points)

Handle f and v. Load example.obj (always that exact filename), which may be different when we test your code than it was while you were developing your code.

If example.obj does not exist (i.e. fetch("example.obj") fails) your code should run normally with just the terrain, no OBJ loaded.

This should work with both the teapot and triangle model at a minimum (i.e. after running cp teapot.obj example.obj or cp triangle.obj example.obj).

Vertex colors (10 points)

Handle v in both the 3-value position-only and 6-value position-and-color forms.

You’ll likely need separate shaders for the two because they have different sets of vertex shader inputs.

The example file cow.obj has vertex colors.

Custom file (5 points)
The file you open should be window.location.hash.substr(1) if that’s defined, or example.obj otherwise. That will allow us to visit URL/to/your/file.html#thing.obj to have it load thing.obj instead of example.obj.
More attributes (15 points)

Handle vn and vt as well as f and v.

If the OBJ file uses vt and you implement vt, you should use the same name as a OBJ with the ending jpg instead of objas the texture map (e.g. for thing.obj use thing.jpg as the texture map).

You’ll likely need separate shaders for the v-only case, the v+vn case, the v+vt case, and the v+vn+vt case because each has a different set of vertex shader inputs.

You don’t need to combine these with vertex colors, even if you implement them both.

The example file monkey.obj has texture coordinates and surface normals.

Fog (15 points)

Add a key that acts like a toggle instead of a held-down control. Pressing F should toggle between no-fog mode (default) and fog-mode. In fog mode, the fragment shader should cause fragments that are farther from the camera to be drawn closer to the background color, which should be some pale foggy color if you implement fog.

There are multiple options for fog. From simple to accurate, you may do any of

  • use gl_FragCoord.z; at 0 use full object color, at 1 use full fog color.
  • use the length of the interpolated view-space position as the z value in the uniform fog equation \displaystyle e^{-dz} where d is a fog density constant you pick.
  • use physically-based fog such as Nathan Hoobler’s version – note this is almost certainly out of scope for a single assignment in this class, but worth documenting as an option.

Your fog should be dense enough to be obviously present and thin enough to see at least part of the terrain from the initial camera position.