CS 424: Computer Graphics, Fall 2017
Lab 13 Alternative: WebGL Life

For the final lab of this course, you have the option of doing either this lab or a procedural textures lab (or both!).

In this version of the lab, you will implement John Conway's Game of Life in WebGL. This is an example of using the power of a GPU to do something that is more computational than graphical. The only part of the program that you will write is the fragment shader that uses information from the current board to compute the state of the pixel in the next generation.

You will need a copy of the folder WebGL-life from /classes/cs424. The starting point for the lab is the file WebGL-life.html.

You will not turn in any work for this lab. You will get full credit for showing up and showing me some completed work at the end of the lab. However, remember that Lab 12 should be turned in by tomorrow, December 1, at 3:00 PM.

About The Game of Life

The Game of Life (which is not really a game) is a cellular automaton. It is played on a rectangular game board that is divided into a grid of squares. Each square is called a cell. A cell can be in one of two states, "alive" or "dead". An initial configuration of living and dead cells is created on the board. After that, the game plays itself. At each time step, a new "generation" is computed, based on the current configuration of the board.

A cell has eight neighboring cells, which are next to the cell horizontally, vertically, and diagonally. (For cells along the edges of the board, we can think of the board as a torus, with the top edge connected to the bottom edge and the left edge connected to the right edge.) To compute the state of a cell in the next generation, you need to know its current state and the number of neighboring cells that are alive. The rule is as follows:

if the cell is currently dead, then {
    if the cell has exactly 3 living neighbors, then
        the cell is alive in then next generation
        the cell remains dead in the next generation
else {
    if the cell has exactly 2 or 3 living neighbors, then
        the cell remains alive in the next generation
        the cell becomes dead in the next generation

This rule is applied simultaneously to all of the cells on the board.

About the WebGL Implementation

The idea for the lab is to represent the grid of cells for the game of life as a grid of pixels. Cells are colored white if they are alive and are colored black if they are dead. The original version of WebGL-life.html can create several configurations of black and white pixels in the color buffer (where WebGL does its drawing). There is a checkbox labeled "Run" which is supposed to run an animation in which the next generation in the Game of Life is repeatedly computed and displayed. Currently, it doesn't work because the necessary fragment shader is missing. Your job is to write the missing fragment shader.

The point of the lab is to compute the next generation using the massive parallelism of the GPU. The computation will be triggered by drawing a square that fills the entire canvas. To draw the square, the GPU will call the fragment shader for each pixel. The fragment shader will compute the state of its assigned pixel in the next generation of the Game of Life.

To make that possible, the fragment shader needs access to the current Life board, so that it can check the current state of the pixel and its eight neighbors. The access can be provided by having a copy of the current board in a texture, so that the fragment shader can use texture sampling to read values from the current board.

The program uses a texture object to store a copy of the current board. To configure the texture, its minification and magnification filters are set to gl.NEAREST and the S and T wrap modes are set to gl.REPEAT (using gl.texParameteri). We need gl.NEAREST for the filters in this application to avoid getting some sort of averaged value when taking samples from the texture. Using gl.REPEAT as the wrap mode effectively connects the left edge of the texture to the right edge and the top edge to the bottom, which will give the correct values for neighboring cells for cells along the boundary of the Life board.

To compute the next generation of the game, the current board is first copied from the color buffer into an image texture, using the WebGL function gl.copyTexImage2D. Then a square is rendered that covers that entire color buffer. As the square is rendered, the fragment shader will be called once for each pixel in the color buffer. That pixel represents one cell in the game fo life. The fragment shader must compute the state of that cell in the next generation. It should set the color of the pixel to black if that cell is dead, and to white if the cell is alive.

The Fragment Shader Program

The only thing left to discuss is how to write the fragment shader program. The fragment shader will get the value of a vec2 varying variable named v_coords. This variable will hold the texture coordinates corresponding to the pixel that is being rendered. The job of the fragment shader is to decide whether that pixel is alive or dead in the next generation, and to set its color to white or black to represent its status. To do that, it needs to know the state of that pixel and its neighbors in the previous generation. The information that it needs is in the texture.

So, the fragment shader will need a sampler2D variable to access information in the texture. That sampler variable must be named source, since that is the name used for it in the JavaScript side of the program. The fragment shader can use the command

vec4 color = texture2D( source, v_coords );

to access the color of the pixel from the previous generation. It also needs to know the colors of the eight neighbors, and for that, it needs to get texture coordinates for each of the neighbors. Given a canvas size of 1024, the difference in texture coordinate from one pixel to a neighboring pixel is 1.0/1024.0. Use that fact to get the texture coordinates that you need for accessing the states of the eight neighboring cells.