0
votes

I am using SDL2 to create a context for OpenGL. I use SDL_image to load the images, and I bind them to OpenGL textures. But because the coordinate system isn't the same the textures are flipped.

I found two ways to correct this:

Modify the texture after loading it

Advantage: Only done once per texture

Disadvantage: Done using the CPU which slows down the loading of each texture

Apply a rotation of 180° on the Y and Z axis when rendering

Advantage: Using super fast functions

Disadvantage: Needs to be done multiple times per frame

Is there another way to flip back the textures after they have been loaded with SDL_Image? And if not, which method is usually used?

2
How about storing the images in OpenGL's origin in the first place?datenwolf
You mean by using something else than SDL_image? Do you have one to recommend?Heckel
No, I mean the files themself are already stored in the right orientation. Oh, and it's actually trivial to implement an image loader in a way, that it loads images with the right scanline ordering. Image loaders targeting OpenGL will do this.datenwolf

2 Answers

2
votes

There are a bunch of options. Some that come to mind:

Edit original assets

You can flip the image files upside down with an image processing tool, and use the flipped images as your assets. They will look upside down when viewed in an image viewer, but will then turn out correct when used as textures.

This is the ideal solution if you're in full control of the images. It obviously won't work if you get images from external sources at runtime.

Flip during image load

Some image loading libraries allow you to flip the image during loading. From the documentation of SOIL_image I could find, I did not see this option there. But you might be able to find an alternate library that supports it. And of course you can do this if you write your own image loading.

This is a good solution. The overhead is minimal sice you do the flipping while you're touching the data anyway. One common approach is that you read the data row by row, and store in the texture in the opposite order, using glTexSubImage2D().

Flip between loading and first use

You can create a flipped copy of the texture after you already loaded it. The typical way to do this would be by drawing a screen sized quad while sampling the original texture and rendering to an FBO that has the resulting flipped texture as a rendering target. Or, more elegant, use glBlitFramebuffer().

This is not very appealing because it involves copying the memory. While it should be quite efficient if you let the GPU create the copy, extra copying is always undesirable. Even if it happens only once for each texture, it can increase your startup/loading time.

Apply transformation to texture coordinates

You can apply a transformation to the texture coordinates in either the vertex or fragment shader. You're talking about rotations in your question, but the transformation you need is in fact trivial. You basically just map the y of the texture coordinate to 1.0 - y, and leave the x unchanged.

This adds a small price to shader execution. But the operation is very simple and fast compared to the texture sampling operation it goes along with. In reality, the added overhead is probably insignificant. While I don't think it's very pretty, it's a perfectly fine solution.

Invert the texture coordinates

This is similar to the previous option, but instead of inverting the texture coordinates in the shader, you already specify them inverted in the vertex attribute data.

This is often trivial to do. For example, it is very common to texture quads by using texture coordinates of (0, 0), (1, 0), (0, 1), (1, 1) for the 4 corners. Instead, you simply replace 0 with 1 and 1 with 0 in the second components of the texture coordinates.

Or say you load a model containing texture coordinates from a file. You simply replace each y in the texture coordinates by 1.0f - y during reading, and before storing away the texture coordinates for later rendering.

IMHO, this is often the best solution. It's very simple to do, and has basically no performance penalty.

2
votes

I would disagree with most of the previous answer's point, except for flipping the image either on load, or before first use.

The reason being that if you are following data driven software development practices, you should never allow code to dictate the nature of data. The software should be designed to support the data accurately. Anything else is not fit for purpose.

Modifying texture coordinates is hackery, despite it's ease of use. What happens if you decide at some later stage, to use a different image library which doesn't flip the image? Now your image will be inverted again during rendering.

Instead, deal with the problem at the source, and flip the image during load or before first use (I advocate on load, as it can be integrated into the code that loads the image via SDL_Image, and therefore is more easily maintained).

To flip an image, I'll post some simple pseudo code which will illustrate how to do it:

function flip_image( char* bytes, int width, int height, int bytes_per_pixel):

char buffer[bytes_per_pixel*width]

for ( i = 0 -> height/2 ) loop
    offset = bytes + bytes_per_pixel*width * i
    copy row (offset -> offset + bytes_per_pixel*width) -> buffer
    offset2 bytes + bytes_per_pixel * height * width;
    copy row (offset2 -> offset2 + bytes_per_pixel*width) ->  (offset -> offset + bytes_per_pixel*width)
    copy row(buffer -> buffer + width * bytes_per_pixel ) -> offset
end loop

Here is a visual illustration of one iteration of this code loop:

row swap

  1. Copy current row N to buffer
  2. Copy row (rows - N) to row N
  3. Copy buffer to row (rows - N)
  4. Increment N and repeat until N == rows/2

However, this will only work on images which have an even number of rows, which is fine as opengl doesn't like texture with non-power of two dimensions.

It should also be noted that if the image loaded does not have power of two width, SDL_Image pads it. Therefore, the "width" passed to the function should be the pitch of the image, not it's width.