Rendering Large Amounts of Sprites with WebGL

Rendering a large amount of sprites and maintaining good performance is a difficult task but thankfully there are some good ways to manage it. I’m going to talk about one of the methods I use to render large amounts of sprites in WebGL.

Given that at the time of writing there is no native support for instancing in WebGL I have had to come up with a way to do this myself. I’m not going to claim that this is the most efficient way to do things and there may be better methods out there so I encourage you to go look and find one that works for you. This way fits my use-case perfectly, although I’m sure I’ll continue to refine it as I go along.

When rendering a good rule of thumb is to reduce the number of calls to gl.drawArrays or gl.drawElements. So instead of creating multiple vertex buffers that contain each individual sprites vertex data I create one huge buffer that contains all the vertex data for all the sprites. I try to group these into related sprites just to make things easier to manage but that is optional. This means we can just call one draw method and draw all the sprites in one go, however if we want to add another sprite to the list we have to upload all the data again to the GPU. So we need to be able to set these sprites up in advance so not to incur a performance hit, and we need to avoid adding more sprites if necessary while in the main rendering loop.

I created a class to handle this called SpriteBatch. Feel free to read through the commented code and get a feel for how the class works. I’ve talked in an abstract way about the main way the class handles the drawing of large amounts of sprites but I always find it’s helpful to see some actual code. I won’t go through the code here as I think it’s pretty simple, feel free to comment and ask questions if necessary and I’ll answer anything as best I can.

– Simon

2D WebGL Boilerplate

For my first post I thought I would talk about a simple project I recently uploaded to GitHub (as in this very day) that I use all the time.

When I was starting out learning WebGL I scoured the internet for tutorials trying to learn the best practices and all kinds of tips and tricks, (I personally recommend the Mozilla Developer tutorials if you’re just starting out) and ended up rewriting the same starting code over and over again. I personally find this to be useful when first learning a language or framework as it helps solidify in my head what I am doing and why I am doing it. However once you’ve got the hang of things it can get very tedious to autonomously write out the same thing over and over again.

It becomes even more tedious if you just want to quickly prototype something, which is what I often use WebGL for as it’s possible to quickly mock up something if needed. So I found myself copying and pasting code from previous half-finished projects in an attempt to propel myself to get to the “good stuff” as it were. This ended up actually taking more time than starting from scratch as I had to refactor most of the previous code to suit my needs. After getting tired of this I decided to write some boilerplate code that I would be able to re-use to give myself a head-start when starting a new 2D WebGL project. I’m specifically mentioning 2D here as I ended up writing some more boilerplate code for 3D WebGL projects.

The code is all up on GitHub with instructions on how to use it if any of you think it might be useful. I’m also going to go through now how to set it up here and explain how it works.

First of all the project needs Node.js and the npm package manager to be installed. The project uses the node package express.js to create a local web server that serves up all the files in the “Client” folder. I did this because it’s difficult to load cross-origin images locally, so it’s much easier to create a small web server so that we can load these images. Without this we cannot load any textures and the WebGL content might be a bit boring!

Once Node.js is installed you can install the required package by opening up the terminal in the project directory and running:

npm install

Once the packages have been installed, you can start the web server by opening up the terminal in the project directory and running:

node server.js

Now that the server is running if you open up a webpage and navigate to localhost:8080 you should be presented with this screen:

If you use the WASD keys you should see a red circle in the top left move around, this is the “player sprite”. The scene is a little boring but that’s the point, it’s a start of a project not a whole one.

Now I’ll briefly talk about the overview of the project.
There are 4 main files, main.jsgame.jsSprite.js. and index.html. When starting out I usually only edit main.js and game.js but occassionally if I need to touch the shader code I’ll go into index.html, but most of the time it’s just those 2 files I change.

Within main.js is the core of the WebGL code. I try to keep everything associated with WebGL and HTML elements and events here. The main rendering loop is here along with all the initialisation of WebGL.

All the game logic is located in game.js which handles things like the inputs and anything to do with running the game itself. Things like simulating the day/night cycle or the update loops for the NPCs are handled here even if the code to do these things is in another file, it is first called here.

Sprite.js is a helpful class I created to simplify the drawing and handling of vertex data. It contains vertex data such as position and texture information, as well as individual buffers for this data. Obviously this gives terrible performance when using lots of individual sprites, but for small numbers of sprites or unique sprites such as the player sprite this class is perfect.

The only WebGL code that is in index.html is shader code. It contains in DOM elements the vertex and fragment shader code. This is loaded by main.js using some functions that were taken from various tutorials.

This post is getting a little long now so I’m going to end it here. I only meant to give a brief overview anyway as the code should be simple enough to understand, especially since it’s only meant to be built on. I’ll be making more posts soon, probably continuing with WebGL content for the next couple of weeks. I have a dissertation that I have to start in a few weeks so most of the posts will be about that once I start.

Thanks for reading through all of this and if you have any comments leave them below!

– Simon