CS 488/688: Introduction to Computer Graphics
Fall 2015 Assignment 1: OpenGL
Summary
In this assignment you will learn the basics of programming real-time 3D graphics using OpenGL. You will create geometric primitives, draw them to the screen, and modify the contents of the 3D environment in response to user interaction.
In particular, you will develop a 3D sandbox environment for constructing coloured, low-resolution blocky landscapes from cubes. The program is inspired by the 3D sandbox game Minecraft, though it's a pretty improverished version of that game, since you can only build upward in columns. (Also, there is no texturing, no shading, no redstone, no creepers, no cats, etc.) Let's call it Barcraft.
The Barcraft environment
The most obvious feature of the Barcraft environment is the grid, which lies on the ground (i.e., the plane y = 0) and defines the legal squares upon which individual bars may be grown. Each grid cell is a unit square, and the grid itself is an N×N arrangement of legal cells for some constant N. In the environment, we actually draw a grid of size (N+2)×(N+2), surrounding the main grid with an extra ring of cells as a visual reference. This extra ring is purely for decoration—no bars can be grown on it.
Every live (non-ring) grid cell supports a bar. The bar is a stack of unit cubes, all of the same colour. The point of the Barcraft environment is to navigate around on the grid, growing and shrinking the bars and assigning colours to them, in order to produce a simple landscape, sculpture, or heightfield.
At any time, one cell in the grid is "active". Commands to grow or shrink bars should affect that cell. The active cell is clearly indicated in the user interface in some way, for example, by highlighting the cell or the top of the bar that lives on it. Whatever form of indication is used, it should be drawn on top of the rest of the Barcraft environment; otherwise it may be hidden by tall bars in other cells that cover up the active cell. (It may also be possible to include useful indicators in the outer ring of cells.)
For the most part, we will use the keyboard to control the modelling environment, as explained below. In addition, we can manipulate the view in a few simple ways via the mouse: we can rotate the grid around its centre to look at the design from all angles, and scale it up or down.
User interaction
Your implementation of Barcraft should support (at least) the following commands and interactions:
- Quit Application: There should be a Quit Application button in the control panel that terminates. It should also be possible to perform this action using the Q key.
- Reset: There should be a Reset button that restores the grid to its initial, empty state, resets the view to the default, and moves the active cell back to (0,0). (Hotkey: R)
- Colours: There should be at least eight colours that can be applied to bars. The colours should be defined via a sequence of colour pickers in the control panel, each paired with a radio button. Clicking the radio button sets the active colour, so that any new bars (i.e., those grown on initially empty cells) are given that colour. If there is already a bar in the active cell, the selected colour is also applied immediately to that bar. Editing the colour itself (e.g., changing the R, G and B sliders) should immediately affect all bars of that colour in the grid.
- Rotation: Dragging the mouse to the left or the right should rotate the grid smoothly around its centre (as if it were sitting on a turntable). This feature lets the user examine the grid from all angles. Moving the mouse should produce a "reasonable" amount of rotation, proportional to the distance travelled by the mouse in the x direction.
- Scaling: The mouse's scroll wheel (or the scroll gesture on a trackpad) should control the scaling of the grid. Scrolling down should make the grid smaller, scrolling up should make it bigger. The amount of scaling should be constrained by reasonably chosen maximum and minimum amounts.
- Growing bars: The space key should grow the bar in the active cell by one unit. The backspace key should shrink it by one unit. It should not be possible to shrink a bar below the ground plane. The environment may impose an upper limit on bar height, but this limit is not required.
- Arrow keys: The arrow keys move the active cell around on the grid. Use the left and right arrows to modify the x location of the active cell, and the up and down arrows to modify y. Holding down the shift key with the arrows adds extra functionality: when the active cell moves, it copies the height and colour of the bar from its previous location. Thus, by holding the shift key and walking around with the arrows, it's easy to build a wall of a given height and colour.
Skeleton code
If you haven't already, follow the instructions in
Assignment 0 for downloading the framework and
skeleton code. You'll find the skeleton code for this
assignment in the A1/
directory. It should compile
and run out of the box using the same premake4/make commands used
for Assignment 0. You'll see the the skeleton program sets up
the control panel, including a Quit button and single example of
a colour slider and radio button. It also draws a ground grid
for you. Notice that the grid is of size 18×18. The live
area of the grid is 16×16, as defined by the constant
DIM
in A1.cpp
; the program automatically
adds the extra ring of cells around the live core. The program
defines reasonable initial values for the projection, view and
model matrices, and passes them into the vertex shader for you.
Here is a suggested sequence of steps to get you started with this assignment. (But feel free to work on the assignment in any order you want!)
- First and foremost, get a cube on the screen. This is probably
the hardest part of the assignment! Define the geometry of a
unit cube, and get it into OpenGL by creating appropriate
attribute arrays and vertex buffers. You must use
modern OpenGL commands here and throughout the assignment.
That is, eventually you'll draw cubes using a function like
glDrawArrays()
orglDrawElements()
, and you should never call functions likeglVertex()
. - See if you can draw a stack of cubes instead of just one.
- If you can do that, hook up the space and backspace keys to grow and shrink that stack.
- Now define an active cell, and figure out a reasonable way to indicate it in the user interface. As mentioned above, the indicator should always be drawn on top of all other 3D geometry. That's easily done by temporarily disabling depth testing. Once the indicator is drawn, add the code to move it around using the arrow keys. Then make sure that space and backspace are applied to the active cell.
- Next, add in the functionality for copying by holding down shift with the arrow keys.
- Turn the single colour selector into at least eight colours. Write the code to hook up clicks on radio buttons with changing the colour of the bar in the active cell and setting the colour for any new bars. Make sure that editing the colours modifies all bars of that colour in the world.
- Add in mouse rotation. This is most easily accomplished by modifying the model-to-world transformation while holding the camera fixed. As is typical in interactive programs, it'll help to keep track of the previous x position of the mouse, so that you can rotate by an amount proportional to the difference in x every time you receive a mouse event. (Note that after rotating, the arrow keys might become quite unintuitive, since the grid is rotated relative to the directions of the keys. You don't have to worry about that for this assignment.)
- Add scaling by responding to scroll events. There are probably several ways to do this. One would be to scale the world itself by manipulating the model-to-world transformation. Another is to move the camera closer to or farther from the centre of the grid. Either technique is fine, as long as the scaling is bounded within reasonable limits.
- Finally, add in the Reset button and its hotkey, setting all the values manipulated above back to the defaults.
Deliverables
The submission process for this assignment is basically the same
as the one for Assignment 0. Prepare a ZIP file of the A1/
directory, omitting unnecessary files (executables, build files, etc.).
Upload the ZIP file to LEARN.
As with Assignment 0, you must submit two other files
in your A1/
directory: the README
and
a screenshot. If you omit either file, you will receive a deduction.
See Assignment 0 for instructions about how
to prepare these two files.
Other thoughts
As OpenGL has evolved, the API has actually gotten a bit more streamlined. The part of OpenGL you talk to from inside a C++ application tends to focus on telling the GPU about the pieces of data that power your program, and maybe setting a couple of system-wide parameters; most of the actual "graphics" has been pushed into the shaders.
If it helps, here is the complete set of OpenGL API calls we used in
the model solution. This is for information only: you should
feel free to use a subset of these functions, and to include others
not on the list. But you should not use deprecated OpenGL
calls from the compatibility profile: no glBegin()
,
glVertex()
, or glEnd()
!
glBindBuffer()
glBindVertexArray()
glBufferData()
glClearColor()
glDisable()
glDrawArrays()
glDrawElements()
glEnable()
glEnableVertexAttribArray()
glGenBuffers()
glGenVertexArrays()
glUniform3f()
glUniformMatrix4fv()
glVertexAttribPointer()
Ideas for extensions
The core Barcraft environment is a great starting point for expansion in new directions. We'd be excited to see what additional features you can pack on top of the base interface, if you've got the time. We may award a bonus point or two for especially ambitious extensions. Here are some random ideas for extensions:
- The look of Barcraft is pretty bare-bones. In fact, given that bars are flat, solid colours, the 3D geometry of the world can be pretty hard to understand. Try to add some kind of simple shading model to your cubes, possibly including lights. The simplest model is Lambertian shading based on an implied directional light source (though you'll discover that flat shaded meshes are surprisingly annoying to set up in modern OpenGL). If you want to get really fancy, look around for Screen-Space Ambient Occlusion algorithms.
- An obvious way to get closer to the look of Minecraft is to apply textures to your cubes.
- You could consider extending the geometry beyond bars. The ideal would be a fully 3D grid of voxels that can be controlled independently, but it can be hard to design an interface for turning individual voxels off and on.
- Of course, Minecraft's world is much larger than 16×16. You could try working with a much larger grid, but beyond a certain limit you'll undoubtedly need two things: a "sparse" data structure that represents the world without storing data for every single cell, and a "culling" algorithm that preprocesses the grid and only draws the cells that are actually in view.
- Implement a way to load and save the contents of the grid, and maybe to load a grid from some kind of image.
- Add a WASD/mouselook interface so that you can walk (or fly) into the maze and look around as if it were a city grid. For extra fanciness, implement rudimentary collision detection so that you can't walk through cubes.
- Turn the Bars into a platformer game in which a character can walk around on the tops of cubes and jump.
- Add new geometric primitives beyond cubes. (Creepers? Sheep?)
- Modify the key event handlers so that they simulate key repeat, making it easier to apply a bunch of growth or movement events all at once.
If you do extend the base interface, be sure to document your
extensions in your README
file. Keep in mind that
you must still satisfy the core objectives listed here. If
your changes are so radical that your modified program is
incompatible with Barcraft, you must include a "compatibility
mode" that makes the interface behave like the requirements
here. (Or consider creating an entirely separate executable.)
Step 5: Objective list
Every assignment includes a list of objectives. Your mark in the assignment will be based primarily on the achievement of these objectives, with possible deductions outside the list if necessary.
Assignment 1 objectives
- ▢ The user interface has the correct controls in a reasonable layout. The controls work appropriately.
- ▢ The program is able to draw a 3D grid of cubes of the correct sizes and locations.
- ▢ Bars of cubes can be given at least one of eight colours, as described above.
- ▢ There is an active cell, which is clearly indicated in the user interface. The indicator is drawn on top of all other scene geometry.
- ▢ The space and backspace keys work as described above.
- ▢ When used without the shift key, the arrow keys move the active cell around.
- ▢ When used with the shift key, the arrow keys move the active cell around while copying information from the previously active location.
- ▢ Rotation works as described above.
- ▢ Scaling works as described above.
- ▢ The reset button returns the user interface and grid stat to the starting state.