Introduction to WebGL

Benoît Jacob (Mozilla Corporation)
FITC Spotlight Javascript, Toronto, March 24, 2012

Online copy of these slides:
http://people.mozilla.org/~bjacob/webgl-spotlight-js-2012

What is WebGL?

WebGL demos

2D game:
http://www.phoboslab.org/xtype

3D cars:
http://alteredqualia.com/three/examples/webgl_cars.html

Ported 3D game:
http://dl.dropbox.com/u/6873971/data/cube2/index.html

Path tracing demo:
http://madebyevan.com/webgl-path-tracing

Pure shader demo:
http://iacopoapps.appspot.com/hopalongwebgl

Physics simulations:
http://madebyevan.com/webgl-water
http://www.ibiblio.org/e-notes/webgl/gpu/fluid.htm

Photo editing demo:
http://evanw.github.com/webgl-filter

WebGL browsers

Chrome and Firefox: WebGL is enabled by default
Opera 12 alpha: WebGL enabled by default
Safari 5.1: WebGL can be enabled in developer menu

Very good compatibility across browsers, thanks to extensive conformance test suite (thanks especially to Google!)

WebGL and blocked GPU drivers

Browsers block old drivers for security, stability
Blacklists wiki page

With Firefox, about 50% of attempts to view WebGL content are successful

Browsers may provide a non-accelerated fallback. Chrome 18; Mozilla plans to use Mesa LLVMpipe.

Some sites fall back to Canvas 2D, or DOM-based rendering. Example: Angry Birds (DOM), Google Maps (DOM), Three.js (Canvas 2D).

WebGL: what to expect in 2012

Increasing % of users who get WebGL, especially on Mobile
All-around browser improvements to run games better
Compressed textures
More extensions: cross-context sharing, depth textures, perhaps multiple render targets.
WebGL in web workers and/or asynchronous operations
More content creation tools.
More porting tools.

Getting a WebGL context

var gl;
try {
  gl = canvas.getContext("experimental-webgl");
} catch(e) {}
Soon "experimental-webgl" will become "webgl". Prepare to try both.
Creating a WebGL context is slow (often > 5 ms, sometimes > 50 ms).

Runnable example

My first WebGL program

gl.clearColor(0.8, 0.2, 0.6, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
Runnable example

My first nontrivial WebGL program

A white triangle

A colored triangle

Understanding these examples is the goal of the rest of this presentation.

WebGL rendering pipeline

WebGL renders triangles

WebGL only draws points, line segments, and triangles.

In 99% of cases, that means triangles only.

For example, a rectangle can be drawn as two triangles.

WebGL native coordinate system

WebGL native coordinates go from -1 to +1.

X axis: -1 is the left end of the canvas, +1 is the right end.
Y axis: -1 is the bottom of the canvas, +1 is the top.

The Z axis is used for depth testing. -1 is nearest, +1 is farthest.

Vertex shaders

In WebGL, you don't say "Here are some vertex coordinates".

Instead, you say "Here is a program that computes each vertex's native coordinates".

That program is called the vertex shader.

The GPU iterates over vertices, runs the vertex shader on each.

Vertex attributes and uniforms

A vertex shader typically reads some data to compute a vertex's native coordinates.

Some of that data is specific to each vertex. Such vertex-dependent variables are called vertex attributes. Examples:

Some other variables are shared by all vertices. These are called uniforms. Examples:

A vertex shader for static 2D drawing

/* Declare a vertex attribute giving the vertex
 * position in native coordinates.
 */
attribute vec2 vertexPosition;

void main() {
  /* gl_Position is a built-in variable.
   * Assigning to it sets the resulting vertex
   * position in native coordinates.
   */
  gl_Position = vec4(vertexPosition, 0.0, 1.0),
}

A vertex shader for a scrolling 2D game

attribute vec2 vertexPosition;
uniform vec2 cameraPosition;

void main() {
  gl_Position = vec4(vertexPosition - cameraPosition, 0.0, 1.0),
}

A vertex shader for a 3D scene

attribute vec3 vertexPosition;
uniform mat4 modelview;
uniform mat4 projection;

void main() {
  gl_Position = projection * (modelview * vec4(vertexPosition, 1.0)),
}

Fragment shaders

In WebGL, you don't say "Here is the color to use for this pixel".

Instead, you say "Here is a program that computes each pixel's color".

That program is called the fragment shader.

The GPU iterates over pixels, runs the fragment shader on each.

A fragment shader painting all in white

/* Have to specify default precision in fragment shaders
 */
precision mediump float;

void main(void) {
  /* gl_FragColor is a built-in variable.
   * Assigning to it sets the resulting pixel color.
   */
  gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}

Passing data from the vertex to the fragment shader

Can be done by interpolating the vertex data for each fragment between vertices.

Example: each vertex has a color. Want to interpolate colors between vertices.

Such an interpolated variable is called a varying.

The vertex shader assigns to the varying. The fragment shader reads from it.

Vertex shader:
attribute vec2 vertexPosition;
attribute vec4 vertexColor;
varying vec4 varyingColor;

void main() {
  varyingColor = vertexColor;
  gl_Position = vec4(vertexPosition, 0.0, 1.0),
}

Fragment shader:
precision mediump float;

varying vec4 varyingColor;

void main() {
  gl_FragColor = varyingColor;
}

Setting up shaders

From the point of view of JavaScript, shader sources are just strings.

These are passed to WebGL and compiled in opaque 'shader' objects.

One then links together the vertex and fragment shader into an opaque 'program' object.

The program is the set of shaders used for drawing at a given time.
var vertexShaderString = 
'attribute vec2 vertexPosition;                  \n\
 void main(void) {                               \n\
   gl_Position = vec4(vertexPosition, 0.0, 1.0); \n\
 }                                               \n';

var fragmentShaderString =
'void main(void) {                          \n\
   gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); \n\
 }                                          \n';

var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderString);
gl.compileShader(vertexShader);

var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderString);
gl.compileShader(fragmentShader);

var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);

Setting up vertex attributes

The data of vertex attributes, such as vertex positions, is stored in special arrays called WebGL buffers.

One first sets up a WebGLBuffer object, and one then specifies how a given vertex attribute sources it.

One refers to a vertex attribute by means of its location in a given program, which is determined when the program is linked.
var vertexPositionAttrLoc = gl.getAttribLocation(program, "vertexPosition");
gl.enableVertexAttribArray(vertexPositionAttrLoc);

vertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
var vertices = [ 0.0,  1.0,
                -1.0,  -0.5,
                  1.0,  -0.5 ];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.vertexAttribPointer(vertexPositionAttrLoc, 2, gl.FLOAT, false, 0, 0);

The actual drawing

Once the program and its data sources (like vertex attributes) have been set up, drawing is simple:
gl.drawArrays(gl.TRIANGLES, 0, 3);
This completes the explanation of our White Triangle and Colored Triangle examples.

Continue learning

Famous tutorial:
http://learningwebgl.com/lessons/

Mozilla resources:
https://developer.mozilla.org/en/WebGL

Blogs:
http://planet-webgl.org
http://www.webgl.com

Mailing list:
http://groups.google.com/group/webgl-dev-list

Questions?