cocos2dx 3.x跟当年我用的cocos2dx 2.x在熏染上做了很大的改动。已经默认使用了自动合批的方式,简单做了一个实验,在屏幕上绘制3000个精灵,并进行随机移动:
1)第一种方式是每一个sprite都源自一张独立的图片,不合批,代码和数据如下:
1 2 3 4 5 6 7 8 |
for(int i=0; i<3000; i++){ auto *sprite = Sprite::create(); sprite->initWithFile(StringUtils::format("res/item/item_%d.png", i % 8 + 1)); sprite->setPosition(cocos2d::random<float>(-300, 300), cocos2d::random<float>(-300.0f, 300.0f)); auto move = MoveBy::create(3.0f, Vec2(cocos2d::random<float>(-200, 200), cocos2d::random<float>(-200.0f, 200.0f))); sprite->runAction(Repeat::create(Sequence::create(move, move->reverse(),NULL), INT_MAX)); mContainer->getChildByName("root_node")->addChild(sprite); } |
2)第二种方式,使用TexturePacker 将以上图片合并,代码和数据如下:
1 2 3 4 5 6 7 8 9 |
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("res/item/candy_patch.plist"); for(int i=0; i<3000; i++){ auto *sprite = Sprite::create(); sprite->initWithSpriteFrameName(StringUtils::format("item_%d.png", i % 8 + 1)); sprite->setPosition(cocos2d::random<float>(-300, 300), cocos2d::random<float>(-300.0f, 300.0f)); auto move = MoveBy::create(3.0f, Vec2(cocos2d::random<float>(-200, 200), cocos2d::random<float>(-200.0f, 200.0f))); sprite->runAction(Repeat::create(Sequence::create(move, move->reverse(),NULL), INT_MAX)); mContainer->getChildByName("root_node")->addChild(sprite); } |
可以看到两者的Draw Call 和帧率差距比较大。这种变化正是cocos2dx合批带来的。我们知道一个批次(batch)是指一次GPU的绘制调用,CPU把顶点送入GPU,同时也会进行一次渲染管线的状态切换,这个过程是比较耗时的。因此需要尽量减少渲染过程中的批次,减少状态切换数量。cocos2dx的做法就是把相同材质的Sprite合并成一个批次。具体做法我们来看一下代码(cocos2dx 3.11):
当我们绘制一个Sprite时,在Sprite::draw函数中,会去初始化一个渲染命令(TriangleCommand):
1 2 3 4 5 6 7 8 |
_trianglesCommand.init( _globalZOrder , _texture->getName() , getGLProgramState() , _blendFunc , _polyInfo.triangles , transform, flags); renderer->addCommand(&_trianglesCommand); |
而在初始化的过程中,会生成一个材质ID:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void TrianglesCommand::generateMaterialID() { // do not batch if using custom uniforms (since we cannot batch) it if(_glProgramState->getUniformCount() > 0) { _materialID = Renderer::MATERIAL_ID_DO_NOT_BATCH; setSkipBatching(true); } else { int glProgram = (int)_glProgram->getProgram(); int intArray[4] = { glProgram, (int)_textureID, (int)_blendType.src, (int)_blendType.dst}; _materialID = XXH32((const void*)intArray, sizeof(intArray), 0); } } |
我们看到,这个材质ID,是由shader对象指针,贴图ID,混合选项参数,这几项哈希得来,也就是说,这些参数相同的Sprite,会生成相同的材质ID。
而在最后绘制的时候:
1 2 3 4 5 6 7 8 |
//void Renderer::drawBatchedTriangles() // in the same batch ? if (batchable && (prevMaterialID == currentMaterialID || firstCommand)) { CC_ASSERT(firstCommand || _triBatchesToDraw[batchesTotal].cmd->getMaterialID() == cmd->getMaterialID() && "argh... error in logic"); _triBatchesToDraw[batchesTotal].indicesToDraw += cmd->getIndexCount(); _triBatchesToDraw[batchesTotal].cmd = cmd; } |
相同材质ID的三角形会被合并到一起,然后同时送入GPU,从而达到合批的目的。所以在上面的例子中,所有Sprite的shader和混合参数都是相同的,如果他们用同一张贴图,哪就会满足合批的条件,所以把所有精灵的贴图合成一张,就可以把3000个sprite合成一个批次。加速渲染。
在cocos2dx的实现中,有几个问题需要注意:
1)它只会把相邻的两个Sprite合并到一起,所以尽管在分散图片的例子中,有很多sprite使用了相同的贴图,但是它们的渲染次序没有相邻,所以也不会合批。所以每帧会有3000次draw call。
2)如果要渲染的Sprite使用了自定义的Shader,如果Shader存在Uniform参数,也不会进行合批。
你好,
我在網路上看到”用Shader绘制攻击范围的圆环”這篇文章,
在使用了你提供的Shader卻達不到範例圖的效果,
正確的說應該是繪製不出圓環的效果,
想請問是否有什麼我應該注意卻沒注意到的問題?
对不起,从您的描述来看,我也无法判断您的问题出在什么地方。我这个方法已经在项目验证过了,你可以提供更多信息。