最近碰到了一点问题,关于图片载入和显示方面的。
自己的引擎显示方面是基于opengl来实现的。显示图片也就是画一个矩形,然后贴上纹理。并且采用了批量绘图方法,将顶点数据存入显存里。用到了glsl。不知道的可以百度一下。
那在大量载入图片成为纹理并存入显存就有问题出现。本地文件先不说,载入到显存后的显存占用就成了问题。网上百度了一番了一些解决方法:
1.压缩纹理
很简单,具体怎么做百度一下你就知道。这里简单说一下,这分2个部分,内部压缩和,载入压缩纹理。
简单讲一下,内部压缩就是,源文件并不是压缩后的文件,所以opengl需要通过第三方库来加载文件,生成的unsigned char数组,这个数组大小是:unsigned char的大小也就是1Byte * 图片长 * 图片宽 * 通道数,然后使用glTexImage2D这类命令将数组传到内存中并绑定一个数字便于使用这个纹理。在这个过程中进行压缩,压缩后的现存占用最大可降低8倍。当然压缩也是占用时间的。也会失去一些画质。压缩级别分3档可以看需要压缩。
我载入了1000张图片,大小都是350x350,四个通道,占用了700mb左右的显存(不知道为什么比计算的要大)。1/8压缩后就成了:87.5mb,非常爽到!
载入压缩纹理,就是事先压缩好文件成一个dds文件,可以压的很小。怎么做dds文件找度娘吧。很多游戏都使用这个文件。opengl可以直接读取这个文件的数据到显存,不需要第三方库,直接用读取txt文件那样的方法就可以,不需要压缩解压,耗时非常少。
如果你的游戏有大量的纹理,不妨压缩一下可以占用更小的资源,也可以使载入速度更快。
2.进一步降低内存占用
前面说到降低占用,那么可不可以进一步降低占用呢?答案是可以的,以前的游戏为什么可以容量那么低。我估计他们用的是256色的图片,也就是16位的图片。也就是说一张图片顶多拥有256种颜色。每种颜色用3个unsigned char来存,图片的大小就是256 * 3 + 图片长*宽 Byte。
所以这样的图片文件分成2部分,颜色调色板palette和颜色索引color index。
调色板部分就是之前所说的256个颜色,256*3 Byte。
颜色索引就对应图片上的每个像素,不过他不用3个或4个unsigned char来存,他只用1个,就是 长 * 宽 byte。存的都是0-255,对应调色板上的颜色。比24位图片小了3倍左右,比32位图片笑了4倍左右。
我猜测那些游戏的图片全部保存成颜色索引。一个人物,几百张图片,共用一个调色板。这样就可以给人物切调色板不必重新加载一下几百张图片。而且内存占用也可以非常小。
3.调色板
我发现以前很多游戏,比如格斗游戏都可以选择2p角色颜色,也就是可以变色。而且这些游戏占用又非常小。估计是进入游戏后才把需要的图片载入到内存中。然后使用上面所说的256色贴图方法。就可以切换颜色,甚至在游戏中实时切换颜色。
那么opengl要如何做到呢。
opengl的颜色扩展里就可以做到。具体可以百度glColorTableEXT,GL_COLOR_TABLE,GL_SHARED_TEXTURE_PALETTE_EXT。使用的时候可能会发生程序崩溃,原因是这个函数指针是空的。首先检测一下计算机支不支持opengl颜色扩展。具体可百度GL_ARB_imaging。
反正我的机子是不行,不支持这个东西。
有人问,那通过glgetteximage拷贝出纹理的数据,通过替换像素来改变颜色再更新纹理行不行?
答案是可行,但这不是真正的调色板功能,而且效率低下。
举一个例子,一个角色的衣服和裤子都是黑色的,虽然都是黑色,但在调色板里不是一个颜色。这样就能载入不同的调色板让2p角色的衣服和裤子变成不同的颜色。如果按照刚才说的替换颜色的方法来的话,就无法做到这一点。除非把2个黑色弄得接近但不是同一个颜色。
况且2个相同的角色在画面上,用的是同一个纹理,那么把2p的颜色替换了,1p也会被替换。除非2p复制一套新的相同的纹理,再把颜色都替换掉。似乎不是很有效率。
结果只能依靠glsl了。
以下是glsl代码供参考。
uniform sampler1D Palette; // 一个调色板有256个颜色
uniform sampler2D IndexedColorTexture; // 一个纹理中存的是颜色索引
varying vec2 TexCoord0; // UVs
void main()
{
vec4 index = texture2D(IndexedColorTexture, TexCoord0);//提取颜色索引
vec4 texel = texture1D(Palette, index.x);//通过索引从调色板里得到实际的颜色。
gl_FragColor = texel; //显示出来
}
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, 256, 0, GL_BGRA, GL_UNSIGNED_BYTE, pal);
载入调色板给Palette。
这里用了GL_RGBA,也就是说比文件里的RGB调色板多了一个通道,所以data要分配256*4的大小。当然不想要alpha通道也可以。
用
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_Width, m_Height, 0, GL_RED, GL_UNSIGNED_BYTE, colorindex);
载入索引给IndexedColorTexture。
和一般载入图片不一样,这里只用了一个GL_RED通道,因为我们每个像素只需要存0-255的数据就行了。
想要更新调色板的话,用更新纹理一个方法。
这样一个纹理对应一个调色板。就可以现实图片了。至于效率怎么样,虽然我还没测试过,应该比用循环来替换颜色要快多了吧。
因为我都是自学的,可能说得不够好,哪里说的不对或者有什么要补充的欢迎告诉我。
完。