cocos2d-x - Render 渲染

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::sortGLOBALZ_NEGGLOBALZ_POSTRANSPARENT_3D命令在绘制前会进行排序,GLOBALZ_NEGGLOBALZ_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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值