分享Cocos2d-x优化中纹理优化

2015年03月23日 11:39 0 点赞 0 评论 更新于 2025-11-21 18:14

1. 纹理像素格式

纹理优化工作的一个重要指标是纹理像素格式。在最大程度满足用户对保真度要求的前提下,选择合适的像素格式,能够大幅提高纹理的处理速度。此外,纹理像素格式与硬件密切相关。

下面我们先来了解一下主要的纹理像素格式:

  • RGBA8888:32位色,是默认的像素格式。每个通道为8位(比特),每个像素占用4个字节。
  • BGRA8888:同样是32位色,每个通道8位(比特),每个像素4个字节。
  • RGBA4444:16位色,每个通道4位(比特),每个像素2个字节。
  • RGB888:24位色,没有Alpha通道,因此不具备透明度。每个通道8位(比特),每个像素3个字节。
  • RGB565:16位色,没有Alpha通道,没有透明度。R和B通道各为5位,G通道为6位。
  • RGB5A1(或RGBA5551):16位色,每个通道各4位,Alpha通道仅用1位表示。
  • PVRTC4:4位PVR压缩纹理格式,PVR格式是专门为iOS设备上的PowerVR图形芯片设计的。在iOS设备上使用效果极佳,因为可以直接加载到显卡上,无需经过中间的计算转化。
  • PVRTC4A:具有Alpha通道的4位PVR压缩纹理格式。
  • PVRTC2:2位PVR压缩纹理格式。
  • PVRTC2A:具有Alpha通道的2位PVR压缩纹理格式。

此外,PVR格式在保存时还可采用Gzip和zlib压缩格式进行压缩,对应的保存文件为pvr.gz和pvr.ccz。经过压缩后,文件体积会更小,加载时占用的内存也更少。虽然在转化为纹理时需要解压,但对CPU的影响很小。

2. 纹理缓存异步加载

在启动游戏和进入场景时,由于需要加载的资源过多,游戏会出现卡顿现象,影响用户体验。我们可以采用纹理缓存(TextureCache)异步加载纹理图片。TextureCache类的异步加载函数如下:

virtual void addImageAsync(const std::string &filepath, std::function<void(Texture2D *)> callback)

其中,第一个参数是文件路径,第二个参数是回调函数。下面通过一个实例来介绍纹理缓存异步加载的使用。假设有200张小图片,需要加载到纹理缓存中,并且在加载过程中会在界面上显示加载进度。

纹理缓存异步加载实例

HelloWorldScene.cpp中的主要代码如下:

bool HelloWorld::init()
{
if (!Layer::init())
{
return false;
}

Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();

auto closeItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));

closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width / 2,
origin.y + closeItem->getContentSize().height / 2));

auto menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);

_labelLoading = Label::createWithTTF("loading…", "fonts/Marker Felt.ttf", 35);
_labelPercent = Label::createWithTTF("0%", "fonts/Marker Felt.ttf", 35);

_labelLoading->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 - 20));
_labelPercent->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 20));

this->addChild(_labelLoading);
this->addChild(_labelPercent);

_numberOfLoadedSprites = 0;
_imageOffset = 0;

auto sharedFileUtils = FileUtils::getInstance();
std::string fullPathForFilename = sharedFileUtils->fullPathForFilename("ImageMetaData.plist");  // ①
ValueVector vec = FileUtils::getInstance()->getValueVectorFromFile(fullPathForFilename);        // ②
_numberOfSprites = vec.size();                                                                  // ③

// 加载纹理
for (auto& e : vec)                                                                             // ④
{
auto row = e.asValueMap();
auto filename = "icons/" + row.at("filename").asString();
Director::getInstance()->getTextureCache()->addImageAsync(filename,
CC_CALLBACK_1(HelloWorld::loadingCallBack, this));  // ⑤
}

return true;
}

void HelloWorld::loadingCallBack(Texture2D *texture)                                               // ⑥
{
++_numberOfLoadedSprites;
__String* str = __String::createWithFormat("%d%%",
(int)(((float)_numberOfLoadedSprites / _numberOfSprites) * 100));  // ⑦
_labelPercent->setString(str->getCString());                                                    // ⑧

Size visibleSize = Director::getInstance()->getVisibleSize();
int i = ++_imageOffset * 60;

auto sprite = Sprite::createWithTexture(texture);                                               // ⑨
sprite->setAnchorPoint(Vec2(0, 0));
addChild(sprite, -1);
sprite->setPosition(Vec2(i % (int)visibleSize.width, (i / (int)visibleSize.width) * 60));

if (_numberOfLoadedSprites == _numberOfSprites)                                                 // ⑩
{
_numberOfLoadedSprites = 0;
}
}

代码解释

  • 第①行代码:获取资源目录下ImageMetaData.plist文件的全路径。ImageMetaData.plist文件用于描述要加载的图标文件名,其内容如下:
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <array>
    <dict>
    <key>filename</key>
    <string>01-refresh.png</string>
    </dict>
    <dict>
    <key>filename</key>
    <string>02-redo.png</string>
    </dict>
    <dict>
    <key>filename</key>
    <string>03-loopback.png</string>
    </dict>
    <dict>
    <key>filename</key>
    <string>04-squiggle.png</string>
    </dict>
    <!-- ... -->
    </array>
    </plist>
    
  • 第②行代码:通过FileUtils的getValueVectorFromFile函数将ImageMetaData.plist文件内容读入到ValueVector类型的变量vec中。
  • 第③行代码_numberOfSprites = vec.size()用于获取数组的长度,并将其赋值给成员变量_numberOfSprites,以便计算加载进度。
  • 第④行代码:循环遍历数组,数组中的每个元素是键值对结构。auto row = e.asValueMap()用于获取键值对结构,然后通过row.at("filename").asString()从键值对对象row中取出键为filename对应的值。
  • 第⑤行代码:调用TextureCache的addImageAsync函数实现异步加载图片缓存,HelloWorld::loadingCallBack是回调函数,this参数表示回调函数的目标对象。
  • 第⑥行代码:定义回调函数的实现。
  • 第⑦行代码:表达式(int)(((float)_numberOfLoadedSprites / _numberOfSprites) * 100)用于计算加载进度,"%d%%"用于显示百分号,其中%d是格式化输出数字,%%用于输出%,前面的%起到转义作用。
  • 第⑧行代码_labelPercent->setString(str->getCString())用于设置进度标签_labelPercent的内容。
  • 第⑨行代码auto sprite = Sprite::createWithTexture(texture)通过纹理对象texture创建精灵对象。
  • 第⑩行代码if (_numberOfLoadedSprites == _numberOfSprites)用于判断是否完成加载任务,_numberOfLoadedSprites是已加载的图片数,_numberOfSprites是要加载的全部图片数。

作者信息

menghao

menghao

共发布了 3994 篇文章