---
title: An Introduction to WebGL
teaser: WebGL is a thin wrapper around OpenGL that is exposed through a JavaScript
  API.
tags: web,javascript,webgl
author: Sage Griffin
published_on: 2014-09-05
---

On [a recent project][martial-codex] we had to do a lot of work with WebGL. The
most difficult and frustrating thing about the project was the lack of good
resources on working with WebGL, especially for non-trivial projects. What I did
find was usually focused on just having code that puts pixels on the screen, but
not how it worked or why. If you're interested in learning WebGL, these posts
will take you from zero to a working 3D application, with an emphasis on how the
various concepts work and why.

[martial-codex]: https://www.martialcodex.com/player/play/50/item/216

## What is WebGL

WebGL is a thin wrapper around OpenGL ES 2.0 that is exposed through a
JavaScript API. OpenGL is a low level library for drawing 2D graphics. This was
a major misconception for me. I always thought that it was used to produce 3D.
Instead, our job is to do the math to convert 3D coordinates into a 2D image.
What OpenGL provides for us is the ability to push some data to the GPU, and
execute specialized code that we write on the GPU rather than the CPU. This code
is called a shader, and we write it in a language called GLSL.

To get started, we need to understand a few core concepts.

- *Clip Space*: This will be the coordinate system we use in our final output.
  It is represented as a number between -1 and 1, regardless of the size of the
  canvas. This is how the GPU sees things.
- *Pixel Space*: This is how we commonly think about graphics, where X is a
  number between 0 and the width of the canvas, and Y is a number between 0 and
  the height of the canvas.
- *Vertex Shader*: This is the function which is responsible for converting our
  inputs into coordinates in clip space to draw on the screen.
- *Fragment Shader*: This is the function which is responsible for determining
  the color of each pixel we told the GPU to draw in the vertex shader.

## Boilerplate

We need to write a bit of boilerplate to get everything wired up to start
drawing on the screen. The first thing we'll need is a canvas tag.

```html
<canvas width="600" height="600">
</canvas>
```

In our JavaScript code, we need to find the canvas, and use it to get an
instance of `WebGLRenderingContext`. This is the object that contains all of the
OpenGL methods we are going to use. The [documentation for WebGL][mdn-webgl] is
generally quite lacking, but every method and constant maps to an equivalent
method in the C API. The function `glVertexAttrib1f` in C would be
`gl.vertexAttrib1f` in WebGL, assuming the variable `gl` is your
`WebGLRenderingContext`. The constant `GL_STATIC_DRAW` in C would be
`gl.STATIC_DRAW` in WebGL.

[mdn-webgl]: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext

```coffeescript
main = ->
  canvas = document.getElementByTagName("canvas")
  gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl")
```

The next thing we're going to need is an instance of `WebGLProgram`. This is an
object that will hold information about which shaders we're using, and what data
we've passed into it. As we initialize the program, we are going to want to
compile and link our shaders. We're going to need the source code of the shaders
as a string. I prefer to write them in separate files, in order to get syntax
highlighting and other file type specific helpers from my editor. In a Rails
app, we can just spit out the files into the page server side.

```ruby
module ApplicationHelper
  def shaders
    shaders = {}
    Dir.chdir(Rails.root.join("app", "assets", "shaders")) do
      Dir["**"].each do |path|
        shaders[path] = open(path).read
      end
    end
    shaders
  end
end
```

```erb
<% # app/assets/layouts/application.html.erb %>
<script>
  window.shaders = #{shaders.to_json.html_safe}
</script>
```

## Compiling the Shaders

Now in our JavaScript, we can create a few helper functions to create a program,
compile our shaders, and link them together. To compile the shaders, we need to
do three things:

1. Get the source of the shader
1. Determine if it's a vertex or fragment shader
1. Call the appropriate methods on our `WebGlRenderingContext`

Once we've got both of our shaders, we can create the program, and link up the
shaders. Let's create an object that wraps up this process for us. You can find
a gist [here](https://gist.github.com/sgrif/de7756cd44772ad4c7ba).

```coffeescript
class WebGLCompiler
  constructor: (@gl, @shaders) ->

  createProgramWithShaders: (vertexShaderName, fragmentShaderName) ->
    vertexShader = @_createShader(vertexShaderName)
    fragmentShader = @_createShader(fragmentShaderName)
    @_createProgram(vertexShader, fragmentShader)

  _createShader: (shaderName) ->
    shaderSource = @shaders["#{shaderName}.glsl"]
    unless shaderSource
      throw "Unknown shader: #{shaderName}"

    @_compileShader(shaderSource, @_typeForShader(shaderName))

  _typeForShader: (name) ->
    if name.indexOf("vertex") != -1
      @gl.VERTEX_SHADER
    else if name.indexOf("fragment") != -1
      @gl.FRAGMENT_SHADER
    else
      throw "Unknown shader type for #{name}"

  _compileShader: (shaderSource, shaderType) ->
    shader = @gl.createShader(shaderType)
    @gl.shaderSource(shader, shaderSource)
    @gl.compileShader(shader)

    unless @gl.getShaderParameter(shader, @gl.COMPILE_STATUS)
      error = @gl.getShaderInfoLog(shader)
      console.error(error)
      throw "Could not compile shader. Error: #{error}"

    shader

  _createProgram: (vertexShader, fragmentShader) ->
    program = @gl.createProgram()
    @gl.attachShader(program, vertexShader)
    @gl.attachShader(program, fragmentShader)
    @gl.linkProgram(program)

    unless @gl.getProgramParameter(program, @gl.LINK_STATUS)
      error = @gl.getProgramInfoLog(program)
      console.error(error)
      throw "Program failed to link. Error: #{error}"

    program
```

We're going to call `createProgramWithShaders`, giving it the name of the files
to use for the vertex and fragment shaders. We assume that all vertex shaders
are going to have the word "vertex" in the name, and that fragment shaders will
have the word "fragment". After compiling each shader, we attempt to compile it
and check for errors. Finally, we attach the shaders to our program, and try to
link the shaders. If all of this succeeded, the result will be an instance of
`WebGLProgram`

```coffeescript
main = ->
  canvas = document.getElementsByTagName("canvas")[0]
  gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl")
  compiler = new WebGLCompiler(gl, window.shaders)
  program = compiler.createProgramWithShaders("main_vertex", "main_fragment")
```

Now we can start writing actual code! We'll start by writing the simplest
possible vertex shader. It will do nothing but return the input unchanged.

```glsl
attribute vec2 vertexCoord;

void main() {
  gl_Position = vec4(vertexCoord, 0.0, 1.0);
}
```

An `attribute` is the primary input to the vertex shader. We're going to give it
an array of values. OpenGL will loop over them, and call this function once per
element. The function doesn't actually return anything. Instead, we set a local
variable called `gl_Position`. That variable expects a `vec4`, which means it
has an `x`, `y`, `z`, and `w`, rather than a `vec2`, which just has `x` and `y`.
`z` works like the z-index property in CSS. `w` is a value that every other axis
will be divided by. We'll set it to `1.0` for now, so nothing is affected.

Once the vertex shader has set enough points to draw a triangle, the fragment
shader will be called once per pixel in that triangle. For now, we'll just
always return blue.

```glsl
void main() {
  gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
```

## Sending Data to the GPU

The last step is to wire up our program to our rendering context, pass in the
data, and draw a triangle. First we'll make sure our screen is in a consistent
state.

```coffeescript
gl.clearColor(1.0, 1.0, 1.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
```

`clearColor` tells the GPU what color to use for pixels where we don't draw
anything. We've set it to white. Then, we tell it to reset the canvas so nothing
has been drawn. The next then we need to do is give our program some data. In
order to do this, we'll need to create a buffer. A buffer is essentially an
address in memory where we can shove an arbitrary number of bits.

```coffeescript
gl.useProgram(program)
buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(
  gl.ARRAY_BUFFER
  new Float32Array([
    0.0, 0.8
    -0.8, -0.8
    0.8, -0.8
  ])
  gl.STATIC_DRAW
)
```

OpenGL is highly stateful. When we call `bufferData`, we never specify which
buffer is being used. Instead, it works with the last buffer we passed to
`bindBuffer`. `gl.ARRAY_BUFFER` tells OpenGL that the contents of this buffer
are going to be used for an attribute. `gl.STATIC_DRAW` is a performance hint
that says this data is going to be used often, but won't change much.

Now that we've put the data in memory, we need to tell OpenGL which attribute to
use it for, and how it should interpret that data. Right now it just sees it as
a bunch of bits.

```coffeescript
vertexCoord = gl.getAttribLocation(program, "vertexCoord")

gl.enableVertexAttribArray(vertexCoord)
gl.vertexAttribPointer(vertexCoord, 2, gl.FLOAT, false, 0, 0)
```

The first thing we need to do is get the location of the attribute in our
program. This is going to be a numeric index, based on the order that we use it
in our program. In this case, it'll be `0`. Next we call
`enableVertexAttribArray`, which takes the location of an attribute, and tells
us that we want to use the data that we're going to populate it with. I'll
admit, I don't know why you would have an attribute present in your application,
but not enable it. Finally, `vertexAttribPointer` will populate the attribute
with the currently bound buffer, and tell it how to interpret the data. This is
what each of the arguments mean:

```coffeescript
gl.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
  gl.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
)
```

Finally, we need to tell it that we've finished giving it all of the data it
needs, and we're ready to draw something to the screen.

```coffeescript
gl.drawArrays(gl.TRIANGLES, 0, 3)
```

`drawArrays` means that we want to loop through the attribute data, in the order
that it was given. The first argument is the method we should use for drawing.
`TRIANGLES` means that it should use every three points as a surface. It would
take 6 points to draw two triangles. There are other options, such as
`TRIANGLE_STRIP`, which would only take 4 points to draw 2 triangles. There's
also `POINTS` or `LINES`, which completely change how a single triangle is
drawn. The second argument is which element in the array we should start from.
The final argument is the number of points we're going to draw. The end result,
is a simple triangle. All of the code used for this sample is available
[here](https://gist.github.com/sgrif/fc9e4a7cb765b49b6890).

![blue triangle](https://images.thoughtbot.com/gl-tutorial/blue-triangle.png)
