我正在使用 SDL2 为 OpenGL 创建上下文。我使用 SDL_image 加载图像,然后将它们绑定到 OpenGL 纹理。但由于坐标系不同,纹理会被翻转。
我找到了两种方法来解决这个问题:
加载后修改纹理
优势:每个纹理只执行一次
缺点:使用CPU完成,这会减慢每个纹理的加载速度
渲染时在Y和Z轴上应用180°旋转
优势:使用超快速功能
缺点:每帧需要多次完成
在使用 SDL_Image 加载纹理后,还有另一种方法可以翻转纹理吗?如果没有,通常使用哪种方法?
答案 0 :(得分:2)
有很多选择。一些浮现在脑海中的想法:
您可以使用图像处理工具颠倒图像文件,并将翻转的图像用作资源。在图像查看器中查看时,它们会颠倒过来,但在用作纹理时会变得正确。
如果您完全控制图像,这是理想的解决方案。如果你在运行时从外部来源获取图像,它显然不会起作用。
某些图像加载库允许您在加载期间翻转图像。从我可以找到的SOIL_image文档中,我没有看到这个选项。但是您可能能够找到支持它的备用库。当然,如果您编写自己的图像加载,则可以执行此操作。
这是一个很好的解决方案。无论如何,当您触摸数据时,开销是最小化的。一种常见的方法是逐行读取数据,并使用glTexSubImage2D()
以相反的顺序存储在纹理中。
您可以在加载纹理后创建纹理的翻转副本。执行此操作的典型方法是在对原始纹理进行采样时绘制屏幕大小的四边形,并渲染到具有生成的翻转纹理作为渲染目标的FBO。或者,更优雅,使用glBlitFramebuffer()
。
这不太吸引人,因为它涉及复制内存。虽然如果你让GPU创建副本应该非常有效,但额外的复制总是不受欢迎的。即使每个纹理只发生一次,也会增加启动/加载时间。
您可以将变换应用于顶点或片段着色器中的纹理坐标。你在谈论你的问题中的轮换,但你需要的转变实际上是微不足道的。您基本上只需将纹理坐标的y
映射到1.0 - y
,并保持x
不变。
这为着色器执行增加了一个小的价格。但与其一起使用的纹理采样操作相比,该操作非常简单快速。实际上,增加的开销可能是微不足道的。虽然我不认为它非常漂亮,但它是一个非常好的解决方案。
这与前一个选项类似,但您不是在着色器中反转纹理坐标,而是在顶点属性数据中指定它们。
这通常是微不足道的。例如,通过对4个角使用(0, 0)
,(1, 0)
,(0, 1)
,(1, 1)
的纹理坐标来纹理四边形非常常见。相反,您只需在纹理坐标的第二个组件中将0
替换为1
,将1
替换为0
。
或者说你加载一个包含文件纹理坐标的模型。您只需在阅读期间将纹理坐标中的每个y
替换为1.0f - y
,然后再存储纹理坐标以便以后渲染。
答案 1 :(得分:1)
除了在加载时或首次使用前翻转图像外,我不同意之前回答的大部分内容。
原因在于,如果您遵循数据驱动的软件开发实践,则不应允许代码指示数据的性质。该软件应设计为准确支持数据。其他任何东西都不适合。
尽管易于使用,但修改纹理坐标仍然很麻烦。如果您在稍后阶段决定使用不翻转图像的不同图像库会发生什么?现在,您的图像将在渲染过程中再次反转。
相反,在源处理问题,并在加载期间或首次使用之前翻转图像(我主张加载,因为它可以集成到通过SDL_Image加载图像的代码中,因此更容易维护)。
要翻转图片,我会发布一些简单的伪代码,说明如何操作:
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
以下是此代码循环的一次迭代的直观说明:
然而,这只适用于行数偶数的图像,这很好,因为opengl不喜欢具有二维功率的纹理。
还应注意,如果加载的图像不具有两个宽度的功率,则SDL_Image将其填充。因此,"宽度"传递给函数应该是图像的间距,而不是它的宽度。