Skip to content

Instantly share code, notes, and snippets.

@thomaswilburn
Last active October 14, 2017 12:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save thomaswilburn/6128987 to your computer and use it in GitHub Desktop.
Save thomaswilburn/6128987 to your computer and use it in GitHub Desktop.
Annotated WebGL sample.
/*
We're going to start by adding a canvas to the page and getting a context for it.
*/
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 320;
document.body.appendChild(canvas);
var gl = canvas.getContext("experimental-webgl");
/*
WebGL doesn't know how to draw anything onscreen out of the box. We have to
teach it via shaders--programs that set the coordinates of polygon corners
(vertex) and set the color for each pixel inside (fragment).
Shaders are written in a C-like language named GLSL. We'll set up the source
code in a set of strings, then compile the shaders.
*/
var vertexSource =
//declare variables that we'll pass in from JavaScript
"attribute vec2 a_position;" +
"attribute vec3 a_color;" +
"varying vec3 v_color;" +
//main() is the entry point to the shader
"void main() {" +
//gl_Position is the final vertex position in screen-space, which
//ranges from -1 to 1. It's basically x, y, z, w, the latter two
//you don't need to worry about for 2D.
"gl_Position = vec4(a_position, 1, 1);" +
//Pass the color in to the fragment shader from the attribute
"v_color = a_color;" +
"}";
var vertex = gl.createShader(gl.VERTEX_SHADER);
//plug the source into the new shader and compile it
gl.shaderSource(vertex, vertexSource);
gl.compileShader(vertex);
//if something goes wrong, we can ask for the info log for each shader
console.log( gl.getShaderInfoLog(vertex) );
var fragmentSource =
//precision is required, but mediump is fine as a default
"precision mediump float;" +
"varying vec3 v_color;" +
"void main() {" +
//The fragment shader is easier: we just set the pixel color directly.
//we'll add the alpha as the fourth component
"gl_FragColor = vec4(v_color.r, v_color.g, v_color.b, 1);" +
"}";
var fragment = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragment, fragmentSource);
gl.compileShader(fragment);
/*
A GL program is the combination of vertex and fragment shader used to
draw shapes onscreen. We take our compiled shaders, attach them to the program,
link it all together, then tell WebGL that this is the program we want to use.
Later on, you can make different programs and switch between them, depending on
what you're drawing, but for right now we'll stick to one.
*/
var program = gl.createProgram();
gl.attachShader(program, vertex);
gl.attachShader(program, fragment);
gl.linkProgram(program);
gl.useProgram(program);
/*
There are three kinds of variables in GLSL:
- uniform: gets passed in to both fragments and vertexes
- attribute: gets passed in to the vertex shader as an array of values
- varying: set from the vertex shader, used in the fragment shader
We don't use uniforms in this sample, but they're pretty simple. Let's set up
an array of attribute values, for the corners and colors of a triangle.
*/
//create the empty buffer
var buffer = gl.createBuffer();
var bufferData = [
//structured as x, y, r, g, b
0, 0.5, 1, 0, 0, //top corner, red
-0.5, -.3, 0, 1, 0, //left corner, green
0.5, -0.3, 0, 0, 1 //right corner, blue
];
//tell WebGL that the next command applies to the buffer we created.
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
//fill the buffer with our data (converted to a Float32Array)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(bufferData), gl.STATIC_DRAW);
//STATIC_DRAW sets WebGL to optimize for drawing from this data once
/*
Now we want to tell the WebGL context how to interpret our buffer, which is
organized as two coordinate values followed by three color values. We're still
bound to the buffer, so we'll ask WebGL for the ID of each attribute we used in
the vertex shader, then declare their position in the array.
*/
//get the id for the a_position variable
var a_position = gl.getAttribLocation(program, "a_position");
//Why do we have to enable it? It's complicated, but we do.
gl.enableVertexAttribArray(a_position);
//declare its position in the buffer (see below)
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 20, 0);
/*
The arguments for gl.vertexAttribPointer() are as follows:
- the variable id that we got from gl.getAttribLocation()
- the size of the variable (a_position is a vec2, meaning an array of 2 values)
- the type of the variable. Most GL values are FLOATs
- whether the value is normalized. Assume this is false for now.
- the distance between values--in this case, each vertex gets 5 floats, each
of which is 4 bytes. So our "stride" is 20 bytes from one coordinate pair to
the next.
- how far the value is offset from the start of the array--in this case, 0.
*/
//get the next variable id
var a_color = gl.getAttribLocation(program, "a_color");
gl.enableVertexAttribArray(a_color);
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 20, 8);
/*
In the case of the color, the stride is the same--20 bytes between each set of
values--but the color values start after 8 bytes (2 floats) of coordinates.
Finally, we'll draw our polygon by calling gl.drawArrays. We're going to draw
a triangle onscreen. Most of the time, WebGL wants us to draw triangles because
they're unambiguous--there's only one possible polygon they could be. If we
want a rectangle, we have to draw two triangles.
*/
//draw triangles, starting at location 0 and proceeding for three vertices
gl.drawArrays(gl.TRIANGLES, 0, 3);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment