cocos渲染引擎分析(一)-----渲染启动流程

做了两年的cocos引擎底3D层优化和特效,以及对应的编辑器开发。原本的计划中,参考Unity3D引擎,优化cocos渲染流程,实现批次渲染,BRDF渲染,卡通渲染,多材质渲染,延迟渲染,模型阴影,以及HDR,次表面散射,中国风特效,砖石特效等,并制作对应的编辑器。而今只实现了底层优化,批次渲染,BRDF渲染,卡通渲染,多材质渲以及对应编辑器制作。自感部门危险,这几天整理下做过的工作,记录下来,也算对自己工作一个回顾总结。

windows系统中,首先进入main()函数:

 // create the application instance
    AppDelegate app;
    int ret = Application::getInstance()->run();

           然后,进入run函数:

    //初始引擎环境,包括GLView创建(最终实现根据不同的平台进入不同实现),目录设置,LUA绑定C++实现 
   (cocos自身实现和项目加入的三方SDK实现)
    if (!applicationDidFinishLaunching())
        return 1;
    。。。。。。。
    //渲染主循环,根据设置的FPS,while函数每隔一定时间执行一次,执行内容包括项目设置的各种定时器 
   (scheduler,update函数),以及最重要的渲染刷新。
    while(!glview->windowShouldClose())
    {
        QueryPerformanceCounter(&nNow);
        if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
            director->mainLoop();
    }

android环境中,Cocos2dxRenderer继承自GLSurfaceView.Renderer,andriod启动后主循环进入Render的OnDrawFrame(此为android开发的知识):

 public void onDrawFrame(final GL10 gl) {
        //类似winodws的while主循环函数,每隔一定时间进行调用一次nativeRender
        if (sAnimationInterval <= 1.0 / 60 * Cocos2dxRenderer.NANOSECONDSPERSECOND) {
            Cocos2dxRenderer.nativeRender();
        } else {
            final long now = System.nanoTime();
            final long interval = now - this.mLastTickInNanoSeconds;
            if (interval < Cocos2dxRenderer.sAnimationInterval) {
                try {
                    Thread.sleep();
                } catch (final Exception e) {
                }
            }   
            this.mLastTickInNanoSeconds = System.nanoTime();
            Cocos2dxRenderer.nativeRender();
        }
    }

nativeRender是一个静态函数,在Java_org_cocos2dx_lib_Cocos2dxRenderer中实现,通过JNI转换后调用C++也调用mainLoop函数。

JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) {
        cocos2d::Director::getInstance()->mainLoop();
    }

mainLoop中执行drawScene功能:

   //此处的update函数是各种update,action,动画,定时器的入口,如果项目的网络接口部分也是在cocos的 
   update函数实现,此时也是网络接收的入口,这就是为什么进入后台后网络阻塞,进入前台后一下接收到很多 
   消息的原因,类似捕鱼游戏,如果不想做恢复可以让程序在进入后台后,仍然执行update。
  if (! _paused)
        _scheduler->update(_deltaTime);
   //压入模型矩阵
    pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    if (_runningScene)
    {
        //此处为遍历场景的所有节点的数据以及遍历renderQue进行渲染,包括vist,draw,render
        _openGLView->renderScene(_runningScene, _renderer); 
    }

进入遍历函数renderScene,此函数调用scene->render(renderer, Mat4::IDENTITY, nullptr)进入下面,其中renderer为Director传递进来的参数,由Director维护,整个游戏过程和Director一样,只有一个渲染器render:

for (const auto& camera : getCameras())
    {
        //遍历每个相机下的模型
        Camera::_visitingCamera = camera;
        //设置相机投影矩阵
        if (eyeProjection)
            camera->setAdditionalProjection(*eyeProjection * camera->getProjectionMatrix().getInversed());
        camera->setAdditionalTransform(eyeTransform.getInversed());
       //压入和加载投影矩阵,后面专门开一张将cocos的矩阵流程
        director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
        director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, Camera::_visitingCamera->getViewProjectionMatrix());
        //设置当前相机的FBO和视口
        camera->apply();
        camera->clearBackground();
        //遍历节点生成meshCommand加入渲染队列
        visit(renderer, transform, 0);
        //渲染
        renderer->render();
    }

visit是个虚函数,所有的节点类都是基于Node的子类,最终遍历会进入具体类的vist中,主要优化的3D部分,这里以sprite3D为例子:

    bool visibleByCamera = isVisitableByVisitingCamera();
    //按正常的设计,这里应该为visibleByCamera&&!_children.empty(), 
    如果不被当前摄像机可见则不进行遍历,然而,cocos有个设计和这个理念是相悖的,如果一个3D物体加入 
    一个2D的attchnode节点的粒子动画,那么这个粒子就不会被2D摄像机看到,因为父亲节点被2D摄像机给 
   排除了,所以明知是个无用的遍历,还是要遍历下
    if(!_children.empty())
    {
        //根据_localZOrder的顺序进行孩子节点排序,cocos的Order排序有三种依据LocalZOrder、 
        GlobalZOrder、OrderOfArrival,优先级为GlobalZOrder>LocalZOrder>OrderOfArrival
        sortAllChildren();
        // 首先遍历_localZOrder<0的孩子节点
        for( ; i < _children.size(); i++ )
        {
            auto node = _children.at(i);
            if (node && node->getLocalZOrder() < 0)
                node->visit(renderer, _modelViewTransform, flags);
            else
                break;
			}
        // 遍历自身的模型数据
        if (visibleByCamera)
            this->draw(renderer, _modelViewTransform, flags);
        //最后遍历_localZOrder>0的孩子节点
        for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
            (*it)->visit(renderer, _modelViewTransform, flags);
    }
    else if (visibleByCamera)
    {
        this->draw(renderer, _modelViewTransform, flags);
    }

 Sprite3D的draw函数如下:

    Color4F color(getDisplayedColor());
    color.a = getDisplayedOpacity() / 255.0f;//此处应加一个判断,当透明度为零时,不进行绘制
    for (auto mesh: _meshes)
    {
        //面片生成meshCommd
        mesh->draw(renderer,
                  .......);

    }

Mesh::draw的详情如下:

  void Mesh::draw()
 {
  _meshCommand.init(globalZ,
                      _material,
                      getVertexBuffer(),//创建顶点缓冲区
                      getIndexBuffer(),//创建索引缓存区
                      getPrimitiveType(),//获得绘制类别,线,多边形,三角形
                      getIndexFormat(),//索引格式
                      getIndexCount(),//索引数
                      transform,//模型矩阵
                      flags);//3d,透明等的标志
    _material->getStateBlock()->setDepthWrite(true);//cocos的深度设置有问题,透明时不应该开 
    启深度写入功能,透明物体的渲染顺序需要在c3t中手动调整,cocos的bug
    _material->getStateBlock()->setBlend(_force2DQueue || isTransparent);//设置透明混合
    //shader中传入Uniform变量,cocos变量写死,如果加入新特效需要加入新的传入参数,
    const auto scene = Director::getInstance()->getRunningScene();
    auto technique = _material->_currentTechnique;
    for(const auto pass : technique->_passes)
    {
        auto programState = pass->getGLProgramState();
        programState->setUniformVec4("u_color", color);
        //传入光照的uniform
        if (scene && scene->getLights().size() > 0)
            setLightUniforms(pass, scene, color, lightMask);
    }
    //加入_meshCommand到渲染队列中
    renderer->addCommand(&_meshCommand);
}

     addCommand的函数如下:

void Renderer::addCommand(RenderCommand* command)
{
    int renderQueue =_commandGroupStack.top();
    addCommand(command, renderQueue);
}
addCommnd函数如下:
void Renderer::addCommand(RenderCommand* command, int renderQueue)
{
    _renderGroups[renderQueue].push_back(command);
}
push_back函数如下:
void RenderQueue::push_back(RenderCommand* command)
{
    float z = command->getGlobalOrder();
    if(z < 0)
        _commands[QUEUE_GROUP::GLOBALZ_NEG].push_back(command);
    else if(z > 0)
        _commands[QUEUE_GROUP::GLOBALZ_POS].push_back(command);
    else
    {
        if(command->is3D())
        {
            if(command->isTransparent())
                _commands[QUEUE_GROUP::TRANSPARENT_3D].push_back(command);
            else
                _commands[QUEUE_GROUP::OPAQUE_3D].push_back(command);
        }
        else
           _commands[QUEUE_GROUP::GLOBALZ_ZERO].push_back(command);
    }
}
RenderQueue在加入新commd时,会根据command的global和透明属性将command分到一下五个类别中
  enum QUEUE_GROUP
    { 
        GLOBALZ_NEG = 0,//globalZorder<0最先绘制
        OPAQUE_3D = 1,//globalZorder=0,但是属于3D不透明物体
        TRANSPARENT_3D = 2,//globalZorder=0,但是属于3D透明物体
        GLOBALZ_ZERO = 3,//globalZorder=0,2D物体
        GLOBALZ_POS = 4,globalZorder>0最后绘制
    };

       renderScene的 visit函数执行完后,进入renderer->render()中:

   void Renderer::render()
   {
      for (auto &renderqueue : _renderGroups)
        //首先排序渲染队列
        renderqueue.sort();
        visitRenderQueue(_renderGroups[0]);
   }
     
   void RenderQueue::sort()
   {
   //不同类别调用不用的排序算法
    std::sort(std::begin(_commands[QUEUE_GROUP::TRANSPARENT_3D]), std::end(_commands[QUEUE_GROUP::TRANSPARENT_3D]), compare3DCommand);
    std::sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_NEG]), std::end(_commands[QUEUE_GROUP::GLOBALZ_NEG]), compareRenderCommand);
    std::sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_POS]), std::end(_commands[QUEUE_GROUP::GLOBALZ_POS]), compareRenderCommand);
   }
    

     排序渲染队列后,进入遍历渲染队列的函数:

void Renderer::visitRenderQueue(RenderQueue& queue)
{
    //处理globaZorder<0队列
    const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG);
    if (zNegQueue.size() > 0)
    {
        。。。。。。//渲染属性设置
        for (auto it = zNegQueue.cbegin(); it != zNegQueue.cend(); ++it)
            processRenderCommand(*it);//进入渲染
        flush();
    }
    //处理3D不透明队列
    const auto& opaqueQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::OPAQUE_3D);
    if (opaqueQueue.size() > 0)
    {
        。。。。。。
    }
    //处理3D透明队列
    const auto& transQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::TRANSPARENT_3D);
    if (transQueue.size() > 0)
    {
        。。。。。。
    }
    //处理globaLZorder等于0的非3D队列
    const auto& zZeroQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_ZERO);
    if (zZeroQueue.size() > 0)
    {
       。。。。。
    }
    //处理Global-Z > 0 队列
    const auto& zPosQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_POS);
    if (zPosQueue.size() > 0)
    {
        。。。。。
    }
    //恢复到默认状态,包括深度写入,背面或者前面渲染,深度测试
    queue.restoreRenderState();
}

       processRenderCommand出来渲染命令如下:

void Renderer::processRenderCommand(RenderCommand* command)
{
    //根据渲染类型调用不同的渲染指令,指令类型如下
  enum class Type
    {
        UNKNOWN_COMMAND,
        QUAD_COMMAND,//多边形渲染指令
        /**自定义渲染,可用于加入自定制的渲染命令,可用于自定义3D批次渲染.*/
        CUSTOM_COMMAND,
        /**批次渲染使用图集纹理中的节点*/
        BATCH_COMMAND,
        /**指令集合,其他指令的集合,需要遍历其子节点指令*/
        GROUP_COMMAND,
        /**网格渲染指令,以三角面为绘制单元的绘制指令*/
        MESH_COMMAND,
        /**原生指令绘制点,线,多边形.*/
        PRIMITIVE_COMMAND,
        /**Triangles command, used to draw triangles.*/
        TRIANGLES_COMMAND
    };

    auto commandType = command->getType();
    if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
    {
        auto cmd = static_cast<TrianglesCommand*>(command);   
        drawBatchedTriangles();
        _queuedTriangleCommands.push_back(cmd);
        _filledIndex += cmd->getIndexCount();
        _filledVertex += cmd->getVertexCount();
    
    }
    else if()
      ........
   
}

下面网格渲染指令为例,如下代码:

void MeshCommand::execute()
{
 if (_material)
    {
       // 创建缓存区内容已经在mesh::Draw中完成
       glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);//绑定顶点缓冲区
       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);//绑定索引缓冲区
       {
        //设置渲染面
         _material->getStateBlock()->setCullFaceSide(pass->getCullFaceSide());  
        // 传递模型属性(设置顶点属性,Uniform变量,各种矩阵变量)
        _glProgramState->apply(_mv);
        // 绘制
        glDrawElements(_primitive, (GLsizei)_indexCount, _indexFormat, 0);
        // 统计绘制的顶点数
        CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, _indexCount);
      }

      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
      glBindBuffer(GL_ARRAY_BUFFER, 0);
   } else
    {
       
        _glProgramState->apply(_mv);
        //纹理传入
        applyRenderState();
        // 绘制
        glDrawElements(_primitive, (GLsizei)_indexCount, _indexFormat, 0);
        
        CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, _indexCount);
    }

}

    到此,cocos完成渲染一个渲染指令,后面指令遍历完成swapbuffer后显示到屏幕。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值