• 5 cocos2dx 3.0源码分析 渲染 render


    渲染,感觉这个挺重要了,这里代入一个简单的例子 Sprite 建立及到最后的画在屏幕上, 我们描述一下这个渲染的流程:
     
    1 sprite 初始化(纹理, 坐标,及当前元素的坐标大小信息)
    2 主循环调用sprite的draw(), 把绘制命令发送到系统的render的渲染队列中。 
    3 Render拿到渲染队列中的渲染命令, 分别对每个命令进行处理, 我们这里的QUAD_COMMAND, 把这个命令中的坐标信息复制到自己的渲染缓冲中, 之后通过调用OpenGL命令对当前的矩形进行绘制。 
     

     
    1 Sprite初始化:
     
    如:
        Size visibleSize = Director::getInstance()->getVisibleSize();
        Sprite *sp = Sprite::create("HelloWorld.png");
        sp->setPosition(Point(visibleSize.width * .5, visibleSize.height * .5));
        this->addChild(sp, 1);
     
    1.1 简述:
     
    1> 初始化Sprite, 从纹理缓存中读取纹理的大小及相关信息。 
    2> 初始化当前对象中的_quad属性,Sprite对OpenGL来说就是一个矩形的绘制,要绘制它, 就需要它的顶点信息,纹理信息, 这些信息都在Sprite初始化时被添加到了_quad属性中。 具体看一下下面的这个属性的数据结构。 
     
    1.2 详细:
     
    1> 初始化Sprite 
     
    做了一堆的初始化工作这里我们注意一下_quad. 这个对象记录了当前的Sprite, 矩形的坐标,及纹理相关的信息。我们之后的绘制就需要这个对象。 
    void Sprite::setTextureRect(const Rect& rect, bool rotated, const Size& untrimmedSize)

    1. _quad

    V3F_C4B_T2F_Quad _quad;
    V3F_C4B_T2F_Quad, 这个数据结构记录了我们的左上,左下,右上, 右下,这个矩形的4个顶点信息, 每个顶点是由一个V3F_C4B_T2F结构组成的, 可以看到,其中有坐标, 颜色, 纹理的数据存储。
     
    //! 4 Vertex3FTex2FColor4B
    struct CC_DLL V3F_C4B_T2F_Quad
    {
        //! top left
        V3F_C4B_T2F    tl;
        //! bottom left
        V3F_C4B_T2F    bl;
        //! top right
        V3F_C4B_T2F    tr;
        //! bottom right
        V3F_C4B_T2F    br;
    };
     
    //! a Vec2 with a vertex point, a tex coord point and a color 4B
    struct CC_DLL V3F_C4B_T2F
    {
        //! vertices (3F)
        Vec3    vertices;            // 12 bytes
    
        //! colors (4B)
        Color4B      colors;              // 4 bytes
    
        // tex coords (2F)
        Tex2F        texCoords;           // 8 bytes
    };

     
    2. 纹理相关的初始化 setTextureCoords()
     
         _quad.bl.texCoords.u = left;
            _quad.bl.texCoords.v = bottom;
            _quad.br.texCoords.u = right;
            _quad.br.texCoords.v = bottom;
            _quad.tl.texCoords.u = left;
            _quad.tl.texCoords.v = top;
            _quad.tr.texCoords.u = right;
            _quad.tr.texCoords.v = top;
    3.  坐标相关初始化
            
      // Atlas: Vertex
            float x1 = 0 + _offsetPosition.x;
            float y1 = 0 + _offsetPosition.y;
            float x2 = x1 + _rect.size.width;
            float y2 = y1 + _rect.size.height;
    
            // Don't update Z.
            _quad.bl.vertices = Vec3(x1, y1, 0);
            _quad.br.vertices = Vec3(x2, y1, 0);
            _quad.tl.vertices = Vec3(x1, y2, 0);
            _quad.tr.vertices = Vec3(x2, y2, 0);

      

    2 调用Sprite draw() 
     
    还记得之前一篇的主循环吗?
     
    Director 每帧都调用drawScene(), drawScene()中会调用当前Scene->render() , Scene->render() 又会递归遍历其子元素,分别调用子元素的visit() 或 draw() , 当有子元素时, 用visit() , 没有子元素时直接调用其draw. 我们这里的Sprite被调用了, 这里调用的是draw(). 
     
    这个函数, 主要做了一件事,初始化一个矩形绘制命令, 把当前绘制命令发送到当前的系统 Render 的绘制队列中。
     
    void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
    {
        // Don't do calculate the culling if the transform was not updated
        _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
    
        if(_insideBounds)
        {
            _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
            renderer->addCommand(&_quadCommand);
    }
     
    3 Render:render() 
     
    Render内部保存了一个命令的绘制队列,cocos2dx的所有绘制都会被封装成为命令发送到当前的绘制队列中。
     
    当整个的子节点遍历后, 即所有的绘制命令加入到绘制队列上时, 这里调用 render->render()
    这个函数的作用是绘制当前绘制队列中的绘制信息, 最终是调用OpenGL的命令完成了,绘制。 
    下面看看它是怎么做的。 
     
     
    3.1 排序, 对绘制命令进行排序
     
    这里默认使用的遍历顺序排序. 当然也提供了可以改变这个顺序的方法,  这里不展开。 
     
    3.2 遍历当前的绘制队列
     
    命令的类型:
    TRIANGLES_COMMAND
    QUAD_COMMAND
    GROUP_COMMAND
    CUSTOM_COMMAND
    BATCH_COMMAND
    PRIMITIVE_COMMAND
    MESH_COMMAND
     
    1》根据不同的命令类型来对命令进行处理, 这里我们只关注,QUAD_COMMAND, 之前我们的Sprite就发送的这种类型的绘制命令。
     
      
    else if ( RenderCommand::Type::QUAD_COMMAND == commandType )
            {
                flush3D();
                if(_filledIndex > 0)
                {
                    drawBatchedTriangles();
                    _lastMaterialID = 0;
                }
                auto cmd = static_cast<QuadCommand*>(command);
                //Batch quads
                if( (_numberQuads + cmd->getQuadCount()) * 4 > VBO_SIZE )
                {
                    CCASSERT(cmd->getQuadCount()>= 0 && cmd->getQuadCount() * 4 < VBO_SIZE, "VBO for vertex is not big enough, please break the data down or use customized render command");
                    //Draw batched quads if VBO is full
                    drawBatchedQuads();
                }
     
                _batchQuadCommands.push_back(cmd);
               
                fillQuads(cmd);
            }
     
    有2个关键点,
     
    1 把绘制命令加到到批绘制列表中。_batchQuadCommands.push_back(cmd)
     
    2 处理当前的命令中的顶点数据, fillQuads(cmd) 

    void Renderer::fillQuads(const QuadCommand *cmd)
    {
        memcpy(_quadVerts + _numberQuads * 4, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());
        
        const Mat4& modelView = cmd->getModelView();
        
        for(ssize_t i=0; i< cmd->getQuadCount() * 4; ++i)
        {
            V3F_C4B_T2F *q = &_quadVerts[i + _numberQuads * 4];
            Vec3 *vec1 = (Vec3*)&q->vertices;
            modelView.transformPoint(vec1);
        }
        
        _numberQuads += cmd->getQuadCount();
    }
     
    1》首先, Render在初始化时, 初始化了一个的很大的顶点信息的缓存区, 我们的QuadCommand中保存的节点信息, 会先被复制到这个缓存区中。 
        
    memcpy(_quadVerts + _numberQuads * 4, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());
    2》对当前的顶点坐标信息, 做顶点坐标变化, 也就是模型坐标到世界坐标的变换
    for(ssize_t i=0; i< cmd->getQuadCount() * 4; ++i)
        {
            V3F_C4B_T2F *q = &_quadVerts[i + _numberQuads * 4];
            Vec3 *vec1 = (Vec3*)&q->vertices;
            modelView.transformPoint(vec1);
        }
    3 记录当前Render中有多少个这种矩形信息。 
        
    _numberQuads += cmd->getQuadCount();
     
     
    3.3 绘制
     
    flush() 关于这块的OpenGL相关的函数,(VAO, VBO,glDrawBuffer) 参考下面的附录:
     
    1 绑定VAO, 这里的VAO在 setupVBOAndVAO里已经初始化过了。初始化时,对顶点的索引值进行了初始化。 
          
      GL::bindVAO(_quadVAO);
    2 设置VBO数据 
            
    //Set VBO data
            glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);
            // option 3: orphaning + glMapBuffer
            glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * _numberQuads * 4, nullptr, GL_DYNAMIC_DRAW);
            void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
            memcpy(buf, _quadVerts, sizeof(_quadVerts[0])* _numberQuads * 4);
            glUnmapBuffer(GL_ARRAY_BUFFER);
           
    3 绑定顶点索引数据 , 这里的索引数据, 详细看 setupVBOAndVAO()
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
     
    4 遍历之前的批绘制列表,(这个有一个优化,就是当相邻的矩形的材质相同时,统一在一个绘制命令中绘制)
       
     for(const auto& cmd : _batchQuadCommands)
    5 绘制
     
     glDrawElements(GL_TRIANGLES, (GLsizei) indexToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (startIndex*sizeof(_indices[0])) );
     
     
    源码:
     
    void Renderer::drawBatchedQuads()
    {
        //TODO: we can improve the draw performance by insert material switching command before hand.
        
        int indexToDraw = 0;
        int startIndex = 0;
        
        //Upload buffer to VBO
        if(_numberQuads <= 0 || _batchQuadCommands.empty())
        {
            return;
        }
        
        if (Configuration::getInstance()->supportsShareableVAO())
        {
            //Bind VAO
            GL::bindVAO(_quadVAO);
            //Set VBO data
            glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);
            // option 3: orphaning + glMapBuffer
            glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * _numberQuads * 4, nullptr, GL_DYNAMIC_DRAW);
            void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
            memcpy(buf, _quadVerts, sizeof(_quadVerts[0])* _numberQuads * 4);
            glUnmapBuffer(GL_ARRAY_BUFFER);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
        }
        else
        {
        }
        //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;
            }
            
            indexToDraw += cmd->getQuadCount() * 6;
        }
        
        //Draw any remaining quad
        if(indexToDraw > 0)
        {
            glDrawElements(GL_TRIANGLES, (GLsizei) indexToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (startIndex*sizeof(_indices[0])) );
            _drawnBatches++;
            _drawnVertices += indexToDraw;
        }
        
        if (Configuration::getInstance()->supportsShareableVAO())
        {
            //Unbind VAO
            GL::bindVAO(0);
        }
        else
        {
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        }
        
        _batchQuadCommands.clear();
        _numberQuads = 0;
    }
     
    参考:
    VAO&VBO 
    OpenGL ES 2.0 
    cocos2D-X源码分析之从cocos2D-X学习OpenGL(2)----QUAD_COMMAND
     
  • 相关阅读:
    奶酪真香
    规格说明书
    33
    111
    出题
    随笔 01
    我爱奶酪
    用户规格说明书
    第二次结对作业
    结对作业1
  • 原文地址:https://www.cnblogs.com/liangzhimy/p/4448126.html
Copyright © 2020-2023  润新知