‎Cocos2d-x 學習筆記(25) 渲染 繪製 Render

  • 2019 年 10 月 23 日
  • 筆記

【Cocos2d-x】學習筆記目錄

本文鏈接:https://www.cnblogs.com/deepcho/p/cocos2dx-render.html

1. 從程式入口到渲染方法

一個Cocos2d-x項目流程中,在每一幀進行一次渲染,渲染的時機是在調度器update方法執行之後。所渲染的是當前的場景_runningScene,當前場景執行Scene::render()方法進行渲染。

在場景的渲染方法Scene::render()中,對UI樹進行中序遍歷,遍歷到的元素執行其draw方法。對於Sprite,其draw方法是主要語句:

        _trianglesCommand.init(_globalZOrder,                                 _texture,                                 getGLProgramState(),                                 _blendFunc,                                 _polyInfo.triangles,                                 transform,                                 flags);            renderer->addCommand(&_trianglesCommand);

Sprite有成員變數TrianglesCommand _trianglesCommand,這是該Sprite的渲染命令。渲染命令在初始化後被添加到對應的渲染隊列中。最後執行Renderer::render()方法。

從程式入口到Renderer::render()方法與渲染相關流程如下:

 

 

2. Renderer::render()

Node的draw方法只是將該Node的渲染命令加入到隊列中,渲染的執行由Renderer::render()方法進行。

Renderer類有兩個重要成員變數,也是兩個容器:

std::stack<int> _commandGroupStack;  std::vector<RenderQueue> _renderGroups;

_commandGroupStack是存儲ID的棧。

_renderGroups是存儲RenderQueue的容器,RenderQueue類實質是一個存儲了5種渲染命令的容器:

std::vector<RenderCommand*> _commands[QUEUE_COUNT];

該容器是渲染命令的隊列,隊列里的命令分為5類分別存儲,每類代表不同的含義:

    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,      };

Renderer::render()方法的執行需要渲染命令中的一些數據,對於Sprite,它的渲染命令是TrianglesCommand。

RenderCommand是TrianglesCommand的父類。RenderCommand有成員變數枚舉Type,其定義如下:

    enum class Type      {          /** Reserved type.*/          UNKNOWN_COMMAND,          /** Quad command, used for draw quad.*/          QUAD_COMMAND,          /**Custom command, used for calling callback for rendering.*/          CUSTOM_COMMAND,          /**Batch command, used for draw batches in texture atlas.*/          BATCH_COMMAND,          /**Group command, which can group command in a tree hierarchy.*/          GROUP_COMMAND,          /**Mesh command, used to draw 3D meshes.*/          MESH_COMMAND,          /**Primitive command, used to draw primitives such as lines, points and triangles.*/          PRIMITIVE_COMMAND,          /**Triangles command, used to draw triangles.*/          TRIANGLES_COMMAND      };

TrianglesCommand的Type值是TRIANGLES_COMMAND。

在Sprite的draw方法中,TrianglesCommand類型的渲染命令通過init方法初始化。

該init方法首先調用父類RenderCommand的init方法設置3個變數:globalOrder, mv, flags。

之後將參數triangles賦給成員_triangles,這是一個結構體,其組成如下:

    struct Triangles      {          /**Vertex data pointer.*/          V3F_C4B_T2F* verts;          /**Index data pointer.*/          unsigned short* indices;          /**The number of vertices.*/          int vertCount;          /**The number of indices.*/          int indexCount;      };

包括所有頂點數據容器、索引數據容器、頂點個數、索引個數。頂點數據使用V3F_C4B_T2F結構體存儲。

接下來設置矩陣_mv、_textureID 、_blendType、_glProgramState。這裡的_textureID是由參數紋理執行getName方法得到的。

init方法最後執行generateMaterialID()方法生成材質ID。該方法是通過四個變數_textureID、_blendType.src、_blendType.dst、_glProgramState,計算哈希值作為材質ID(變數_materialID)。也就是說,當兩個渲染命令的四個變數完全一致時,兩個渲染命令(兩個Sprite)的材質才算是相同的。

Renderer::render()內的執行流程大致如下:

Renderer::render()方法對_renderGroups里的每個渲染隊列執行sort方法排序,對每個隊列中的TRANSPARENT_3D類型的渲染命令按Depth從小到大進行排序,對GZOrder小於0的渲染命令、GZOrder大於0的渲染命令按ZOrder從小到大進行排序。此時沒有對GZOrder等於0的渲染命令排序,因為這些渲染命令的添加是按照所屬的Node的LocalZOrder順序添加的,即已經排好序,無需再次排序。

排序後執行visitRenderQueue(_renderGroups[0]),該方法是按隊列里命令分類的順序,依次對每個分類的每個命令執行processRenderCommand方法。

processRenderCommand方法里會對參數命令的Type進行判斷。對於Sprite的TrianglesCommand命令,當VBO的buffer已滿時,會觸發drawBatchedTriangles方法;當沒滿時,命令會存入到Renderer容器vector<TrianglesCommand*> _queuedTriangleCommands中。

剛才講到visitRenderQueue()方法對隊列里的每個命令執行processRenderCommand()方法,主要是遍歷隊列內的每個分類,把命令加到容器中。在當前分類的命令都被遍歷之後,執行flush()方法,該方法主要是調用了drawBatchedTriangles()方法。

drawBatchedTriangles()方法對存儲命令的容器進行遍歷,對每個命令的操作可分為四個步驟:

 

 

裝載

每個命令執行fillVerticesAndIndices方法,填充Renderer的頂點容器_verts和索引容器_indices,具體做法是:將命令的頂點坐標轉為該頂點的世界坐標,再存入到_verts中,再將命令的索引存入到_indices中,最後修改(增加)_filledVertex和_filledIndex的值。

接下來,判斷批量渲染的條件是否成立,主要是比較當前命令材質ID和上個命令材質ID。如果可以進行批量繪製,把當前命令的資訊加入到容器數組_triBatchesToDraw[]中,下標為上次操作的容器下標。該容器大致介紹如下:

TriBatchToDraw* _triBatchesToDraw;    // Internal structure that has the information for the batches  struct TriBatchToDraw {      TrianglesCommand* cmd;  // needed for the Material      GLsizei indicesToDraw;      GLsizei offset;  };

如果該命令不能進行批量繪製,則讓容器數組下標加1,新容器存儲當前命令的資訊。

頂點和索引複製到GL快取

很簡單。_verts和_indices內的數據被複制到GL對象的快取里。

繪製

繪製的參數是裝載步驟時的TriBatchToDraw內的資訊。每個TriBatchToDraw進行一次繪製,也導致了每次繪製時DrawCall的值加1。

清理

很簡單,就不介紹了。

以上就是渲染的全流程解析。可以總結出,渲染是在每幀結束前進行的;渲染之前是把每幀的所有元素的繪製用命令統一進行存儲,在渲染時讀取這些命令,進行繪製;渲染時還會進行批量繪製的判斷,這能有效降低DrawCall值。

有關降低DrawCall值的學習在這篇文章里:Cocos2d-x 學習筆記(26) 從源碼學習 DrawCall 的降低方法


本文鏈接:https://www.cnblogs.com/deepcho/p/cocos2dx-render.html