分析
z order主要作用于渲染阶段,先从Director::mainLoop()开始看代码(游戏运行过程中每帧都会调用mainLoop函数,用于每帧处理事件、渲染图像等):
void Director::mainLoop()
{
//...
{
//处理事件并绘制图像
drawScene();
//...
}
}
void Director::drawScene()
{
//...
if (! _paused)
{
_eventDispatcher->dispatchEvent(_eventBeforeUpdate);
//处理事件
_scheduler->update(_deltaTime);
_eventDispatcher->dispatchEvent(_eventAfterUpdate);
}
//清空上一帧渲染的内容
_renderer->clear();
experimental::FrameBuffer::clearAllFBOs();
//...
if (_runningScene)
{
//...
if(_openGLView)
//渲染当前运行的场景
_openGLView->renderScene(_runningScene, _renderer);
//...
}
//...
}
void GLView::renderScene(Scene* scene, Renderer* renderer)
{
//...
{
//渲染场景
scene->render(renderer, Mat4::IDENTITY, nullptr);
}
}
void Scene::render(Renderer* renderer, const Mat4& eyeTransform, const Mat4* eyeProjection)
{
render(renderer, &eyeTransform, eyeProjection, 1);
}
void Scene::render(Renderer* renderer, const Mat4* eyeTransforms, const Mat4* eyeProjections, unsigned int multiViewCount)
{
{
//...
//遍历Scene中的节点
visit(renderer, transform, 0);
//...
//渲染
renderer->render();
//...
}
//...
}
//中序遍历访问节点
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
int i = 0;
//如果这个节点有子节点
if(!_children.empty())
{
//排序所有子节点
sortAllChildren();
//先访问local z order < 0的节点
for(auto size = _children.size(); i < size; ++i)
{
auto node = _children.at(i);
//如果local z order >= 0就会走else跳出循环
if (node && node->_localZOrder < 0)
node->visit(renderer, _modelViewTransform, flags);
else
break;
}
//draw自身
if (visibleByCamera)
this->draw(renderer, _modelViewTransform, flags);
//访问local z order >= 0的节点
for(auto it=_children.cbegin()+i, itCend = _children.cend(); it != itCend; ++it)
(*it)->visit(renderer, _modelViewTransform, flags);
}
//没有子节点的情况
else if (visibleByCamera)
{
this->draw(renderer, _modelViewTransform, flags);
}
//...
}
首先看sortAllChildren函数是怎么给_children排序的:
void Node::sortAllChildren()
{
//如果修改过z order
if (_reorderChildDirty)
{
//排序
sortNodes(_children);
_reorderChildDirty = false;
//...
}
}
template<typename _T> inline
static void sortNodes(cocos2d::Vector<_T*>& nodes)
{
//...
#if CC_64BITS
std::sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {
return (n1->_localZOrder$Arrival < n2->_localZOrder$Arrival);
});
#else
std::sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {
//首先根据local z order从小到大排序,如果相同根据order of arrival从小到大排序
return (n1->_localZOrder == n2->_localZOrder && n1->_orderOfArrival < n2->_orderOfArrival) || n1->_localZOrder < n2->_localZOrder;
});
#endif
}
(为什么这里可以只使用LocalZOrder$Arrival排序?)
在中序遍历到自身时,调用了draw函数,但是注意调用draw函数时并没有真正渲染,而只是把渲染当前节点的Command(指令)插入到了RenderQueue(渲染队列)中(Node不进行渲染,这里使用Sprite的draw函数):
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
//...
{
//初始化一个渲染指令
_trianglesCommand.init(_globalZOrder,
_texture,
getGLProgramState(),
_blendFunc,
_polyInfo.triangles,
transform,
flags);
//添加指令到渲染器中
renderer->addCommand(&_trianglesCommand);
//...
}
}
void Renderer::addCommand(RenderCommand* command)
{
//获取到最后一个渲染队列id(实际上默认情况只有一个渲染队列)
int renderQueueID =_commandGroupStack.top();
addCommand(command, renderQueueID);
}
void Renderer::addCommand(RenderCommand* command, int renderQueueID)
{
//...
_renderGroups[renderQueueID].push_back(command);
}
void RenderQueue::push_back(RenderCommand* command)
{
float z = command->getGlobalOrder();
//根据节点的global z order插入到不同的指令数组中
if(z < 0)
{
_commands[QUEUE_GROUP::GLOBALZ_NEG].push_back(command);
}
else if(z > 0)
{
_commands[QUEUE_GROUP::GLOBALZ_POS].push_back(command);
}
else
{
//...
{
_commands[QUEUE_GROUP::GLOBALZ_ZERO].push_back(command);
}
}
}
到此为止,可见draw函数只是插入了一条渲染指令到渲染队列,而真正的渲染过程在之前 void Scene::render(Renderer* renderer, const Mat4* eyeTransforms, const Mat4* eyeProjections, unsigned int multiViewCount) 的renderer->render();中:
void Renderer::render()
{
//...
{
for (auto &renderqueue : _renderGroups)
{
//对每个渲染队列排序
renderqueue.sort();
}
//访问渲染队列(开始渲染)
visitRenderQueue(_renderGroups[0]);
}
//...
}
先看renderqueue.sort():
void RenderQueue::sort()
{
//...
//global z order为负和为正的都根据compareRenderCommand函数排序
std::stable_sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_NEG]), std::end(_commands[QUEUE_GROUP::GLOBALZ_NEG]), compareRenderCommand);
std::stable_sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_POS]), std::end(_commands[QUEUE_GROUP::GLOBALZ_POS]), compareRenderCommand);
}
static bool compareRenderCommand(RenderCommand* a, RenderCommand* b)
{
//按照global z order从小到大排序
return a->getGlobalOrder() < b->getGlobalOrder();
}
由此可知最终每个渲染队列中的指令又根据global z order重新排序了一遍,而当global z order相同时,stable_sort不会改变元素的顺序,所以global z order相同的节点,顺序依然按照插入时的顺序(local z order的顺序)排序。最后是渲染:
void Renderer::visitRenderQueue(RenderQueue& queue)
{
//处理global z order < 0的指令
const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG);
if (zNegQueue.size() > 0)
{
//...渲染
}
//...
//处理global z order == 0的指令
const auto& zZeroQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_ZERO);
if (zZeroQueue.size() > 0)
{
//...渲染
}
//处理global z order > 0的指令
const auto& zPosQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_POS);
if (zPosQueue.size() > 0)
{
//...渲染
}
}
结论
local z order和order of arrival影响的只是渲染指令的添加顺序,渲染的顺序始终首先由global z order决定,global z order相同时再根据指令添加的顺序决定。
本文深入剖析了Cocos2d-x游戏引擎中GlobalZOrder、LocalZOrder和OrderOfArrival对渲染顺序的影响。通过分析`Director::mainLoop()`、`sortAllChildren()`和渲染队列的过程,揭示了在全局z order相同的情况下,局部z order和到达顺序如何决定节点的绘制顺序。
352

被折叠的 条评论
为什么被折叠?



