Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

openGL Creating texture atlas at run time?

Tags:

c++

opengl

So I've set up my framework in a neat little system to wrap SDL, openGL and box2D all together for a 2D game.

Now how it works is that I create an object of "GameObject" class, specify a "source PNG", and then it automatically creates an openGL texture and a box2d body of the same dimensions.

Now I am worried about if I start needing to render many different textures on screen.

Is it possible to load in all my sprite sheets at run time, and then group them all together into one texture? If so, how? And what would be a good way to implement it (so that I wouldn't have to manually be specifying any parameters or anything).

The reason I want to do it at run time and not pre-done is so that I can easily load together all (or most) of the tiles, enemies etc.. of a certain level into this one texture, because every level won't have the same enemies. It'd also make the whole creating art process easier.

like image 690
Omar Shehata Avatar asked Mar 28 '12 03:03

Omar Shehata


People also ask

Should I use texture atlases?

There are two main reasons to use a texture atlas. First, it can increase rendering speed by allowing you to batch more objects into a single draw call. For example, if all the plants in the game use just one texture atlas, then you can draw them all at the same time.

How big should a texture atlas be?

With a common maximum texture size of 4,096 x 4,096 pixels, the build system will ideally fit all of your source textures into one destination texture.

How do you stop a texture bleed?

One solution is to use a format that have generated mipmaps, then you could create thees mipmaps your self and correct the error. Another solution is to force a specific mipmap level in your fragmentshader when you are sampling the texture. Another solution is to fill the mipmaps with the first mipmap.

Does Minecraft use a texture atlas?

Minecraft uses both procedurally-generated and predefined texture atlases for different purposes.


1 Answers

There are likely some libraries that already exist for creating texture atlases (optimal packing is a nontrivial problem) and converting old texture coordinates to the new ones.

However, if you want to do it yourself, you probably would do something like this:

  • Load all textures from disk (your "source PNG") and retrieve the raw pixel data buffer,
  • If necessary, convert all source textures into the same pixel format,
  • Create a new texture big enough to hold all the existing textures, along with a corresponding buffer to hold the pixel data
  • "Blit" the pixel data from the source images into the new buffer at a given offset (see below)
  • Create a texture as normal using the new buffer's data.
  • While doing this, determine the mapping from "old" texture coordinates into the "new" texture coordinates (should be a simple matter of recording the offsets for each element of the texture atlas and doing a quick transform). It would probably also be pretty easy to do it inside a pixel shader, but some profiling would be required to see if the overhead of passing the extra parameters is worth it.

Obviously you also want to check to make sure you are not doing something silly like loading the same texture into the atlas twice, but that's a concern that's outside this procedure.


To "blit" (copy) from the source image to the target image you'd do something like this (assuming you're copying a 128x128 texture into a 512x512 atlas texture, starting at (128, 0) on the target):

unsigned char* source = new unsigned char[ 128 * 128 * 4 ]; // in reality, comes from your texture loader
unsigned char* target = new unsigned char[ 512 * 512 * 4 ];

int targetX = 128;
int targetY = 0;

for(int sourceY = 0; sourceY < 128; ++sourceY) {
    for(int sourceX = 0; sourceX < 128; ++sourceX) {
        int from = (sourceY * 128 * 4) + (sourceX * 4); // 4 bytes per pixel (assuming RGBA)
        int to = ((targetY + sourceY) * 512 * 4) + ((targetX + sourceX) * 4); // same format as source

        for(int channel = 0; channel < 4; ++channel) {
            target[to + channel] = source[from + channel];
        }
    }
}

This is a very simple brute force implementation: there are much faster, more succinct and more clever ways to copy an array, but the idea is that you are basically copying the contents of the source texture into the target texture at a given X and Y offset. In the end, you will have created a new texture which contains the old textures in it.

If the indexing math doesn't make sense to you, think about how a 2D array is actually indexed inside a 1D space (such as computer memory).

Please forgive any bugs. This isn't production code but instead something I wrote without checking if it compiles or runs.


Since you're using SDL, I should mention that it has a nice function that might be able to help you: SDL_BlitSurface. You can create an SDL_Surface entirely within SDL and simply use SDL_BlitSurface to copy your source surfaces into it, then convert the atlas surface into a GL texture.

It will take care of all the math, and can also do a format conversion for you on the fly.

like image 145
ravuya Avatar answered Sep 22 '22 23:09

ravuya