WebGL 2 Development with PicoGL.js, Part 3: Uniform Buffers and Instanced Drawing

In the last lesson, we rounded out our core rendering toolkit with textures and framebuffers. In this lesson, we’ll look at some new WebGL 2 features that don’t introduce new functionality, but let us draw the things we can already draw much more efficiently. We’ll start with uniform buffers, which allows us to load all our uniforms into a block of memory that stays on the GPU, rather than updating them individually on each draw call. Then we’ll take a look at instanced drawing, which allows us to draw multiple copies of an object in a single draw call.

Uniform Buffers

Those coming from WebGL 1 know that uniforms are variables that remain constant for all vertices and all fragments of a given draw call. In WebGL 1, uniforms would have to be updated individually for every draw call, which could lead to significant CPU overhead. In WebGL 2, we have the option to declare our uniforms in a uniform block backed by a uniform buffer that you just bind to update all uniform values in the block (similarly to how you bind vertex buffers for attributes). To take advantage of uniform buffers, you normally have to carefully lay out your buffers according to the std140 layout specification. Fortunately, PicoGL.js takes care of most of that for us.

The main thing to note here is the uniform block, SceneUniforms. Other than wrapping our declarations in the block, we use the uniforms as we normally would. We’re passing a matrix, uRotation, that we use to transform the input position, and uColor, which isn’t used here. On to the fragment shader:

Again, the only new thing is the uniform block. In this case, we’re using uColor to set the color for the fragment. Note that we’re using the same uniform block for both shaders, and despite the fact that we don’t use both uniforms in both shaders, we have to declare all uniforms in the block for both.

We use glMatrix to create a matrix that will rotate our triangle about the z-axis by 15 degrees, and also create a float array to represent the color of our triangle. The call to createUniformBuffer() takes an array of GL enums to define the layout, which must match the layout of the uniform block in our shaders. With the layout defined, we can use the set() method to update individual uniforms by referring to their indices in the layout as if they were in an array. These updates all happen in a local cache of the buffer, and the call to update() sends the new values to the GPU.

We then create our draw call, binding our uniform buffer to the uniform block, and draw:

If everything went well, you should see the image from the top of this post.

Instanced Drawing

It’s common when rendering a scene to render the same geometry multiple times while modifying things like the color or transform. WebGL 2 lets us reduce CPU overhead in these situations by drawing these copies in a single draw call. We’ll demonstrate instanced drawing by making a few small changes to the application we’ve been working on. First, the vertex shader:

We’ve added two new in variables to our vertex shader, but these will be passed per instance rather than per vertex. We’ll use uOffset to arrange our instances on the screen and uColor to modify the color of each instance. Notice that we’ve declared our out variable, vColor, as flat. This tells the rasterizer not to interpolate values across the triangle surface, since they’re constant per instance.

The instanceOffets and instanceColors buffers are created the same way we created our per-vertex buffer. The key thing is calling instanceAttributeBuffer() to bind them to the vertex array, which sets the vertex array up to pass elements from those buffers once per instance rather than once per vertex. If all went well, you should see the image from the beginning of this section. A live version is available here.

The completed example for part 3 is available here. If you have any questions, feel free to post them in the comments, visit the PicoGL.js Gitter chat room, or look me up on Twitter.