CS424 Notes, 9 April 2012
- OpenGL Version 1.0
- For fun, let's look at some OpenGL 1.0 code. We use the C API.
- OpenGL is not a windowing API. It has no standard way to create a window or to associate an OpenGL drawing context with a region on the screen. Each implementation has its own way of doing that. However, GLUT is a cross-platform toolkit that provides basic OpenGL windows.
- Here's a
main program in C using GLUT:
int main(int argc, char **argv) { glutInit(&argc,argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(600,600); // Set the size of the window. glutInitWindowPosition(150,50); // Upper left corner of window. glutCreateWindow("OpenGL Window"); // Title displayed in window title bar. glutDisplayFunc(display); // Set the function that does the drawing. glutReshapeFunc(reshape); // Set the function that reacts to window resizing. init(); // do any OpenGL and program initialization glutMainLoop(); }
There is a function void display() that will be called whenever the content of the window needs to be drawn, and a function void reshape(int w, int h) that will be called when the user resizes the widow. The call to glutInitDisplayMode says that the display will use RGBA color (as opposed to RGB or grayscale), will be double-buffered, and will have a depth buffer. Double-buffering means that the drawing will actually be done off-screen, to a "back buffer." After drawing, the back buffer is "swapped" with the front buffer, which appears on-screen. (Double-buffering is automatic in WebGL.) - Here is a 2D display function for the standard first example, a triangle
with vertices of three different colors:
void display() { glClearColor(1.0,1.0,1.0,1.0); // Set the color to use in glClear. glClear(GL_COLOR_BUFFER_BIT); // Fill the window with the color. glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-5,5,-5,5,-1,5); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glBegin(GL_POLYGON); // Draw a picture glColor3f(1,0,0); glVertex2f(0,4); glColor3f(0,1,0); glVertex2f(-4,-3); glColor3f(0,0,1); glVertex2f(4,-3); glEnd(); glutSwapBuffers(); // Makes the drawing appear on the screen! }
You can understand the first two lines. The next five lines set up the transformation matrices. The code between glBegin and glEnd does the drawing by specifying vertices and attributes one at a time. The last line swaps the front and back buffers. - GL_POLYGON is a primitive, which produces a polygon with any number of vertices. The original OpenGL has the seven line, point, and triangle primitives that you are familiar with. It also has GL_POLYGON, for drawing convex polygons, and two primitive for drawing quadrilaterals, GL_QUADS and GL_QUAD_STRIP.
- There are many more functions in standard OpenGL than in WebGL.
You need several functions -- and a lot of constants --
just to manage light and material properties. Consider
glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color );
where color is an array of 4 numbers. GL_FRONT_AND_BACK means that you are applying the color to both the front and back sides of the primitive. (It is possible to have different material properties on the two sides.) GL_AMBIENT_AND_DIFFUSE means that you are setting both properties; you can set the separately, and you can also set GL_EMISSION and GL_SPECULAR. - Already in OpenGL 1.1, you could draw using glDrawArrays and glDrawElements, and the use of glBegin and glEnd were discouraged. However, learning always started with glBegin/glEnd since they are so much easier to understand. They were deprecated in OpenGL 3.0 and removed in OpenGL 4.0 and OpenGL ES 2.0 (and hence in WebGL), and textbook writers don't seem to have decided yet what to do about their loss.
- The programmable pipeline was introduced in OpenGL 2.0. Both it and a great part of the API are gone from OpenGL 4.0 and WebGL.
- Creating a WebGL context and program.
- Next, we are going to fill in some parts of OpenGL that we haven't paid much attention to.
- The WebGL context, conventionally referred to as gl, is used for
drawing on an HTML canvas element. It should be created by calling
c.getContext("webgl"), where c refers to the canvas element.
In fact, however, you have to use "experimental-webgl"
as the parameter is a lot of browsers. Here is my method for creating the
context.
/** * Create a WebGL drawing context for a canvas element. The parameter can * be either a string that is the id of a canvas element, or it can be the * canvas element itself. */ function createWebGLContext(canvas) { var c; if ( ! canvas ) throw "Canvas required"; if (typeof canvas == "string") c = document.getElementById(canvas); else c = canvas; if ( ! c.getContext ) throw "No legal canvas provided"; var gl = c.getContext("webgl"); if ( ! gl ) { gl = c.getContext("experimental-webgl"); } if ( ! gl ) throw "Can't create WebGLContext"; return gl; }
This method throws an error if it can't create gl. (In JavaScript, you can throw anything; I usually throw a string that describes the error.) - However, getContext can take a second parameter that specifies certain
properties that you want for the context. For example, you can say whether
or not you want a depth buffer. Here is an example specifying the possible
options, with their default values:
var options = { alpha: true, // Use RGBA rather than RGB colors. depth: true, // Provide a depth buffer. stencil: false, // Provide a stencil buffer. antialias: true, // Use antialiasing, if available. premultipliedAlpha: true, preserveDrawingBuffer: false // Keep image after copying to screen. }; var gl = c.getContext("webgl",options);
You only have to provide non-default values. For example, if you are doing 2D drawing, you don't need a depth buffer, so you might use c.getContext("webgl",{depth:false}). - We should also look at creating a shader program. A program is always
associated with a WebGL context, gl. You have to compile the vertex
and fragment shaders separately and then link them, and you have to check
for errors at each step:
/** * Creates a program for use in the WebGL context gl, and returns the * identifier for that program. If an error occurs while compiling or * linking the program, an exception of type String is thrown. The * string contains the compilation or linking error. If no error occurs, * the program identifier is the return value of the function. */ function createProgram(gl, vertexShaderSource, fragmentShaderSource) { var vsh = gl.createShader( gl.VERTEX_SHADER ); gl.shaderSource(vsh,vertexShaderSource); gl.compileShader(vsh); if ( ! gl.getShaderParameter(vsh, gl.COMPILE_STATUS) ) { throw "Error in vertex shader: " + gl.getShaderInfoLog(vsh); } var fsh = gl.createShader( gl.FRAGMENT_SHADER ); gl.shaderSource(fsh, fragmentShaderSource); gl.compileShader(fsh); if ( ! gl.getShaderParameter(fsh, gl.COMPILE_STATUS) ) { throw "Error in fragment shader: " + gl.getShaderInfoLog(fsh); } var prog = gl.createProgram(); gl.attachShader(prog, vsh); gl.attachShader(prog, fsh); gl.linkProgram(prog); if ( ! gl.getProgramParameter( prog, gl.LINK_STATUS) ) { throw "Link error in program: " + gl.getProgramInfoLog(prog); } return prog; }
(There should be a way to separately compile fragments, such as functions, independently of the vertex and fragment shaders and link them to the program, which would increase the flexability.) - DISCUSSION: Suppose that you want to use more than one shader program in a WebGL application. How would you manage the programs and their uniform and attribute variable locations?
- Gets
- You can read the value of many WebGL settings using gl.getParameter(prop) where prop is a constant specifying the property whose value you want to read. One use for this is to check the value of certain limits such as the number of texture units (prop = gl.MAX_TEXTURE_IMAGE_UNITS), the number of attribute variables (prop = gl.MAX_VERTEX_ATTRIBUTES), and the maximum number of uniform vectors in the shaders (prop = gl.MAX_VERTEX_UNIFORM_VECTORS and prop = gl.MAX_FRAGMENT_UNIFORM_VECTORS).
- Another use is to retrieve values set by other functions. For example, use gl.getParameter(gl.LINE_WIDTH) to get the value that was set by gl.lineWidth.
- You can get the current value of a uniform variable in a shader program, prog, by calling gl.getUniform(prog,uLoc), where uLoc is the uniform location as returned by gl.getUniformLocation. The type of the return value depends on the type of the uniform. This works even for uniform matrix variables, where the return value is a Float32Array.
- DISCUSSION: Could we use this to manage state while traversing a scene graph. For example,
if there is a uniform variable representing diffuse color with uniform location uDiffuseLoc:
var savediffuse = gl.getUniform(prog, uDiffuseLoc); // save current color gl.uniform4fv(uDiffuseLoc, diffuseColor); // set this object's diffuse color . . // draw the object . gl.uniform4fv(uDiffuseLoc, savediffuse); // restore the saved color.