我应该垂直翻转加载了 stb_image 的图像的线条以在 OpenGL 中使用吗?
Posted
技术标签:
【中文标题】我应该垂直翻转加载了 stb_image 的图像的线条以在 OpenGL 中使用吗?【英文标题】:Should I vertically flip the lines of an image loaded with stb_image to use in OpenGL? 【发布时间】:2013-11-04 14:48:39 【问题描述】:我正在开发一个基于 OpenGL 的 2d 引擎。
我正在使用 stb_image 加载图像数据,以便创建 OpenGL 纹理。我知道 OpenGL 的 UV 原点位于左下角,我还打算在该空间中为我的屏幕空间 2d 顶点工作,即我正在使用 glm::ortho( 0, width, 0, height, -1, 1 ),不反转 0 和高度。
您可能猜到了,我的纹理是垂直翻转的,但我 100% 确定我的 UV 指定正确。
那么:这是由 stbi_load 存储像素数据引起的吗?我目前只加载 PNG 文件,所以我不知道如果我使用其他文件格式是否会导致此问题。会吗? (我现在不能测试,我不在家)。
我真的很想将屏幕坐标保持在“标准”OpenGL 空间中...我知道我可以反转正交投影来修复它,但我真的不想这样做。
我可以看到两个明智的选择:
1- 如果这是由像素数据的 stbi_load 存储引起的,我可以在加载时反转它。出于性能原因,我有点担心,因为我使用纹理数组 (glTexture3d) 来制作精灵动画,这意味着我需要单独反转纹理图块,这看起来很痛苦,而不是一般的解决方案。
2- 我可以使用纹理坐标变换来垂直翻转 GPU 上的 UV(在我的 GLSL 着色器中)。
可能的第三个选项是使用 glPixelStore 来指定输入数据...但我无法找到一种方法来告诉它输入的像素是垂直翻转的。
您对处理我的问题有什么建议?我想我不能是唯一一个使用 stbi_load + OpenGL 并遇到这个问题的人。
最后,我的目标平台是 PC、android 和 ios :)
编辑:我回答了我自己的问题...见下文。
【问题讨论】:
在 OpenGL 中使用归一化纹理坐标,(0,0) 总是指纹理图像的左下角。没有办法改变这种行为,除非你说在将图像提交到 GL 之前翻转图像或翻转纹理坐标。另外,请注意,通过使用像您正在使用的那样的非标准投影矩阵,如果您曾经剔除多边形刻面侧或执行每侧照明或模板,您的缠绕方向将向后。多边形缠绕是在投影后确定的,并假定为标准右手投影。 为此,OpenGL 中没有在像素传输期间翻转图像的机制。您可以颠倒组件的顺序,更改格式或数据类型(请注意,在 OpenGL ES 中 不是),但不能垂直或水平翻转图像。随着可编程片段着色的引入,这完全没有必要。考虑到混合纹理的能力,颠倒组件的顺序也有点不必要,如果当前的弃用率保持不变,它可能会在 10-20 年内从 API 中删除;) 为什么说我用的是非标准的投影矩阵? AFAIK 我的问题来自这样一个事实,即我希望矩阵尽可能标准(原点位于屏幕的左下角)......看看我对 glm::ortho 的调用。我想保持这种方式,因为我不想担心以后的投影后操作。我在这里错过了什么吗? 明确一点:我知道 (0,0) 是纹理贴图的左下角。我的问题正是当我指定 (0,0) 时,我得到了左上角的纹素。这就是为什么我要问 stbi_load 像素存储和 OpenGL 像素存储是否不同。 (0,0) 位于左下角似乎是人们做出的假设,我在实际规范中看不到任何证据,所有规范都说t
( “y”坐标)必须在 [0,1] 范围内,并且可以进行线性插值。 lodepng
加载底部优先,因此左上角为 (0,1),而 gdiplus
加载顶部优先,因此左上角为 (0, 0)。因此,选择坐标取决于您使用的图像加载器,而不是 OpenGL。
【参考方案1】:
我知道这个问题已经很老了,但它是谷歌在尝试解决这个问题时的第一批结果之一,所以我想我会提供一个更新的解决方案。
在最初询问这个问题后的某个时间,stb_image.h 添加了一个名为“stbi_set_flip_vertically_on_load”的函数,只需将 true 传递给此函数将导致它以 OpenGL 期望的方式输出图像 - 从而无需手动翻转/纹理坐标翻转.
此外,对于那些不知道从哪里获得最新版本的人,无论出于何种原因,您都可以在 github 上找到它正在积极开发中: https://github.com/nothings/stb
还值得注意的是,在 stb_image 的当前实现中,它们逐像素翻转图像,这并不完全是高性能的。这可能会在以后发生变化,因为他们已经将其标记为优化。 编辑:似乎他们已经切换到 memcpy,这应该会快一点。
【讨论】:
如何做到比逐个像素更快地完成?我想不出更好的方法 您可以修改加载逻辑以仅遵守 stbi_set_flip_vertically_on_load 设置的顺序,从而完全不需要翻转。下一个最好的方法可能是对整行使用 memcpy 或 memmove。【参考方案2】:好的,我会回答我自己的问题...我浏览了两个库(stb_image 和 OpenGL)的文档。
以下是适当的参考位:
glTexImage2D 对数据指针参数的描述如下:“第一个元素对应于纹理图像的左下角。 后续元素从左到右遍历最下面的剩余纹素纹理图像的行,然后是纹理图像的连续更高的行。最后一个元素对应于纹理图像的右上角。来自http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml
stb_image 库对加载的图像像素这样说:“图像加载器的返回值是一个指向像素数据的 'unsigned char *'。像素数据由 *x 像素的 *y 扫描线组成,其中每个像素由 N 个交错的 8 位分量组成;指向的第一个像素位于图像的最左上角。“来自http://nothings.org/stb_image.c
因此,问题与图像加载库和 OpenGL 之间的像素存储差异有关。如果我加载除 PNG 以外的其他文件格式也没关系,因为 stb_image 为它加载的所有格式返回相同的数据指针。
所以我决定在我的 OglTextureFactory 中交换 stb_image 返回的像素数据。这样,我保持我的方法独立于平台。如果加载时间成为问题,我将在加载时删除翻转并在 GPU 上做一些事情。
希望这对将来的其他人有所帮助。
【讨论】:
Gamedev.net 论坛版 本主题:gamedev.net/topic/…【参考方案3】:是的,你应该这样做。这可以通过在加载图像之前调用这个 STBI 函数来轻松完成:
stbi_set_flip_vertically_on_load(true);
【讨论】:
【参考方案4】:由于这是一般图像库和 OpenGL 之间的相反假设问题,我认为最好的方法是操纵垂直 UV 坐标。这需要很少的努力,并且在使用任何图像库加载图像并将其传递给 OpenGL 时总是相关的。
在顶点填充中使用 1.0f-uv.y 馈送 tex-coords 或在着色器中反转。
fcol = texture2D( tex, vec2(uv.x,1.-uv.y) );
【讨论】:
以上是关于我应该垂直翻转加载了 stb_image 的图像的线条以在 OpenGL 中使用吗?的主要内容,如果未能解决你的问题,请参考以下文章
我的渲染技术进阶之旅解决显示3D模型时因为使用stb_image库或者opencv库加载纹理时未翻转y轴导致模型纹理映射出错的问题