top of page

Renderer2D! With instancing and textures!

  • Writer: Pablo Narvaja
    Pablo Narvaja
  • Dec 19, 2019
  • 4 min read

When you want to render 2d most of the time you just wanna render sprites, quads with a alpha texture applyed to them, and lots of them! But how do you do it so the drawcalls don't become a bottleneck? What if I told you you can draw all of the quads in one drawcall?


You have two common ways of doing it: 1) Batching

2) Instancing


Batching

This technique use one dynamic vertex buffer to store all the quad vertices having the quads form a large mesh. So you only drawcall is to render the big mesh. You need to update the buffer everytime something changes in the quads.


Instancing

This technique use a mesh and tells opengl that render the mesh multiple times using different transforms. But how do you tell opengl what transforms to use? Again there are two ways to do it.

Using a uniform vector of transforms. This have a pretty low limit in the count of transforms.

Or using a dynamic buffer for instances vertex attributes. The last one is the one I use in llamathrust.

You use a vertex buffer for you geometry and another one for your matrices. The matrices are now part of the vertex attributes but you say to opengl that they change every instances instead of every vertex using glVertexAttribDivisor(index, divisor) and then you render your mesh with glDrawArraysInstanced or glDrawElementsInstanced and that's it.


Before doing so you might be thinking but how do I apply a different texture in a quad if they are all rendered at once? This is a problem that both of this techniques have and here is where texture atlases come in.


Texture Atlas

Basically is a big image containing lots of images inside it. Here is a example:


The image above show One texture with 4 textures inside it.


Implementation

Overview

I used a vertex buffer for the 4 vertex positions of the quad and a element buffer for the 6 indices. For the instances attributes I used another vertex buffer and store color, pvm transform and a array of 4 vec2 for the uv coordinates.


The Shader

This is how the vertex shader looks like:

When I discover the gl_VertexID variable in GLSL it all become clear of what to do. The thing is that every layout location have a max of vec4 size so the transform is 4 layout locations from 2 to 5 and for the array is the same but every location from 6 to 9 uses only vec2 size. Being more thorough for every mat4 in your vertex layout you have to call glEnableVertexAttribArray four times.



And for the fragment a uniform for the texture atlas is all we need since the texture will have a white border outside

[0, 1] uv coordinates so every quad with uv_coords outside the texture will have the color of its vertices. If it have the uvs inside the texture the color should be white else it will be tinted.


The Buffers

As I said we need 2 vertex buffers. The first will have the 4 points positions. And the second will have the per instance attributes. And of course we need the elements buffer for the indices of the triangles like this:

As you can see the per instance buffer (m_quadsTransforms, yep it needs a better name now) is a empty buffer that's why is passed nullptr as data. We will update the buffer with data before rendering the mesh.

The instance data is called "Quad", because every instance is a different quad, and is conformed by color of the instance, the transform and the uv coords for every vertex (4, four).





Texture Atlas

Fot the texture atlas I need a way to know where every texture inside it begins and end so I came with this solution.


This structure define a texture inside the atlas.

"offset" is the offset from the (0, 0) uv_coord at which the texture begins and "extent" is where the texture ends.


So I create a map of textures names to texture definitions:

They are created manually at the moment.


That way I can set the correct uv's to the quad like this:

Since the texture atlas is loaded to opengl as:


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, WhiteColor));


And the shader multiply the texture with the instance color, for a "only color" quad I can do set the uvs outside the texture like this:


The rendering

Now to present this to the screen we need to first disable depth testing, enable blending for transparency (most of the times sprites have alpha channels) and to make a little faster we disable cull faces. After we did that we can now update the instances buffer and render the quads.


Demo

Here is how the demo look on code:


And here what it shows to the screen:


We can see that the sizes are in pixels since they maintain the size no matter the screen size and the positions are in pixels too but the center of the screen is pixel (0, 0).

Note: The viewport is being updated when the window size is else the viewport will deform to fit into the window and the quads will deform with it.


That's it for this post, folks. Bye!

Comments


  • facebook
  • linkedin
  • youtube
  • generic-social-link
  • Grey LinkedIn Icon
  • Facebook
  • YouTube

2019 Pablo Narvaja. Llamathrust GameEngine

bottom of page