openGL 在运行时创建纹理图集?

Posted

技术标签:

【中文标题】openGL 在运行时创建纹理图集?【英文标题】:openGL Creating texture atlas at run time? 【发布时间】:2012-03-28 03:45:29 【问题描述】:

因此,我在一个简洁的小系统中设置了我的框架,以将 SDL、openGL 和 box2D 封装在一起,用于 2D 游戏。

现在它的工作原理是我创建一个“GameObject”类的对象,指定一个“源PNG”,然后它会自动创建一个openGL纹理和一个相同尺寸的box2d主体。

现在我担心是否开始需要在屏幕上渲染许多不同的纹理。

是否可以在运行时加载我的所有精灵表,然后将它们全部组合成一个纹理?如果是这样,怎么做?什么是实现它的好方法(这样我就不必手动指定任何参数或任何东西)。

我想在运行时而不是预先完成的原因是,我可以轻松地将某个级别的所有(或大部分)图块、敌人等加载到这个纹理中,因为每个级别不会有相同的敌人。它还可以让整个艺术创作过程变得更容易。

【问题讨论】:

多少是多少?我会更担心重用现有纹理——引擎会处理这个吗? 我想我最多可以同时拥有 100 种不同的纹理,所以这将是 100 次绑定操作。另外,当您说重用现有纹理时,您的意思是,如果我有一个使用了 12 次的某个图块,引擎应该在仅绑定一次而不是重新绑定时绘制它 12 次,对吗?那将是一个好主意..有什么方法可以检查当前绑定了哪个纹理,所以我不重新绑定它?或者我是否必须创建一个系统来将相同的纹理绘制在一起以减少绑定......但是如果我想按特定顺序绘制它们以进行深度排序怎么办? 是的,没错——这会保存绑定。第二条评论更针对具有相同数据的纹理,忘记了。 【参考方案1】:

可能已经存在一些用于创建纹理图集的库(优化打包是一个非常重要的问题)并将旧的纹理坐标转换为新的坐标。

但是,如果你想自己做,你可能会这样做:

从磁盘(您的“源 PNG”)加载所有纹理并检索原始像素数据缓冲区, 如有必要,将所有源纹理转换为相同的像素格式, 创建一个足以容纳所有现有纹理的新纹理,以及一个用于容纳像素数据的相应缓冲区 将像素数据从源图像“Blit”到新缓冲区中的给定偏移量(见下文) 使用新缓冲区的数据照常创建纹理。 在执行此操作时,确定从“旧”纹理坐标到“新”纹理坐标的映射(应该是记录纹理图集中每个元素的偏移量并进行快速变换的简单问题)。在像素着色器中执行此操作可能也很容易,但需要进行一些分析以查看传递额外参数的开销是否值得。

显然,您还想检查以确保您没有做一些愚蠢的事情,例如将相同的纹理两次加载到图集中,但这是此过程之外的问题。


要从源图像“blit”(复制)到目标图像,您需要执行以下操作(假设您正在将 128x128 纹理复制到 512x512 地图集纹理,从目标上的 (128, 0) 开始) ):

unsigned char* source = new unsigned char[ 128 * 128 * 4 ]; // in reality, comes from your texture loader
unsigned char* target = new unsigned char[ 512 * 512 * 4 ];

int targetX = 128;
int targetY = 0;

for(int sourceY = 0; sourceY < 128; ++sourceY) 
    for(int sourceX = 0; sourceX < 128; ++sourceX) 
        int from = (sourceY * 128 * 4) + (sourceX * 4); // 4 bytes per pixel (assuming RGBA)
        int to = ((targetY + sourceY) * 512 * 4) + ((targetX + sourceX) * 4); // same format as source

        for(int channel = 0; channel < 4; ++channel) 
            target[to + channel] = source[from + channel];
        
    

这是一个非常简单的蛮力实现:有更快、更简洁和更聪明的方法来复制数组,但其想法是,您基本上是将源纹理的内容复制到给定的目标纹理中X 和 Y 偏移。最后,您将创建一个包含旧纹理的新纹理。

如果索引数学对您没有意义,请考虑how a 2D array is actually indexed inside a 1D space(例如计算机内存)。

请原谅任何错误。这不是生产代码,而是我在没有检查它是否编译或运行的情况下编写的代码。


由于您使用的是 SDL,我应该提一下它有一个很好的功能可以帮助您:SDL_BlitSurface。您可以完全在 SDL 中创建 SDL_Surface,然后只需使用 SDL_BlitSurface 将源表面复制到其中,然后使用 convert the atlas surface into a GL texture。

它会处理所有的数学运算,还可以即时为您进行格式转换。

【讨论】:

感谢您的回答 ravuya!我想我知道如何执行您所说的所有操作,除了:“将源图像中的像素数据以给定的偏移量“Blit”到新缓冲区中”任何有关我将如何以代码方式执行此操作的链接或方向将不胜感激,再次感谢! 我快速解释了“blitting”的含义——它实际上是将一个数组复制到另一个数组中。 等等,基本上,openGL 中的纹理包含它们的像素数据(称为缓冲区?),我们要做的是从每个单独的纹理中复制像素数据并将它们全部扔到一个大像素缓冲区中东西,然后将其应用于我的大纹理,有效地将所有小纹理复制到大纹理中?我想我可以处理复制的数学,我只是不确定我需要在 openGL 中使用什么函数来访问像素数据。再次感谢您迄今为止的帮助,我真的很感激! 嗯......这太棒了!希望它像听起来一样简单!非常非常感谢拉武亚!这应该就是我让它工作所需要的。 您可以完全跳过中间的 blitting 部分,使用 glTexSubImage2D 将较小的图像直接上传到目标纹理中的位置。

以上是关于openGL 在运行时创建纹理图集?的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL 中的 2D 绘图:在原始尺寸下具有像素精度的线性过滤

如何向我的 openGL 程序添加多个纹理?

SpriteKit 示例游戏错误:纹理未加载?

Awesomium 与 OpenGL 纹理转换

SpriteKit animateWithTextures 不适用于纹理图集

确保释放 OpenGL 纹理内存