Lab 5: A Camera Class, World Navigation, Perspective

Due Monday, 12 October 2020, before 7am

This is a partnered lab assignment and a continuation of last weeks lab. There is no new repo to clone. Please work with the same partner and push to the same repo for this week.

1. Lab Goals

Through this lab you will learn to

  • Use an Perspective projection transform to convert from a 3D world coordinate system suitable for a 3D solar system model to webGL2 clip coordinates.

  • Build a Camera class and construct your own View matrix

  • Navigate through the scene by moving the camera with translations and rotations.

  • Use event listeners or DAT.gui to interact with the user.

2. General Guidance

Please see the page on General Guidance for help on setting up the python server, or ngrok, and submitting your labs via git.

3. References

3.2. WebGL2/TWGL

3.3. LearnOpenGL

  • Camera motion - This link has another derivation of what we call the view matrix and what they call the lookAt matrix (but is actually the inverse of twgl’s lookAt matrix). They also talk a bit about a Camera model translation and rotation effects. The figures may be helpful, but the code uses GLM and glfw and doesn’t transfer easily to Javascript/TWGL.

4. Overview

If you have not completed lab 4, you can finish some of your remaining features for partial credit, but you can do the lab 5 components even if you have a single sphere from the lab 4 starter code. Labs 4 and 5 will receive separate grades. For this lab you will add a camera class and some keyboard or mouse interaction to manipulate the camera. Additionally, you will switch your orthographic projection to a perspective projection for a better sense of 3D depth.

4.1. Getting Started

For the keyboard or mouse interactive parts, we will need to make some small changes to the .css, .html, and vertex shader file from lab 04. Cut and paste the changes below into your program and verify that your code still works before proceeding.

4.1.1. Changing style.css

/* only the border line and the focus
   block at the end are new, but feel free to copy and paste
   the whole thing */
canvas {
    display: block;
    width: 95vw;
    height: 95vh;
    border: 0px;

canvas:focus {
  outline: none;

4.1.2. Changing index.html

Add the tabindex="0" to you canvas tag as shown below.

<canvas id="lab" tabindex="0"></canvas>

4.1.3. Changing your vertex shader

Add a uniform u_view to your list of uniform in vshader.js

uniform mat4 u_projection;
uniform mat4 u_model;
uniform mat4 u_view;

Sandwich u_view between the projection and model matrix:

gl_Position = u_projection*u_view*u_model*vec4(a_position,1.);

Temporarily add a uniforms.u_view = m4.identity() in your main.js init() function so your program continues to work. Test your changes before proceeding.

5. Writing a Camera Class

The first goal of this lab is to write a Camera class that allows you to set a viewpoint in the world and then navigate by manipulating the camera.

Your constructor must have the form

constructor(eye, at,up)

where eye, at, and up are the coordinates in world space of the two points and one vector used to describe the point of view of the camera.

The first method you should write for your camera class is the getView() method, which returns the current View matrix as described on Friday’s class.

For initial testing purposes, you may use the code snippet we used in the demo to construct the view matrix.

let camera = m4.lookAt(eye, at, up);
let view = m4.inverse(camera);

However, for full credit, you must construct the view matrix using the method outlined in class. You may use the twgl.v3 functions, and basic twgl.m4 functions like multiply, translate, etc, but you cannot use lookAt directly. At some point, you may want to set portions of a 4x4 matrix in twgl. The twgl.m4.setAxis(m,v,i,dst) function sets the first three elements of column i of the 4x4 matrix m to the three elements in the vector v, and stores the result in the optional dst. It then returns the new matrix.

Step through the notes carefully and check the result of your handcrafted matrix to that of the lookAt/inverse method. They should be the same, except, you should not need to take the inverse of your matrix.

Test your implementation by trying a couple different viewpoints similar to that of the w04 demo.

You will need to adjust your ortho matrix such that the near and far values are now relative to the eye. I suggest a small positive value for the near value like 100 and a larger value for the far z value like 10000, but this depends on your scale a little bit.

5.1. Navigating the scene

Add the following translation methods to your camera class. Note half of them are implemented as just opposites of the other half. Think about how you want to implement this and what state you want to maintain in your camera class. Do you want to explicitly maintain an eye, at, and up vector in your class? If so, which variables change when you move, e.g., up?

If you have a this.view matrix, can you apply an easy update to this matrix using an appropriate twgl.m4.translate? If so, is this translate in eye coordinates, or world coordinates, on the left, or on the right. If you think carefully about this, you may have to do very little coding.

/* move camera amt along the
   local +x direction in _eye_ coordinates */

/* move camera amt along the
   local +y direction in _eye_ coordinates */

/* move camera amt along the
   local +z direction in _eye_ coordinates */

/* helpers in the opposite direction */

To test your implementation, you should add event listeners that listen to the keydown event, similar to lab 03. Instead of the keypresses moving the snake, they will move your camera, and now you need to support 3D motion.

/* don't forget to call this in init()
   and check your css and html edits if it isn't working
   you need to click in the window before responding to keypresses */
function setupListeners(){

  gl.canvas.addEventListener("keydown", event => {
    if(event.code === "ArrowUp" ){
    /* add more keys here */
    /* you can use event.shiftKey to see if e.g., the shift key is down */



5.2. Rotational Camera Methods

Add the rotational camera methods roll, pitch, and yaw. Note that the angles are specified in degrees here, but the twgl rotation functions expect angles in radians.

/* rotate the camera CCW about the +z eye axis by deg _degrees_ */

/* rotate the camera CCW about the +x eye axis by deg _degrees_ */

/* rotate the camera CCW about the +y eye axis by deg _degrees_ */

Add more event listeners to control rotational motion. You should now be able to pivot in the scene. Test that your translation still works as expected. The translates are with respect to the camera. If your rotate your scene 45 degrees and then try to move left and your scene moves diagonally, your translation is still with respect to the world frame not the camera frame.

Keep in mind the amt and deg amounts are relative, not absolute, so you can say roll(10) three times to rotate 30 degrees. You will probably want keyboard or mouse events to trigger these updates instead of the dat.gui widget, which is suitable for more absolute value settings.

For an extra optional challenge, you can use mouse events to implement a virtual trackball. This is not required for full credit.

Whatever controls you choose, you should be able to navigate your scene with your camera control. You should be able to e.g., turn to look at Neptune and move closer to it, or move above the solar plane and look down.

6. Changing the projections to Perspective

As a final feature, you should switch your projection matrix from an orthographic to a perspective transformation. You do not have to build the perspective matrix from scratch. Just use twgl.m4.perspective. The zNear and zFar values are similar to those of your ortho matrix. Do not set zNear to be negative or 0. Note that the program currently computes the aspect as a global variable that you can use in perspective. You should experiment with the field of view in radians value. It may be helpful to add a dat.GUI slider to adjust the near, far, and field of view. You can set the field of view in the GUI in degrees and convert to radians in the code. A value of 10-20 degrees has a narrow field of view and a zoom like effect. A much wider field of view, e.g., 120 degrees yields strange fish-eye effects in the corner. Experiment and find something that works for you.


Make a file that contains your answers to the following questions.

# Concept Questions

* What state (this. variables) did you maintain in your Camera class?

* Describe briefly how you implemented moving right.

* Suppose we were given 4x4 view matrix with no other information. Would there be anyway to compute the `eye` position in `world` coordinates given just this matrix? Explain your answer briefly.

# Quick Lab Survey

None of your answers to the questions below  will have an impact on your grade. This is to help provide feedback and improve course quality

* Approximately, how many hours did you take to complete this lab?
  *     Hours

* How difficult did you find this lab?
  (1-5, with 5 being very difficult and 1 being very easy)

* Describe the biggest challenge you faced on this lab.

8. Summary of Requirements

Your project will be graded on the following components:

  • A Camera class with working navigation

  • A properly constructed view matrix that does not use m4.lookAt

  • Keyboard or mouse controls for translation and rotation of the camera

  • A perspective projection matrix.

  • Answer to concept questions in the file.

You will not be graded on the lab survey questions in the file


Once you have edited the files, you should publish your changes using git. See the General Guide for more details.