CS 424: Computer Graphics, Fall 2021
Lab 11: WebGL 1.0 2D and GLSL

This is our first lab using WebGL and GLSL. For now, we will stick to 2D and WebGL 1.0. The starting point for the lab is lab11.html, which is the only file that you will need today. You can find a copy in /classes/cs424.

Your work for this lab is due next Thursday. Please turn in the file lab11.html.

The project

The program in lab11.html currently shows a number of moving red squares that bounce off the edges of the canvas. The canvas fills the entire content area of the web browser. The squares also respond to the mouse: If you left-click or left-click-and-drag on the canvas, all of the square will head towards the position of the mouse. If you shift-left-click, the data for the points is reinitialized, so they start back at the center. You can pause and restart the animation by hitting the space bar.

The squares are in fact all part of one WebGL primitive of type gl.POINTS. Each square corresponds to one of the vertices of the primitive. Of course, the rendering is actually done by a vertex shader and a fragment shader. The source code for the shaders is in two constant multiline strings at the top of the program.

You will be modifying the shader code and the JavaScript code to implement several different styles for the point primitive. For example, it will be possible to draw the squares in different colors, to draw disks instead of squares, and so on. As a final step, you will make it possible to draw lines connecting the points. The user will control the program by hitting keys on the keyboard. It's up to you to decide which keys to use, but please document the interface in an appropriate comment on the doKey() function or at the top of the program.

The Javascript side of the program has three functions that you will need to work on: The function initGL() is called once when the program first starts and the functions updateForFrame() and render() are called once for each frame of the animation. The same set of commands would be legal in all of these, but initGL() is the best place to set up things that won't change during the run of the program, such as the locations of the uniform and attribute variables in the shader program; updateForFrame() is intended to be used for updating JavaScript variables that change from frame to frame; and render() is meant to do the actual WebGL drawing of the frame.

Color Attribute

In the original version of the program, all of the squares are red. The first exercise is to allow the possibility of assigning a different color to each square. Since the squares are really vertices in a single primitive of type gl.POINTS, you can use an attribute variable for the color. An attribute can have a different value for each vertex.

Your first task is to add a color attribute variable of type vec3 to the vertex shader, and use the values of the attribute to color the squares. You will also have to work on the JavaScript side. You will need a Float32Array to hold the color values on the JavaScript side, and you will need a WebGL buffer for the attribute. The program already has one attribute, which is used for the coordinates of the vertices. You will be doing something similar for the color attribute — except that you should do it all in initGL() since the color values won't change after they are created. You can use random values in the range 0.0 to 1.0 for the color components.

After you get multi-colored squares working, you should make the colors optional. (You might also allow other uniform colors for the points, in addition to red.) You can turn the use of the attribute value array on and off using the following commands, where a_color_loc is the id for the color attribute in the shader program:

gl.enableVertexAttribArray(a_color_loc);   // use the color attribute buffer

gl.disableVertexAttribArray(a_color_loc);  // don't use the buffer

When the attribute array is enabled, each vertex gets its own color, from the attribute buffer. When the attribute array is disabled, all vertices get the same color, and that value is set using the gl.vertexAttrib* family of functions. For example, to use a uniform red color, you can disable the vertex attribute array and set the color by calling

gl.vertexAttrib3f(a_color_loc, 1, 0, 0);  // set attrbute color to red

Let the user hit some specific keys to control the colors. The program has a doKey() function that is already set up to respond to keyboard input. You will be adding several types of keyboard interaction to the program. To respond to a key, you need to know the numeric keycode for that key. The doKey() function outputs the keycode to the console every time the user hits a key, and you can use that feature to discover any other keycodes that you need.

Point Style

Square points are boring. You should add the option of using different display styles for the points. Let the user select the style using the keyboard; for example, by hitting number keys, or by hitting some key to cycle through the possibilities.

The styles will have to be implemented in the fragment shader, and you will need a new uniform variable to tell the fragment shader which style to use. Add a uniform variable of type int to the fragment shader to control the point style, and add code to the fragment shader to implement the various styles. You will also need to add a variable on the JavaScript side for the location of the uniform variable, and you will need to call glUniform1i when you want to change the style.

The basic square should be one possible style. Some of the styles should be disks instead of squares. We saw in class how to draw a point as a disk by discarding some pixels:

float dist = distance( vec2(0.5), gl_PointCoord );
if (dist > 0.5) {
    discard;
}

You should also make use of alpha transparency in some of your styles. To enable use of the alpha component, you need to add the following lines to the initGL() function:

gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

With these settings, the alpha value of a color will be used for transparency in the usual way. In particular, one of your styles should show a point as a disk that fades from fully opaque at the center of the disk to fully transparent at the edge.

Other ideas include drawing a diamond shape or a ring (by discarding pixels except in a small range of values of dist). Be creative!

Line Primitive

To add some visual interest, make it possible to draw line segments connecting each point to the next. In fact, you can draw all the lines as a single primitive of type gl.LINE_LOOP, gl.LINE_STRIP, or gl.LINES, using the same vertex coordinates and colors that are used for the gl.POINTS primitive. The only difficulty is that the fragment shader will need to know whether it is drawing a point primitive or a line primitive, since gl_PointCoord is not defined for a line primitive, and all the code that you just wrote for point styles won't make sense for lines. (Another option would be to use another shader program for drawing the lines.)

You should make it possible to draw lines only, points only, or both lines and points. You might allow two styles for lines: gl.LINES and either gl.LINE_LOOP or gl.LINE_STRIP.

Note that line width can be set by calling gl.lineWidth(w), where the width, w, is given in pixels. Wide lines look better, but it is possible that the actual maximum line width is as small as one pixel.