网上流传着一份《Cocos2d (v.3.0) rendering pipeline roadmap 》,因为需要,我已经放到CSDN上了。看了这篇文档之后去看Cocos2dx的源码发现文档有些是错的,如果大家不信也可以去看,先了解个大概再自己跟着源码去看个究竟。
Cocos2dx3.0引入了globalZOrder的概念,与 v3.0 之前的 localZOrder相比就是世界坐标系跟本地坐标系的关系,localZOrder就是节点相对与其直接父节点的逻辑深度,globalZOrder就是所有节点相对于根节点的逻辑深度。如果大家还不清楚可以参考这篇文章。
Cocos2dx3.0的自动批次渲染说白了就是对所有节点的 globalZOrder 从小到大进行排序(为什么不是从大到小,因为Cocos用的是笛卡尔坐标系!!),然后按照 globalZOrder 从小到大的顺序,如果连续的节点使用相同的纹理,相同的混合函数,相同的shader,就可以合并为一个drawcall(为什么要这三个相同?这属于图形渲染管线的知识,大家可以去参考《real time rendering》,或者《GPU编程与Cg语言之阳春白雪下里巴人》)。见下图:
VBO 是Vertex Buffer Object的缩写。
换言之,如果我们改变节点的 globalZOrder ,游戏的 draw call 可能因此改变。实验如下:
auto sprite = Sprite::create("HelloWorld.png");
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
this->addChild(sprite,1);
auto sprite1 = Sprite::create("Clear.png");
sprite1->setPosition(Vec2(origin.x, origin.y));
this->addChild(sprite1,2);
auto sprite2 = Sprite::create("HelloWorld.png");
sprite2->setPosition(Vec2(origin.x + 100, origin.y + 100));
this->addChild(sprite2,1);
auto sprite3 = Sprite::create("Clear.png");
sprite3->setPosition(Vec2(origin.x + 500, origin.y + 500));
this->addChild(sprite3,2);
auto sprite4 = Sprite::create("HelloWorld.png");
sprite4->setPosition(Vec2(origin.x, origin.y + 500));
this->addChild(sprite4,1);
改变下 localZOrder 的数值(同时改变了节点的 globalZOrder数值),draw call 次数就改变了:
auto sprite = Sprite::create("HelloWorld.png");
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
this->addChild(sprite,1);
auto sprite1 = Sprite::create("Clear.png");
sprite1->setPosition(Vec2(origin.x + 500, origin.y));
this->addChild(sprite1,2);
auto sprite2 = Sprite::create("HelloWorld.png");
sprite2->setPosition(Vec2(origin.x + 300, origin.y + 300));
this->addChild(sprite2,3);
auto sprite3 = Sprite::create("Clear.png");
sprite3->setPosition(Vec2(origin.x + 500, origin.y + 500));
this->addChild(sprite3,4);
auto sprite4 = Sprite::create("HelloWorld.png");
sprite4->setPosition(Vec2(origin.x, origin.y + 500));
this->addChild(sprite4,5);
下面是代码分析过程,注释标有“跟踪进去”的代码就是我们要跟踪进去的,代码有省略,只给出关键代码。
int Application::run()
{
//此处省略代码
while(!glview->windowShouldClose())
{
QueryPerformanceCounter(&nNow);
if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
{
nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);
director->mainLoop(); 跟踪进去
glview->pollEvents();
}
else
{
Sleep(1);
}
}
//此处省略代码
return 0;
}
void DisplayLinkDirector::mainLoop()
{
// 省略代码
drawScene(); //跟踪进去
// 省略代码
}
void Director::drawScene()
{
// 省略代码
// draw the notifications node
if (_notificationNode)
{
_notificationNode->visit(_renderer, Mat4::IDENTITY, 0); //遍历节点,跟踪进去
}
if (_displayStats)
{
showStats();
}
_renderer->render(); // 跟踪进去
_eventDispatcher->dispatchEvent(_eventAfterDraw);
// 省略代码
}
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
// 省略代码
if(!_children.empty())
{
sortAllChildren(); // 根据节点的localZOrder进行排序,个人觉得没什么用,因为后面渲染的时候又对节点的globalZOrder进行排序
// draw children zOrder < 0
for( ; i < _children.size(); i++ )
{
auto node = _children.at(i);
if (node && node->_localZOrder < 0)
node->visit(renderer, _modelViewTransform, flags);
else
break;
}
// self draw
if (visibleByCamera)
this->draw(renderer, _modelViewTransform, flags); // 跟踪进去
for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
(*it)->visit(renderer, _modelViewTransform, flags);
}
else if (visibleByCamera)
{
this->draw(renderer, _modelViewTransform, flags);
}
}
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) //以Sprite的渲染为例
{
// 省略代码
_quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform, flags);
renderer->addCommand(&_quadCommand); // 把要渲染的节点压入渲染队列
// 省略代码
}
然后我们可以去看render 函数的代码了:
void Renderer::render()
{
//省略代码
if (_glViewAssigned)
{
//Process render commands
//1. Sort render commands based on ID
for (auto &renderqueue : _renderGroups)
{
renderqueue.sort(); //这里就是按照节点的globalZOrder进行排序
}
visitRenderQueue(_renderGroups[0]); // 跟踪进去
}
//省略代码
}
void Renderer::visitRenderQueue(RenderQueue& queue)
{
// 省略代码
processRenderCommand(*it); //跟踪进去
// 省略代码
}
void Renderer::processRenderCommand(RenderCommand* command)
{
auto commandType = command->getType();
if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
{
}
else if ( RenderCommand::Type::QUAD_COMMAND == commandType )
{
//Draw if we have batched other commands which are not quad command
flush3D();
flushTriangles();
//Process quad command
auto cmd = static_cast(command);
//Draw batched quads if necessary
if(cmd->isSkipBatching()|| (_numberQuads + cmd->getQuadCount()) * 4 > VBO_SIZE )
{
//Draw batched quads if VBO is full
drawBatchedQuads(); // 跟踪进去
}
//Batch Quads
_batchQuadCommands.push_back(cmd);
fillQuads(cmd);
if(cmd->isSkipBatching())
{
drawBatchedQuads();
}
}
else if (RenderCommand::Type::MESH_COMMAND == commandType)
{
}
else if(RenderCommand::Type::GROUP_COMMAND == commandType)
{
}
else if(RenderCommand::Type::CUSTOM_COMMAND == commandType)
{
}
else if(RenderCommand::Type::BATCH_COMMAND == commandType)
{
}
else if(RenderCommand::Type::PRIMITIVE_COMMAND == commandType)
{
}
else
{
}
}
void Renderer::drawBatchedQuads()
{
//Start drawing verties in batch
for(const auto& cmd : _batchQuadCommands)
{
auto newMaterialID = cmd->getMaterialID();
if(_lastMaterialID != newMaterialID || newMaterialID == MATERIAL_ID_DO_NOT_BATCH)
{
//Draw quads
if(indexToDraw > 0)
{
glDrawElements(GL_TRIANGLES, (GLsizei) indexToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (startIndex*sizeof(_indices[0])) );
_drawnBatches++;
_drawnVertices += indexToDraw;
startIndex += indexToDraw;
indexToDraw = 0;
}
//Use new material
cmd->useMaterial();
_lastMaterialID = newMaterialID;
}
}
}