Search code examples
opengl

How do I render multiple textures in modern OpenGL?


I am currently writing a 2d engine for a small game.

The idea was that I could render the whole scene in just one draw call. I thought I could render every 2d image on a quad which means that I could use instancing.

I imagined that my vertex shader could look like this

...
in vec2 pos;
in mat3 model;
in sampler2d tex;
in vec2 uv;
...

I thought I could just load a texture on the gpu and get a handle to it like I would do with a VBO, but it seems it is not that simple.

It seems that I have to call

glActiveTexture(GL_TEXTURE0..N);

for every texture that I want to load. Now this doesn't seem as easy to program as I thought. How do modern game engines render multiple textures?

I read that the texture limit of GL_TEXTURE is dependent on the GPU but it is at least 45. What if I want to render an image that consists of more than 45 textures for example 90?

It seems that I would have to render the first 45 textures and delete all the texture from the gpu and load the other 45 textures from the hdd to the gpu. That doesn't seem very reasonable to do every frame. Especially when I want to to animate a 2D image.

I could easily think that a simple animation of a 2d character could consist of 10 different images. That would mean I could easily over step the texture limit.

A small idea of mine was to combine multiple images in to one mega image and then offset them via uv coordinates.

I wonder if I just misunderstood how textures work in OpenGL.

How would you render multiple textures in OpenGL?


Solution

  • The question is somewhat broad, so this is just a quick overview of some options for using multiple textures in the same draw call.

    Bind to multiple texture units

    For this approach, you bind each texture to a different texture unit, using the typical sequence:

    glActiveTexture(GL_TEXTURE0 + i);
    glBindTexture(GL_TEXTURE_2D, tex[i]);
    

    In the shader, you can have either a bunch of separate sampler2D uniforms, or an array of sampler2D uniforms.

    The main downside of this is that you're limited by the number of available texture units.

    Array textures

    You can use array textures. This is done by using the GL_TEXTURE_2D_ARRAY texture target. In many ways, a 2D texture array is similar to a 3D texture. It's basically a bunch of 2D textures stacked on top of each other, and stored in a single texture object.

    The downside is that all textures need to have the same size. If they don't, you have to use the largest size for the size of the texture array, and you waste memory for the smaller textures. You'll also have to apply scaling to your texture coordinates if the sizes aren't all the same.

    Texture atlas

    This is the idea you already presented. You store all textures in a single large texture, and use the texture coordinates to control which texture is used.

    While a popular approach, there are some technical challenges with this. You have to be careful at the seams between textures so that they don't bleed into each other when using linear sampling. And while this approach, unlike texture arrays, allows for different texture sizes without wasting memory, allocating regions within the atlas gets a little trickier with variable sizes.

    Bindless textures

    This is only available as an extension so far: ARB_bindless_texture.