Render
场景图的渲染大致遵循以下几个步骤:
Director::mainLoop
:每一帧都会执行节点的渲染。void Director::mainLoop() { if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (_restartDirectorInNextLoop) { _restartDirectorInNextLoop = false; restartDirector(); } else if (! _invalid) { drawScene(); // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); } }
Director::drawScene
有几个值得一提的地方:- 定时器更新,前后伴随着事件分发(
drawScene
方法中所有的事件类型都是EventCustom
)。//tick before glClear: issue #533 if (! _paused) { _eventDispatcher->dispatchEvent(_eventBeforeUpdate); _scheduler->update(_deltaTime); _eventDispatcher->dispatchEvent(_eventAfterUpdate); }
- 渲染前事件分发。
_eventDispatcher->dispatchEvent(_eventBeforeDraw);
- 场景切换(如果有的话):正如生命周期中所说的,上一个场景
Exit
,下一个场景Enter
。/* to avoid flickr, nextScene MUST be here: after tick and before draw. * FIXME: Which bug is this one. It seems that it can't be reproduced with v0.9 */ if (_nextScene) { setNextScene(); }
- 渲染场景图,关键在于
_openGLView->renderScene(_runningScene, _renderer);
。遍历结束也会进行事件分发。if (_runningScene) { #if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH) _runningScene->stepPhysicsAndNavigation(_deltaTime); #endif //clear draw stats _renderer->clearDrawStats(); //render the scene if(_openGLView) _openGLView->renderScene(_runningScene, _renderer); _eventDispatcher->dispatchEvent(_eventAfterVisit); }
- 渲染通知节点(独立于场景之外的最上层节点),过程和场景图渲染类似,都是先遍历(
visit
),然后渲染(render
),最后事件分发(dispatchEvent
)。// draw the notifications node if (_notificationNode) { _notificationNode->visit(_renderer, Mat4::IDENTITY, 0); } _renderer->render(); _eventDispatcher->dispatchEvent(_eventAfterDraw);
- 定时器更新,前后伴随着事件分发(
GLView::renderScene
:做了一下断言,然后就跳转到scene::render
void GLView::renderScene(Scene* scene, Renderer* renderer) { CCASSERT(scene, "Invalid Scene"); CCASSERT(renderer, "Invalid Renderer"); scene->render(renderer, Mat4::IDENTITY, nullptr); }
Scene::Render
:场景图渲染,关键在于先遍历节点,再执行渲染指令。void Scene::render(Renderer* renderer, const Mat4& eyeTransform, const Mat4* eyeProjection) { const auto& transform = getNodeToParentTransform(); visit(renderer, transform, 0); renderer->render(); }
Node::Visit
:如层级中所言,按照中序遍历的算法去遍历场景图(访问子节点之前要先根据层级进行排序)。void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags) { if (!_visible) { return; } bool visibleByCamera = isVisitableByVisitingCamera(); int i = 0; if(!_children.empty()) { sortAllChildren(); // draw children zOrder < 0 for(auto size = _children.size(); i < 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, itCend = _children.cend(); it != itCend; ++it) (*it)->visit(renderer, _modelViewTransform, flags); } else if (visibleByCamera) { this->draw(renderer, _modelViewTransform, flags); } }
Node::Draw
:该函数是一个虚函数,只有一个空的实现。在Node
的子类(e.g.DrawNode, Sprite
等)会实现该方法,关键在于向_render
加入渲染指令(renderer->addCommand()
)。# CCNode.cpp void Node::draw(Renderer* /*renderer*/, const Mat4 & /*transform*/, uint32_t /*flags*/) { } # CCDrawNode.cpp void DrawNode::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { if(_bufferCount) { _customCommand.init(_globalZOrder); renderer->addCommand(&_customCommand); } if(_bufferCountGLPoint) { _customCommandGLPoint.init(_globalZOrder); renderer->addCommand(&_customCommandGLPoint); } if(_bufferCountGLLine) { _customCommandGLLine.setLineWidth(_lineWidth); _customCommandGLLine.init(_globalZOrder); renderer->addCommand(&_customCommandGLLine); } } # CCSprite.cpp void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { if (_texture == nullptr || _texture->getBackendTexture() == nullptr) return; _trianglesCommand.init(_globalZOrder, _texture, _blendFunc, _polyInfo.triangles, transform, flags); renderer->addCommand(&_trianglesCommand); }
Render::render
:先对每个渲染队列的五组命令(_commands
)进行排序,然后再取出第一个队列进行渲染。void Renderer::render() { _isRendering = true;//Process render commands //1. Sort render commands based on ID for (auto &renderqueue : _renderGroups) { renderqueue.sort(); } visitRenderQueue(_renderGroups[0]); clean(); _isRendering = false; }
RenderQueue::sort
:GLOBALZ_NEG
、GLOBALZ_POS
、TRANSPARENT_3D
命令在绘制前会进行排序,GLOBALZ_NEG
、GLOBALZ_POS
会根据_globalOrder
(即节点的_globalZOrder
)的值从小到大排序,TRANSPARENT_3D会根据绘制物体的深度(离摄像机的远近)进行排序。void RenderQueue::sort() { // Don't sort _queue0, it already comes sorted std::stable_sort(std::begin(_commands[QUEUE_GROUP::TRANSPARENT_3D]), std::end(_commands[QUEUE_GROUP::TRANSPARENT_3D]), compare3DCommand); 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); }
RenderQueue
含有五组命令_commands
,每组命令的索引使用枚举常量QUEUE_GROUP
来表示::class RenderQueue { public: /** RenderCommand will be divided into Queue Groups. */ enum QUEUE_GROUP { /**Objects with globalZ smaller than 0. */ GLOBALZ_NEG = 0, /**Opaque 3D objects with 0 globalZ.*/ OPAQUE_3D = 1, /**Transparent 3D objects with 0 globalZ.*/ TRANSPARENT_3D = 2, /**2D objects with 0 globalZ.*/ GLOBALZ_ZERO = 3, /**Objects with globalZ bigger than 0.*/ GLOBALZ_POS = 4, QUEUE_COUNT = 5, }; protected: /**Get a sub group of the render queue.*/ std::vector<RenderCommand*>& getSubQueue(QUEUE_GROUP group) { return _commands[group]; } private: /**The commands in the render queue.*/ std::vector<RenderCommand*> _commands[QUEUE_COUNT];
Renderer::visitRenderQueue
:依次处理渲染队列中的五组命令(processRenderCommand(command)
)。// CCRender.cpp void Renderer::visitRenderQueue(RenderQueue& queue) { // //Process Global-Z < 0 Objects // doVisitRenderQueue(queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG)); // //Process Opaque Object // pushStateBlock(); setDepthTest(true); //enable depth test in 3D queue by default setDepthWrite(true); setCullMode(backend::CullMode::BACK); doVisitRenderQueue(queue.getSubQueue(RenderQueue::QUEUE_GROUP::OPAQUE_3D)); // //Process 3D Transparent object // setDepthWrite(false); doVisitRenderQueue(queue.getSubQueue(RenderQueue::QUEUE_GROUP::TRANSPARENT_3D)); popStateBlock(); // //Process Global-Z = 0 Queue // doVisitRenderQueue(queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_ZERO)); // //Process Global-Z > 0 Queue // doVisitRenderQueue(queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_POS)); } void Renderer::doVisitRenderQueue(const std::vector<RenderCommand*>& renderCommands) { for (const auto& command : renderCommands) { processRenderCommand(command); } flush(); }
Render::processRenderCommand
:根据不同的RenderCommand
进行相应的渲染操作。RenderCommand
一共有6种类型:class CC_DLL RenderCommand { public: /**Enum the type of render command. */ enum class Type { /** Reserved type.*/ UNKNOWN_COMMAND, /** Quad command, used for draw quad.*/ QUAD_COMMAND, /**Custom command, used to draw things other then TRIANGLES_COMMAND.*/ CUSTOM_COMMAND, /**Group command, which can group command in a tree hierarchy.*/ GROUP_COMMAND, /**Mesh command, used to draw 3D meshes.*/ MESH_COMMAND, /**Triangles command, used to draw triangles.*/ TRIANGLES_COMMAND, /**Callback command, used for calling callback for rendering.*/ CALLBACK_COMMAND, CAPTURE_SCREEN_COMMAND }; }
TRIANLES_COMMAND
:用于绘制三角形,关键代码见void Renderer::drawBatchedTriangles()
,如果相邻的绘制指令使用了同一个纹理,则可以放在同一批次中绘制。void Renderer::drawBatchedTriangles() { if(_queuedTriangleCommands.empty()) return; /************** 1: Setup up vertices/indices *************/ #ifdef CC_USE_METAL unsigned int vertexBufferFillOffset = _queuedTotalVertexCount - _queuedVertexCount; unsigned int indexBufferFillOffset = _queuedTotalIndexCount - _queuedIndexCount; #else unsigned int vertexBufferFillOffset = 0; unsigned int indexBufferFillOffset = 0; #endif _triBatchesToDraw[0].offset = indexBufferFillOffset; _triBatchesToDraw[0].indicesToDraw = 0; _triBatchesToDraw[0].cmd = nullptr; int batchesTotal = 0; int prevMaterialID = -1; bool firstCommand = true; _filledVertex = 0; _filledIndex = 0; // 进行合批 for(const auto& cmd : _queuedTriangleCommands) { auto currentMaterialID = cmd->getMaterialID(); const bool batchable = !cmd->isSkipBatching(); fillVerticesAndIndices(cmd, vertexBufferFillOffset); // 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; } else { // is this the first one? if (!firstCommand) { batchesTotal++; _triBatchesToDraw[batchesTotal].offset = _triBatchesToDraw[batchesTotal-1].offset + _triBatchesToDraw[batchesTotal-1].indicesToDraw; } _triBatchesToDraw[batchesTotal].cmd = cmd; _triBatchesToDraw[batchesTotal].indicesToDraw = (int) cmd->getIndexCount(); // is this a single batch ? Prevent creating a batch group then if (!batchable) currentMaterialID = -1; } // capacity full ? if (batchesTotal + 1 >= _triBatchesToDrawCapacity) { _triBatchesToDrawCapacity *= 1.4; _triBatchesToDraw = (TriBatchToDraw*) realloc(_triBatchesToDraw, sizeof(_triBatchesToDraw[0]) * _triBatchesToDrawCapacity); } prevMaterialID = currentMaterialID; firstCommand = false; } batchesTotal++; /************** 2: Draw *************/ for (int i = 0; i < batchesTotal; ++i) { beginRenderPass(_triBatchesToDraw[i].cmd); _commandBuffer->setVertexBuffer(_vertexBuffer); _commandBuffer->setIndexBuffer(_indexBuffer); auto& pipelineDescriptor = _triBatchesToDraw[i].cmd->getPipelineDescriptor(); _commandBuffer->setProgramState(pipelineDescriptor.programState); _commandBuffer->drawElements(backend::PrimitiveType::TRIANGLE, backend::IndexFormat::U_SHORT, _triBatchesToDraw[i].indicesToDraw, _triBatchesToDraw[i].offset * sizeof(_indices[0])); _commandBuffer->endRenderPass(); _drawnBatches++; _drawnVertices += _triBatchesToDraw[i].indicesToDraw; } /************** 3: Cleanup *************/ _queuedTriangleCommands.clear(); }
【参考文献】
[1] COCOS学习笔记–Cocos引擎渲染流程
[2] Cocos2d-x 渲染器Renderer与6种RenderCommand详解
[3] Cocos2dx源码记录(10) CCRenderer