Department of Computer Science and Software Engineering

CITS3003 Graphics & Animation 2022 — Lab2

Unit Coordinator & Lecturer

Dr. Naeha Sharif

 

Lab Facilitators

David Charkey

Jasper Paterson

 

Consultation Time

Thursdays, 3:00-4:00pm.

Where: Room 1.05 in CSSE
and online (active in consultation hour)

 

News:

  • [28 Feb'22] Welcome to CITS3003

Colour and 2D Rotation

Objectives:

In this lab's exercises, you will extend lab 1 by:
  • Adding colour to primitives like triangles
  • Combine triangles to draw a square.
  • Move triangles around smoothly using double buffering

You can complete this lab under Linux. You may need to reboot to choose the operating system. If you use the Linux platform, use the .cpp and .glsl files supplied under the LINUX_VERSIONS subdirectory.

  1. Continuing from last time

    1. Copy your lab1 folder to lab2 - e.g., via:

      cd ~/cits3003/labs-examples
      cp -r lab1 lab2
    2. Rename q3triangleScene.cpp to q1square.cpp and remove the other questions from lab2 (keep them in lab1!)

      mv q3triangleScene.cpp q1square.cpp
      rm q1circle.cpp q2pointsScene.cpp
  2. Drawing (one face of) a cube in 2D

    1. Suppose we want to draw a solid 3D cube on our 2D screen. To start with, suppose one face is facing directly towards us. Then the other 5 faces are hidden behind this face, so it's easy - we just draw a square (in the next lab we'll come back and consider what to do if there isn't a face directly facing us). So for now we'll just focus on drawing a square using two triangles, say using:

      const int NumTriangles = 2;
      const int NumVertices  = 3 * NumTriangles;
      
      vec3 points[NumVertices] = {
          vec3(-0.5, -0.5, 0.0), vec3(-0.5, 0.5, 0.0), vec3(0.5, -0.5, 0.0),
          vec3(0.5, 0.5, 0.0),   vec3(0.5, -0.5, 0.0), vec3(-0.5, 0.5, 0.0)
      };
                   

      Here we use 3D coordinates just to avoid changing it later - for now all z-coordinates are 0.0 (which is what OpenGL defaults to anyway if you draw using vec2's). You'll need to change the second argument in the call to glVertexAttribPointer from 2 to 3. This is the number of components per vertex - see the OpenGL reference page for this function (for other OpenGL functions, see the OpenGL 3.3 Reference Pages on the left of this web page).

      Use the code above to initialize the triangles and build the program via make and run it. Compare with:



    2. Now suppose we want to see which part of the square is in each triangle. Add colours by adding (below the code for points from above):

      vec3 colors[NumVertices] = {
          vec3(0.0, 1.0, 0.0) /* Green, but choose any 6 colours */, ..., ...,
          vec3(/* ... */), ..., ...
      };
                      
    3. Copy the init function from example4.cpp, starting from the comment:

      // Create a vertex array object
    4. Also comment out the line which enables the depth test (we'll add it back later):

      glEnable( GL_DEPTH_TEST );

    5. The main difference between this init function and the previous one is that it also sends the color array to the OpenGL buffer. Then vshader24.glsl uses this color information via the vColor shader input variable.
    6. Make your program and run it. Verify that the vertices have the colours you assigned. Here's mine that uses the colours with just 0.0's and 1.0's, aside from black and white:




      What colours do you get in between the vertices? Experiment with different colours.

  3. Rotating in 2D

    1. Now suppose we want the cube to rotate such that this face stays facing us. We could modify all the vertex positions, but this would mean all positions would be sent to the graphics hardware again (not an issue with so few vertices, but in general there could be many vertices and we'd want to avoid this).


      Instead we can just pass the angle of rotation to the vertex shader, and have it rotate all the vertex positions by this angle. In fact we can just pass the time (since the program started) to the shader and have it increase the angle over time.

    2. Copy q1square.cpp to q2sqrotate.cpp.
    3. Copy the existing vshader24.glsl to vrotate2d.glsl. In the C++ program q2sqrotate.cpp, we will use vrotate2d.glsl and fshader24.glsl together. In the vertex shader, add the time variable:
      uniform float time;      /* in milliseconds */
      

      to the top of the file (above main)

    4. Change the main function in this vertex shader so that the position is set instead via:

          float angle = 0.001*time;
          gl_Position = vec4(vPosition.x*cos(angle) - vPosition.y*sin(angle),
                             vPosition.x*sin(angle) + vPosition.y*cos(angle),
                             0.0,
                             1.0);
      

      [Why are there four components in gl_Position? Because OpenGL actually uses 4D homogeneous coordinates - we'll come to this in lectures soon. For now the last component should always be 1.0.]

      Here the angle is in radians, so the square rotates roughly every 6.28 seconds. Why sin and cos here? Well, basically because that's what sin and cos were invented for!

      cos tells you how much the original (x or y) coordinate affects the same coordinate when rotated.
      sin tells you how much the original (x or y) coordinate affects the other coordinate when rotated. (It's negative in the line for the x coordinate above because the y-axis when rotated clockwise moves towards the negative x-direction.)

      See 2D rotation.

    5. Change the call to InitShader to use your new vertex shader.
    6. Add a new GLint global variable called timeParam to store the location of the new shader variable time. Set this global variable in the init function (after the shaders have been loaded and "used") via:

          timeParam = glGetUniformLocation(program, "time");

      [This is a uniform variable which means that it doesn't change during the drawing of a primitive, hence no array/buffer is needed unlike vPosition and vColor.]

    7. Set the new uniform time variable before the call to glDrawArrays via:

          glUniform1f( timeParam, glutGet(GLUT_ELAPSED_TIME) );


      [glUniform1f sets a uniform parameter containing a single float to a value. Here the value is that returned by glutGet(GLUT_ELAPSED_TIME) which is the time (in milliseconds) since glutInit was called. See glutGet in the freeglut API (now in the links to the left of this page). ]

    8. Add a function with signature void idle(void) that just calls glutPostRedisplay(). Then add a call to glutIdleFunc in main to set the idle function to this function.

      [Calling glutPostRedisplay tells GLUT that the window needs to be redisplayed. Here we call it in the idle function because there is constant motion, so we want to redisplay whenever GLUT/OpenGL has nothing else to do and is idle. The actual redisplay will happen at some point after the idle function returns, when GLUT is ready to redraw the window.

      More generally, if you only require redrawing when a mouse event causes an object to move, or similar, you should only call glutPostRedisplay() when such a change occurs. Calling it multiple times is fine if multiple changes occur before a redraw happens - it just sets a variable that GLUT uses to remember that a redraw is required.]

    9. Make your program and execute it - you should see your square rotate! Increase the size of the window and enjoy.
    10. Experiment with increasing the speed of rotation. What happens if we square the angle?
      (Don't watch this too long, particularly if you're a little short of sleep.)
    11. Add GLUT_DOUBLE to the glutInitDisplayMode call (using bitwise-or, i.e., vertical bar), and replace glFlush() with glutSwapBuffers(). This causes double buffering, which prevents flickering. Then again, modern graphics hardware often automatically double buffers, so you may not see any difference, including when you're using the lab machines.

  4. Sample Solutions


Department of Computer Science and Software Engineering

This Page

Website Feedback:
naeha(dot)sharif(at)uwa(dot)edu(dot)au