翻转使用 SDL_image 加载并在 OpenGL 中使用的纹理
Flip back textures loaded with SDL_image and used in OpenGL
我正在使用 SDL2 为 OpenGL 创建上下文。我使用 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
这是此代码循环的一次迭代的直观图示:
- 复制当前行N到缓冲区
- 复制行(行-N)到第N行
- 将缓冲区复制到行(行 - N)
- 递增 N 并重复直到 N == rows/2
但是,这仅适用于行数为偶数的图像,这很好,因为 opengl 不喜欢二维非幂的纹理。
还需要注意的是,如果加载的图像不是宽度的两倍,SDL_Image 将其填充。因此,传递给函数的 "width" 应该是图像的间距,而不是宽度。
我正在使用 SDL2 为 OpenGL 创建上下文。我使用 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
这是此代码循环的一次迭代的直观图示:
- 复制当前行N到缓冲区
- 复制行(行-N)到第N行
- 将缓冲区复制到行(行 - N)
- 递增 N 并重复直到 N == rows/2
但是,这仅适用于行数为偶数的图像,这很好,因为 opengl 不喜欢二维非幂的纹理。
还需要注意的是,如果加载的图像不是宽度的两倍,SDL_Image 将其填充。因此,传递给函数的 "width" 应该是图像的间距,而不是宽度。