Create a gist now

Instantly share code, notes, and snippets.

@samliu /webgl_triangle.html Secret
Last active Mar 7, 2016

Intro to WebGL: Source
<html>
<title>Sammy Learns WebGL</title>
<head>
<style type="text/css">
#main-canvas {
width: 600px;
height: 600px;
}
</style>
</head>
<body>
<!-- This will be the canvas in which we draw stuff. -->
<canvas id="main-canvas"></canvas>
<!-- Vertex (position calculator, 3D -> clip space) Shader GLSL code will go here. -->
<script id="vertex-shader" type="x-vertex/x-shader">
// "Attribute" is the primary input to the vertex shader. We give it a vec2
// (array) of values. OpenGL loops over them, calling main() once per element.
// Function returns nothing, only sets a local variable, gl_Position.
//
// gl_Position expects a vec4 (x, y, z, w) rather than a vec2 (x, y).
//
// "z" is like z-index in HTML, and "w" is a value that every other axis is
// divided by.
//
// w = 1.0 means nothing is affected / rescaled.
// z = 0 also does nothing.
attribute vec2 vertexCoord;
void main() {
// Does nothing except return input unchanged.
gl_Position = vec4(vertexCoord, 0.0, 1.0);
}
</script>
<!-- Fragment shader (color of each pixel calculator) GLSL code will go here. -->
<script id="fragment-shader" type="x-fragment/x-shader">
// Once the vertex shader has set enough points to draw a triangle, the fragment
// shader will be called once per pixel WITHIN the triangle!
//
// For now, let's always return blue, which is (0, 0, 1, 1).
void main() {
// Returns blue.
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
</script>
<script type="text/javascript">
/* * * * * * * * * * * * * * * * *
* WebGL here!
*
* OpenGL only deals with (2) kinds of data:
* 1. Geometry data (vertices).
* 2. Pixel data (textures, render buffers).
*
* Mozilla's WebGL API is thorough:
* https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API
* * * * * * * * * * * * * * * * */
var app = function() {
// WebGL Docs: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext
var canvas = document.getElementById('main-canvas');
// Some browsers only have experimental support; this will use the normal
// WebGL context for our canvas if it exists. If there's only experimental
// support, it'll use that instead.
var glContext = canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl');
// Hurrah! Now we have the WebGL rendering context.
// Next we make a dictionary containing our shaders. Shader code should be
// represented as strings. We could write this inline but cleaner to just
// use script tags in the body that we import here as shown.
//
// Remember, shader code is written in GLSL and compiled!!!
var shaders = {
'vertexMain': document.getElementById('vertex-shader').innerHTML,
'fragmentMain': document.getElementById('fragment-shader').innerHTML,
}
// Next we need WebGLProgram: holds info on which shaders we're using
// + what data has been passed. We need to compile + link our shaders at
// initialization time.
//
// (3) things needed to compile shaders.
// 1. Get shader source (passed in with params!)
// 2. Determine if it's vertex or fragment (we noted this in the dict!)
// 3. Call the appropriate methods on our `WebGLRenderingContext`.
//
// Once we have both shaders, we create the program and link the shaders.
// This object does that.
//
// Params:
// WebGL Context: WebGLRenderingContext
// Dictionary containing shader source code strings: object
var WebGLCompiler = function(glContext, shaders) {
// Helper functions of this function in javascript execute in the global
// scope, and so don't know this function's context! In other words,
// their "this" variable will be the global "this". Instead, we create a
// variable "that" that contains the "this" we want them to have. This
// is a shitty thing in javascript. Douglas Crockford says this is the
// pattern to use in "Javascript: The Good Parts" pg 28. He also admits
// it is a mistake. Get used to it, I guess...
//
// Fucking javascript.
var that = {};
that.shaders = shaders || {};
that.glContext = glContext;
// compileShader() compiles a shader given the source and type of shader.
//
// Params:
// shaderSource: string
// shaderType: glContext's VERTEX_SHADER or FRAGMENT_SHADER.
that.compileShader = function(shaderSource, shaderType) {
var shader = this.glContext.createShader(shaderType);
this.glContext.shaderSource(shader, shaderSource);
this.glContext.compileShader(shader);
return shader;
}
// createVertexShader() compiles and returns a VERTEX_SHADER from
// source.
//
// Params:
// shaderName: string
that.createVertexShader = function(shaderName) {
var source = this.shaders[shaderName];
if (!source) {
throw "Shader not found #{shaderName}";
}
return this.compileShader(source, this.glContext.VERTEX_SHADER);
}
// createFragmentShader() compiles and returns a FRAGMENT_SHADER from
// source.
//
// Params:
// shaderName: string
that.createFragmentShader = function(shaderName) {
var source = this.shaders[shaderName];
if (!source) {
throw "Shader not found #{shaderName}";
}
return this.compileShader(source, this.glContext.FRAGMENT_SHADER);
}
// createProgram() takes two compiled shaders, attaches them to a GL
// program, links them, and returns the gl Program.
//
// Params:
// vertextShader: compiled WebGL shader
// fragmentShader: compiled WebGL shader
that.createProgram = function (vertexShader, fragmentShader) {
var program = this.glContext.createProgram();
this.glContext.attachShader(program, vertexShader);
this.glContext.attachShader(program, fragmentShader);
this.glContext.linkProgram(program);
// If link failed, crash.
if (!this.glContext.getProgramParameter(program, this.glContext.LINK_STATUS)) {
error = this.glContext.getProgramInfoLog(program);
console.error(error);
throw "Program failed to link. Error: #{error}";
}
return program;
}
// Function that ties them all together!
//
// createProgramWithShaders() compiles the shaders from source, links
// them to a program, and returns the program.
//
// Params:
// vertex shader name: string
// fragment shader name: string
that.createProgramWithShaders = function (vertexShaderName, fragmentShaderName) {
var vertexShader = this.createVertexShader(vertexShaderName);
var fragmentShader = this.createFragmentShader(fragmentShaderName);
return this.createProgram(vertexShader, fragmentShader);
}
return that;
}
// Use the compiler object to compile the WebGL program.
compiler = WebGLCompiler(glContext, shaders);
// If the compile functions, this variable will contain a WebGLProgram
// instance.
program = compiler.createProgramWithShaders('vertexMain', 'fragmentMain');
// OK, go read the shader(s) code if you haven't already. We're going to
// send data to the GPU now! :]
// Wire up our program to our rendering context:
// 1. Pass in the data.
// 2. Draw a triangle.
// Make sure screen is in consistent state. clearColor() tells GPU what
// color to use for pixels where we DON'T draw anything. ("The color of
// clear"). (1, 1, 1) is white.
//
// Then clear() resets the canvas so nothing has been drawn.
// COLOR_BUFFER_BIT is a constant indicating the buffers currently enabled
// for color writing; it should be everything in the canvas. There's a
// "depth buffer" as well, which has a similar DEPTH_BUFFER_BIT.
glContext.clearColor(1.0, 1.0, 1.0, 1.0);
glContext.clear(glContext.COLOR_BUFFER_BIT);
// Use the program (comprised of the VERTEX + FRAGMENT shaders).
glContext.useProgram(program);
// Now to give our program some input data by creating a buffer (address
// in mem where we can shove arbitrary # of bits).
buffer = glContext.createBuffer();
glContext.bindBuffer(glContext.ARRAY_BUFFER, buffer);
// OpenGL is very state-aware: it will use the last buffer that was bound
// using bindBuffer() instead of asking us for a pointer to the buffer in
// bufferData().
//
// To be clear: bufferData() writes data into whatever buffer was last
// bound; in this case, the one we just created.
//
// STATIC_DRAW is a performance hint that says "this data is going to be
// used often, but won't change much."
glContext.bufferData(
glContext.ARRAY_BUFFER,
new Float32Array([
0.0, 0.8,
-0.8, -0.8,
0.8, -0.8,
]),
glContext.STATIC_DRAW
);
// Now that the data is in memory, we need to tell OpenGL what attribute
// to use it for & how to interpret the data. Right now the buffer is just
// considered a bucket of arbitrary bits.
// Get the location of the attribute: it's a numeric index based on the
// order we use it in the program; in this case 0.
vertexCoord = glContext.getAttribLocation(program, "vertexCoord");
// Takes the location of an attribute and tells us that we want to use the
// data we're going to populate it with.
glContext.enableVertexAttribArray(vertexCoord);
// Populate the attribute with the currently bound buffer + tell it how to
// interpret the data. So essentially, the GPU copies the data from that
// buffer into its local variable; and here we tell it "make your
// local variable a FLOAT" (along with other stuff).
//
// glContext.vertexAttribPointer(
// # Which attribute to use
// vertexCoord
//
// # The number of floats to use for each element. Since it's a vec2, every
// # 2 floats is a single vector.
// 2
//
// # The type to read the data as
// glContext.FLOAT
//
// # Whether the data should be normalized, or used as is
// false
//
// # The number of floats to skip in between loops
// 0
//
// # The index to start from
// 0
// )
glContext.vertexAttribPointer(vertexCoord, 2, glContext.FLOAT, false, 0, 0);
// OK at this point you're done giving it data. Now we can draw something
// to the screen!
// drawArrays() loops through the attribute data in the given order.
//
// The first arg is method to use for drawing: in this case, TRIANGLES.
// TRIANGLES means it should use every 3 points as a surface.
//
// The second arg is what element in the buffer to start from; in this
// case 0.
//
// The final arg is # of points we will draw (3 for a triangle).
// Now it draws it!
glContext.drawArrays(glContext.TRIANGLES, 0, 3);
} // app
// Draw when DOM is ready.
window.requestAnimationFrame(app);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment