12
votes

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?

2
you don't need to delete the old texture, just bind the new ones. also texture atlasratchet freak
Your 'small idea' is actually a very common technique, used in a lot of games. GPUs are quite good at working with large textures.Wander Nauta
I seem to remember a number of similar questions, but I can't find a duplicate right now. Are all your textures the same size? The idea you mention towards the end is mostly called "texture atlas", and is one of the options.Reto Koradi
Actually, the number of Texture Image Units is not at least 45. Its minimum is precisely equal to 16 * <Num Shader Stages> in modern OpenGL. That means in GL 3.3 it is 16 * 3 (Vertex, Geometry, Fragment) = 48, in GL 4.5 it is 16 * 5 (Vertex, Tessellation Control, Tessellation Evaluation, Geometry, Fragment) = 80. The reason for this is so that you can bind a set of 16 unique textures for use in each stage, but you actually cannot sample more than 16 textures in a single execution of a shader in a minimal implementation.Andon M. Coleman
I've posted an answer here: stackoverflow.com/questions/16075791/…user2288580

2 Answers

23
votes

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.

9
votes

You need to learn about the difference of texture units and texture objects.

Texture units are like "texture cartridges" of the OpenGL rasterizer. The rasterizer has a limited amount of "cartridge" slots (called texture units). To load a texture into a texture unit you first select the unit with glActiveTexture, then you load the texture "cartridge" (the texture object) using glBindTexture.

The amount of texture object you can have is only limited by your systems memory (and storage capabilities), but only a limited amount of textures can be "slotted" into the texture unit at the same time.

Samplers are like "taps" into the texture units. Different samplers within a shader may "tap" into the same texture unit. By setting the sampler uniform to a texture unit you select which unit you want to sample from.

And then you can also have the same texture "slotted" into multiple texture units at the same time.

Update (some clarification)

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?

Normally you don't try to render the whole image with a single drawing call. It's practically impossible to catch all variations on which textures to use in what situation. Normally you write shaders for specific looks of a "material". Say you have a shader simulating paint on some metal. You'd have 3 textures: Metal, Paint and a modulating texture that controls where metal and where paint is visible. The shader would then have 3 sampler uniforms, one for each texture. To render the surface with that appearance you'd

  • select the shader program to use (glUseProgram)
  • for each texture activate in turn the texture unit (glActiveTexture(GL_TEXTURE_0+i) and bind the texture ('glBindTexture`)
  • set the sampler uniforms to the texture units to use (glUniform1i(…, i)).
  • draw the geometry.