Introduction to Computer Graphics with WebGL
A. From OpenGL To WebGL
Block Diagram of Typical Application / OpenGL / OS interactions.
OpenGL is the definitive cross-platform 3D library. It has a long history. It inspired Microsoft's Direct3D (D3D) API, and both evolved together from supporting fixed function graphics accelerators to supporting modern programmable hardware. The modern OpenGL style, and the one most like recent versions of (D3D), is known as Core Profile. A simplified predecessor to OpenGL's Core Profile features inspired OpenGL ES 2.0 - by far the most popular low level 3D API for mobile devices. OpenGL ES 2.0 was, in turn used as the basis for WebGL, which you will be learning in this class.
By learning WebGL, you will learn a low level graphics programming style that is common to all modern OpenGL flavours. Especially if you stick to the same programming language, switching between versions is not too hard, and porting code can be easy if you modularize properly.
Getting to the point where you can begin writing OpenGL code is a bit trickier as the process differs from operating system to operating system. Fortunately there are cross platform libraries that can make your code extremely simple to port. The above diagram shows the general case for getting a modern "Core" OpenGL environment. Regardless of how you choose to set up :
- The Application module is developed by you, the programmer
- Windowing API functions specifically developed to support OpenGL are used to set up, shut down, and handle event signals for an OpenGL drawing area called a Rendering Context (RC) in a window or component . These functions may be defined by the operating system, a third party widget set, or by a cross platform OpenGL library.
- OS Defined Libraries:
- Windows: WGL functions
- XWindows (Linux, most UNIXs): GLX functions
- Mac OS X: NSOpenGL classes (usually)
- Full Featured Cross Platform APIs and Environments with OpenGL support
- wxWidgets
- fltk
- tcl/tk
- Qt
- HTML 5 (through WebGL canvas)
- This is the API for this class. Everything you need is built in, including automatic selection of the best drivers for your platform. More on this later.
- Cross Platform OpenGL Libraries
- SDL
- GLFW
- GLUT (pronounced like the glut in gluttony)
- For years, GLUT was the API used in most OpenGL courses, including this one. Old lessons used classic fixed function OpenGL with GLUT 3.7, while newer lessons used freeglut or Apple GLUT to get access to Core Profile functionality.
- OpenGL API functions defined in gl.h and provided by the active OpenGL driver (specified by the RC) are used to draw.
- GLEW is a popular utility used to easily expose new OpenGL features. This is very important for Windows programs, since Microsoft stopped caring about OpenGL at version 1.1 (current version is 4.5). It is also useful for checking for OpenGL capabilities and extensions on other OSes.
- OpenGL Drivers implement the details of OpenGL functions either by passing data directly to 3D hardware, performing computations on the main CPU or some combination of the two.
When you write a WebGL program, many of these details are hidden from you. In fact, your WebGL calls may not even be executed by an OpenGL driver. On Macs, where OpenGL support is built-in to the OS, WebGL calls are translated directly into their OpenGL equivalents. On Windows, DirectX/D3D 9 or better has been built-in to the OS since Vista, so a special layer called ANGLE — developed by Google specifically for WebGL and used by Chrome, Firefox, IE11 and Edge — is used to translate WebGL API commands into equivalent D3D9 or D3D11 commands. On Linux, driver support is EXTREMELY important — most browsers only support nVidia's official drivers.
"Ideal" WebGL Application / OpenGL / OS interactions. | Windows WebGL Application / "OpenGL" / OS interactions. |
B. Overview of an Interactive Program.
Model-View-Controller Architecture
Interactive program design entails breaking your program into three parts:- The Model: all the data that are unique to your program reside there. This might be game state, the contents of a text file, or tables in a database.
- The View: it is a way of displaying some or all of the model's data to user. Objects in the game state might be drawn in 3D, text in the file might be drawn to the screen with formatting, or queries on the database might be wrapped in HTML and sent to a web browser.
- The Controller: the methods for users to manipulate the model or the view. Mouse actions and keystrokes might change the game state, select text, or fill in and submit a form for a new query.
Object-oriented programming was developed, in part, to aid with modularising the components of Model-View-Architecture programs. Most user interface APIs are written in an Object Oriented language.
You will be structuring your programs to handle these three aspects of an interactive program. A 3D graphics:- Model consists of descriptions of the geometry, positioning, apperance and lighting in your scene. Ideally these could be saved to and loaded from a file, but we won't do that much in this module.
- View consists of the OpenGL rendering calls that set up your rendering context, interpret the model, and send things to be drawn.
- Controller is usually a set functions you write that respond to event signals sent to your program via the Windowing API.
The Main Events
All OpenGL programs should respond to at least two events:- Setup - There are at least two things to do here
- Acquire Rendering Context: how you perform this step is determined by your Windowing API.
- Set
initial OpenGL scene settings:
OpenGL's default settings are useless. An important part of the drawing pipeline, the shader program is missing. You will need to load, compile and activate one. It is also desirable to preload some objects you want to draw into data buffers and connect them to your shader programs. You may also need to enable, disable or configure various OpenGL settings to suit your rendering needs.
- Rendering
- the function that does this is Windowing API specific, but the OpenGL
code will be completely generic and should be copy/paste portable.
C. Your First WebGL Program
1. Set Up Your Folders
- Create a root folder for all your modules. Name it something meaningful like "WebGL_Mods".
- Create a folder inside called "Common"
- Copy all the files from Dr. Angel's Common folder to your Common folder. Make sure you use the latest version so you get his bugfixes.
- What are these files? Read the README.txt file!
- Create a folder for this week's module. Name it something meaningful like "Mod1".
- Save the HTML and the associated javascript file into your Mod1 folder.
- Use your favourite web browser to open the HTML file you saved. It should "just work".
2. Create the HTML File
The HTML file for a WebGL application will link in necessary javascript files and resources. Some resources, such as shaders, can be defined in-line. The following is a commented minimal HTML file for a textbook style WebGL application:
<!DOCTYPE html> <html> <head> <title>WebGL Template</title> <!-- This in-line script is a vertex shader resource Shaders can be linked from an external file as well. --> <script id="vertex-shader" type="x-shader/x-vertex"> // All vertex shaders require a position input or "attribute". // The name can be changed. // Other attributes can be added. attribute vec4 vPosition; void main() { // gl_Position is a built-in vertex shader output or "varying". // Its value should always be set by the vertex shader. // The simplest shaders just copy the position attribute straight to // gl_Position gl_Position = vPosition; } </script> <!-- This in-line script is a fragment shader resource. Shaders can be linked from an external file as well. --> <script id="fragment-shader" type="x-shader/x-fragment"> // Sets default precision for floats. // Required, since fragment shaders have no default precision. // Choices are lowp and mediump. precision mediump float; void main() { // gl_FragColor is a built-in fragment shader output // In general it should be set, but this is not required. // The default gl_FragColor is undefined gl_FragColor = vec4(0,0,0,1); } </script> <!-- These are external javascript files. The first three are the textbook libraries. The last one is your own javascript code. Make sure to change the name to match your javascript file. --> <script type="text/javascript" src="../Common/webgl-utils.js"></script> <script type="text/javascript" src="../Common/initShaders.js"></script> <script type="text/javascript" src="../Common/MVnew.js"></script> <script type="text/javascript" src="yourWebGLJavascript.js"></script> </head> <body> <!-- This is the canvas - the only HTML element that can render WebGL graphics. You can have more than one WebGL canvas on a web page, but that gets tricky. Stick to one per page for now. --> <canvas id="gl-canvas" width="512" height="512"> Oops ... your browser doesn't support the HTML5 canvas element </canvas> </body> </html>
Using your favorite editor, create a new HTML file called Mod1Demo.html in the Mod1 folder and paste the above code into it.
3. Create the Javascript File
The javascript file will breathe life into your WebGL application. It sets up the WebGL rendering context, does the drawing and defines responses to various events. The simplest WebGL javascript program will set up the rendering context after the HTML has been loaded by defining an action for window.onload event. It can also draw if no animation is needed.
The following javascript defines a very minimalistic template WebGL program:
// This variable will store the WebGL rendering context
var gl;
window.onload = function init() {
// Set up a WebGL Rendering Context in an HTML5 Canvas
var canvas = document.getElementById("gl-canvas");
gl = WebGLUtils.setupWebGL(canvas);
if (!gl) {
alert("WebGL isn't available");
}
// Configure WebGL
// eg. - set a clear color
// - turn on depth testing
// Load shaders and initialize attribute buffers
var program = initShaders(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);
// Set up data to draw
// Load the data into GPU data buffers
// Associate shader attributes with corresponding data buffers
// Get addresses of shader uniforms
// Either draw as part of initialization
//render();
// Or draw just before the next repaint event
//requestAnimFrame(render());
};
function render() {
// clear the screen
// draw
}
Using your favorite editor, create a new javascript file called Mod1Demo.js in the Mod1 folder and paste the above code into it. Don't forget to edit the .html file so it knows about your new .js file!
4. Add More Code to Draw a Coloured Triangle
Load the HTML file in your WebGL capable web browser. You will see nothing. This is the correct behaviour. Let's add the code to draw a blended colour triangle, starting with the necessary javascript, then moving to the shaders.
In this part, add or change the highlighted code in the template code.
// This variable will store the WebGL rendering context var gl; window.onload = function init() { // Set up a WebGL Rendering Context in an HTML5 Canvas var canvas = document.getElementById("gl-canvas"); gl = WebGLUtils.setupWebGL(canvas); if (!gl) { alert("WebGL isn't available"); } // Configure WebGL // eg. - set a clear color // - turn on depth testing// This light gray clear colour will help you see your canvas gl.clearColor(0.9, 0.9, 0.9, 1.0);
// Load shaders and initialize attribute buffers var program = initShaders(gl, "vertex-shader", "fragment-shader"); gl.useProgram(program); // Set up data to draw// Here, 2D vertex positions and RGB colours are loaded into arrays. var vertices = [ -0.5, -0.5, // point 1 0.5, -0.5, // point 2 0.0, 0.5 // point 2 ]; var colors = [ 1, 0, 0, // red 0, 1, 0, // green 0, 0, 1 // blue ];
// Load the data into GPU data buffers// The vertex array is copied into one buffer var vertex_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); gl.bufferData(gl.ARRAY_BUFFER, flatten(vertices), gl.STATIC_DRAW); // The colour array is copied into another var color_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer); gl.bufferData(gl.ARRAY_BUFFER, flatten(colors), gl.STATIC_DRAW);
// Associate shader attributes with corresponding data buffers//Here we prepare the "vPosition" shader attribute entry point to //receive 2D float vertex positions from the vertex buffer var vPosition = gl.getAttribLocation(program, "vPosition"); gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vPosition); //Here we prepare the "vColor" shader attribute entry point to //receive RGB float colours from the colour buffer var vColor = gl.getAttribLocation(program, "vColor"); gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer); gl.vertexAttribPointer(vColor, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vColor);
// Get addresses of shader uniforms// None in this program...
//Either draw once as part of initializationrender();
//Or schedule a draw just before the next repaint event //requestAnimFrame(render()); }; function render() { // clear the screen// Actually, the WebGL automatically clears the screen before drawing. // This command will clear the screen to the clear color instead of white. gl.clear(gl.COLOR_BUFFER_BIT);
// draw// Draw the data from the buffers currently associated with shader variables // Our triangle has three vertices that start at the beginning of the buffer. gl.drawArrays(gl.TRIANGLES, 0, 3);
}
Reload the HTML page. The result should look like this:
What your project should look like. If you see nothing and you are sure you copy/pasted everything correctly, make sure your HTML is referencing your javascript file instead of the template's dummy one.
Now we're getting somewhere. The reason the triangle is black is that the shaders in the HTML file are hardcoded to paint everything black. Check your web browser's error console. Your WebGL object may have reported errors when you called vertexAttribPointer() and enableVertexAttribArray() with the invalid vColor returned by getAttribLocation(). We need to modify the shaders to make them aware of the colour buffer. Make the following changes to the vertex and fragment shaders:
// All vertex shaders require a position input or "attribute". // The name can be changed. // Other attributes can be added. attribute vec4 vPosition;attribute vec4 vColor;
// This "varying" output is interpolated between the vertices in the // primitive we are drawing before being sent to a varying with the // same name in the fragment shader varying vec4 varColor;
void main() { // gl_Position is a built-in vertex shader output or "varying". // Its value should always be set by the vertex shader. // The simplest shaders just copy the position attribute straight to // gl_Position gl_Position = vPosition;varColor = vColor;
}
// Sets default precision for floats. // Required, since fragment shaders have no default precision. // Choices are lowp and mediump. precision mediump float;varying vec4 varColor;
void main() { // gl_FragColor is a built-in fragment shader output // In general it should be set, but this is not required. // The default gl_FragColor is undefinedgl_FragColor = varColor;
}
Extended Resources
Save