Cocos2d-x 學習筆記(25) 渲染 繪製 Render
- 2019 年 10 月 23 日
- 筆記
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