翻转使用 SDL_image 加载并在 OpenGL 中使用的纹理

Flip back textures loaded with SDL_image and used in OpenGL

我正在使用 SDL2OpenGL 创建上下文。我使用 SDL_image 加载图像,并将它们绑定到 OpenGL 纹理。但是因为坐标系不一样,纹理被翻转了。

我找到了两种纠正方法:

加载后修改贴图

优点:每个纹理只做一次

缺点:使用 CPU 会减慢每个纹理的加载速度

渲染时在 Y 轴和 Z 轴上旋转 180°

优势:使用超级快速的函数

缺点:每帧需要做多次

在使用SDL_Image加载纹理后,还有其他方法可以翻转纹理吗?如果不是,通常使用哪种方法?

有很多选择。想到的一些:

编辑原始资产

您可以使用图像处理工具将图像文件上下翻转,并将翻转后的图像用作您的资产。在图像查看器中查看时,它们看起来会上下颠倒,但用作纹理时会变成正确的。

如果您可以完全控制图像,这是理想的解决方案。如果你在运行时从外部源获取图像,显然是行不通的。

图片加载时翻转

一些图像加载库允许您在加载过程中翻转图像。从我能找到的 SOIL_image 的文档中,我没有看到这个选项。但是您也许可以找到支持它的备用库。当然,如果您自己编写图像加载,也可以这样做。

这是一个很好的解决方案。无论如何,您在触摸数据时进行翻转,因此开销很小。一种常见的方法是逐行读取数据,并以相反的顺序存储在纹理中,使用 glTexSubImage2D().

在加载和首次使用之间切换

加载纹理后,您可以创建翻转的纹理副本。执行此操作的典型方法是绘制一个屏幕大小的四边形,同时对原始纹理进行采样并渲染到 FBO,该 FBO 将生成的翻转纹理作为渲染目标。或者,更优雅地使用 glBlitFramebuffer().

这不是很吸引人,因为它涉及复制内存。虽然让 GPU 创建副本应该非常有效,但额外的复制总是不可取的。即使每个纹理只发生一次,它也会增加您的 startup/loading 时间。

对纹理坐标应用变换

您可以在顶点着色器或片段着色器中对纹理坐标应用变换。你在谈论你的问题中的轮换,但你需要的转变实际上是微不足道的。您基本上只是将纹理坐标的 y 映射到 1.0 - y,并保持 x 不变。

这会为着色器执行增加一点代价。但与它附带的纹理采样操作相比,该操作非常简单和快速。实际上,增加的开销可能微不足道。虽然我不认为它很漂亮,但它是一个完美的解决方案。

反转纹理坐标

这与前面的选项类似,但不是在着色器中反转纹理坐标,而是在顶点属性数据中指定它们反转。

这通常是微不足道的。例如,通过使用纹理坐标 (0, 0)(1, 0)(0, 1)(1, 1) 的 4 个角来对四边形进行纹理处理是很常见的。相反,您只需在纹理坐标的第二个分量中将 0 替换为 1 并将 1 替换为 0

或者假设您从文件中加载包含纹理坐标的模型。您只需在读取期间将纹理坐标中的每个 y 替换为 1.0f - y,然后再存储纹理坐标以供以后渲染。

恕我直言,这通常是最好的解决方案。做起来非常简单,而且基本上没有性能损失。

我不同意之前答案的大部分观点,除了在加载时或首次使用前翻转图像。

原因是,如果您遵循数据驱动的软件开发实践,则绝不应让代码决定数据的性质。软件的设计应该能够准确地支持数据。其他任何内容都不符合目的。

尽管很容易使用,但修改纹理坐标是一种骇客行为。如果您在稍后阶段决定使用不翻转图像的不同图像库,会发生什么情况?现在您的图像将在渲染过程中再次反转。

相反,从源头上解决问题,在加载时或首次使用前翻转图像(我提倡加载,因为它可以集成到通过SDL_Image加载图像的代码中,因此更容易维护)。

要翻转图像,我将 post 一些简单的伪代码来说明如何操作:

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

这是此代码循环的一次迭代的直观图示:

  1. 复制当前行N到缓冲区
  2. 复制行(行-N)到第N行
  3. 将缓冲区复制到行(行 - N)
  4. 递增 N 并重复直到 N == rows/2

但是,这仅适用于行数为偶数的图像,这很好,因为 opengl 不喜欢二维非幂的纹理。

还需要注意的是,如果加载的图像不是宽度的两倍,SDL_Image 将其填充。因此,传递给函数的 "width" 应该是图像的间距,而不是宽度。