Should I vertically flip the lines of an image loaded with stb_image to use in OpenGL?
Asked Answered
M

4

16

I'm working on an OpenGL-powered 2d engine.

I'm using stb_image to load image data so I can create OpenGL textures. I know that the UV origin for OpenGL is bottom-left and I also intend to work in that space for my screen-space 2d vertices i.e. I'm using glm::ortho( 0, width, 0, height, -1, 1 ), not inverting 0 and height.

You probably guessed it, my texturing is vertically flipped but I'm 100% sure that my UV are specified correctly.

So: is this caused by stbi_load's storage of pixel data? I'm currently loading PNG files only so I don't know if it would cause this problem if I was using another file format. Would it? (I can't test right now, I'm not at home).

I really want to keep the screen coords in the "standard" OpenGL space... I know I could just invert the orthogonal projection to fix it but I would really rather not.

I can see two sane options:

1- If this is caused by stbi_load storage of pixel data, I could invert it at loading time. I'm a little worried about that for performance reason and because I'm using texture arrays (glTexture3d) for sprite animations meaning I would need to invert texture tiles individually which seems painful and not a general solution.

2- I could use a texture coordinate transformation to vertically flip the UVs on the GPU (in my GLSL shaders).

A possible 3rd option would be to use glPixelStore to specify the input data... but I can't find a way to tell it that the incoming pixels are vertically flipped.

What are your recommendations for handling my problem? I figured I can't be the only one using stbi_load + OpenGL and having that problem.

Finally, my target platforms are PC, Android and iOS :)

EDIT: I answered my own question... see below.

Magnifico answered 4/11, 2013 at 14:48 Comment(5)
Using normalized texture coordinates in OpenGL, (0,0) always refers to the bottom-left corner of a texture image. There is no way of changing this behavior short of as you said flipping your image before submitting it to GL or flipping your texture coordinates. Also, be aware that by using a non-standard projection matrix like the one you are using, if you were to ever cull polygon facet sides or perform per-side lighting or stenciling, your winding direction will be backwards. Polygon winding is determined post-projection, and assumes a standard right-handed projection.Henning
To that end, there is no mechanism in OpenGL to flip an image during pixel transfer. You can reverse the order of components, change the format or data type (not in OpenGL ES, mind you), but you cannot flip the image vertically or horizontally. With the introduction of programmable fragment shading, this is completely unnecessary. Reversing the order of the components is also somewhat unnecessary given the ability to swizzle textures, it may be removed from the API in 10-20 years if the current rate of deprecation holds ;)Henning
Why are you saying that I'm using a non-standard projection matrix? AFAIK my problem comes from the fact that I want the matrix to be as standard as possible (origin at the bottom-left of the screen)... look at my call to glm::ortho above. I wanna keep it this way because I don't wanna have to worry about post-projection ops later down the road. Am I missing something here?Magnifico
Just to be clear: I know that (0,0) is the bottom-left of the texture map. My problem is precisely that when I specify (0,0) I get the top-left corner texel. That's why I'm asking if stbi_load pixel storage and OpenGL pixel storage differ.Magnifico
That (0,0) is bottom-left seems to be an assumption people have made that I can't see any evidence for in the actual spec, all the spec says is that t (the "y" coordinate) must lie in the range [0,1] and can be linearly interpolated. lodepng loads bottom-first, so top-left is (0,1), while gdiplus loads top-first, so top-left is (0, 0). Choosing your coordinates therefore depends on the image loader you use, not OpenGL.Nsf
S
33

I know this question's pretty old, but it's one of the first results on google when trying to solve this problem, so I thought I'd offer an updated solution.

Sometime after this question was originally asked stb_image.h added a function called "stbi_set_flip_vertically_on_load", simply passing true to this function will cause it to output images the way OpenGL expects - thus removing the need for manual flipping/texture-coordinate flipping.

Also, for those who don't know where to get the latest version, for whatever reason, you can find it at github being actively worked on: https://github.com/nothings/stb

It's also worth noting that in stb_image's current implementation they flip the image pixel-by-pixel, which isn't exactly performant. This may change at a later date as they've already flagged it for optimsation. Edit: It appears that they've swapped to memcpy, which should be a good bit faster.

Snowdrop answered 24/9, 2015 at 8:48 Comment(2)
How could it be done faster than doing it pixel by pixel? I can't think of any better wayNellenelli
You could modify the loading logic to just respect the order set by stbi_set_flip_vertically_on_load, thus preventing the need for flipping at all. The next best thing would probably be using memcpy or memmove for entire rows.Snowdrop
M
15

Ok, I will answer my own question... I went thru the documentation for both libs (stb_image and OpenGL).

Here are the appropriate bits with reference:

glTexImage2D says the following about the data pointer parameter: "The first element corresponds to the lower left corner of the texture image. Subsequent elements progress left-to-right through the remaining texels in the lowest row of the texture image, and then in successively higher rows of the texture image. The final element corresponds to the upper right corner of the texture image." From http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml

The stb_image lib says this about the loaded image pixel: "The return value from an image loader is an 'unsigned char *' which points to the pixel data. The pixel data consists of *y scanlines of *x pixels, with each pixel consisting of N interleaved 8-bit components; the first pixel pointed to is top-left-most in the image." From http://nothings.org/stb_image.c

So, the issue is related the pixel storage difference between the image loading lib and OpenGL. It wouldn't matter if I loaded other file formats than PNG because stb_image returns the same data pointer for all formats it loads.

So I decided I'll just swap in place the pixel data returned by stb_image in my OglTextureFactory. This way, I keep my approach platform-independent. If load time becomes an issue down the road, I'll remove the flipping at load time and do something on the GPU instead.

Hope this helps someone else in the future.

Magnifico answered 5/11, 2013 at 14:53 Comment(1)
Gamedev.net forums version of this topic: gamedev.net/topic/…Magnifico
N
4

Yes, you should. This can be easily accomplished by simply calling this STBI function before loading the image:

stbi_set_flip_vertically_on_load(true);  
Nephron answered 8/5, 2019 at 17:21 Comment(0)
B
0

Since this is a matter of opposite assumptions between image libraries in general and OpenGL, Id say the best way is to manipulate the vertical UV-coord. This takes minimal effort and is always relevant when loading images using any image library and passing it to OpenGL.

Either feed tex-coords with 1.0f-uv.y in vertex-population OR reverse in shader.

 fcol = texture2D( tex, vec2(uv.x,1.-uv.y) );
Bite answered 23/2, 2019 at 22:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.