点击查看【processon】
根据绘制需求的不同,cocos引擎将OpenGL绘制封装成了以下几种类型:

  • TrianglesCommand:绘制一个或多个三角形
    • QuadCommand
      • 绘制一个或多个四边形,比如绘制一张图片
  • PrimitiveCommand
    • 绘制图元。点(GL_POINTS)、线(GL_LINES)、三角形(GL_TRIANGLES)。
  • BatchCommand
    • 批量顶点绘制。
    • 比如从一张字符纹理,绘制一系列quad,组成一行字。
    • 比如SpriteBatchNode,绘制大量相同quad
  • CustomCommand
    • 集成我们自己写的OpenGL绘制指令到cocos的绘制队列中
  • GroupCommand
    • 打包一系列的RenderCommand。
    • 执行这个GroupCommand就是执行这一系列的RenderCommand

      一、RenderCommand

      数据结构

      ```cpp

class CC_DLL RenderCommand { public: enum class Type { // 有哪些类型的RenderCommand UNKNOWN_COMMAND, // 预留类型 QUAD_COMMAND, // 绘制矩形区域 CUSTOM_COMMAND, // 自定义绘制命令,如stencil function, depth functions BATCH_COMMAND, // 封装TextureAtlas GROUP_COMMAND, // 包含1个或多个其他命令,可嵌套,通过一个queueID指向一个renderQueue MESH_COMMAND, // used to draw 3D meshes PRIMITIVE_COMMAND, // 绘制几何图元,如点、线、三角形 TRIANGLES_COMMAND // 绘制三角区域 };

protected:

  1. Type _type; // 绘制指令类型
  2. float _globalOrder; // global Z order
  3. bool _isTransparent; // Transparent flag
  4. /**
  5. QuadCommand and TrianglesCommand could be auto batched
  6. if there material ID is the same, however, if
  7. a command is skip batching, it would be forced to draw
  8. in a separate function call, and break the batch.
  9. */
  10. bool _skipBatching; // true,则强制调用一个gl draw call,这会break the batch
  11. /** Is the command been rendered on 3D pass. */
  12. bool _is3D;
  13. /** Depth from the model view matrix.*/
  14. float _depth;

};

  1. <a name="7j54P"></a>
  2. # 二、TrianglesCommand
  3. 绘制三角形的指令,最常用到的绘制指令,比如绘制纹理,就是用的2个三角形构成一个的一个矩形区域。<br />封装的其实就是OpenGL通过顶点索引绘制三角形的指令glDrawElements:
  4. ```cpp
  5. glDrawElements(GL_TRIANGLES, // 根据顶点索引缓冲中的顶点索引来绘制三角形
  6. 0, // 顶点索引数量,按顺序自动构建三角形
  7. GL_UNSIGNED_INT, // 索引类型
  8. (GLvoid*)(0 * sizeof(GLuint))) // 第一个索引在顶点索引缓冲中的偏移量

这里可以查看OpenGL如何渲染一张矩形纹理的代码。

数据结构

  1. class TrianglesCommand : public RenderCommand
  2. {
  3. struct Triangles { // 顶点数据的封装
  4. V3F_C4B_T2F* verts; // 一个顶点数据的数据结构:
  5. // 1、顶点坐标:3 * sizeof(float)个字节大小
  6. // x: float
  7. // y: float
  8. // z: float
  9. // 2、颜色值:这是我们从应用程序代码传入给顶点着色器的顶点的颜色值,根据需要和纹理颜色值叠加
  10. // 4 * sizeof(GLubyte)个字节大小
  11. // r: GLubyte
  12. // g: GLubyte
  13. // b: GLubyte
  14. // a: GLubyte
  15. // 3、纹理坐标:顶点对应的纹理采样坐标,在片段着色器中要据此坐标进行纹理颜色值采样
  16. // 2 * sizeof(GLfloat)个字节大小
  17. // u: GLfloat,相当于x坐标
  18. // v: GLfloat,相当于y坐标
  19. unsigned short* indices; // 保存一些顶点的索引:通过这个索引就能在VBO管理的内存中找到对应的顶点数据
  20. int vertCount; // 顶点数量
  21. int indexCount; // 索引数量
  22. };
  23. protected:
  24. // 这里面设计了一个MaterialID的概念,
  25. // 相同MaterialID的绘制命令,以下数据都相同:
  26. // GLProgramState: 包含glProgram,以及它所用到的uniforms、attribs、samplers。
  27. // GLState: blend func
  28. // 假设两条连续执行的绘制指令的MaterialID相同,
  29. // 那么只需要一次gl draw call即可,减少了draw call,提高了渲染效率,这就是自动批处理(auto-batching)
  30. uint32_t _materialID; // materialID
  31. GLuint _textureID; // 使用到的纹理ID,glGenTextures生成的,注意和纹理单元区分(GL_TEXTUREi)
  32. GLProgramState* _glProgramState; // 上面解释
  33. BlendFunc _blendType; // 采用的缓和函数
  34. Triangles _triangles; // 顶点数据 + 顶点索引数据
  35. Mat4 _mv; // 模型视图矩阵,用于顶点坐标的变换,获得顶点的最终坐标
  36. GLuint _alphaTextureID; // ANDROID ETC1 ALPHA supports.
  37. };
  1. class TrianglesCommand : public RenderCommand
  2. {
  3. void init(float globalOrder, // globalZOrder,用于全局绘制排序
  4. GLuint textureID, // 使用到的纹理
  5. GLProgramState* glProgramState, // 保存了一个GLProgram极其用到的所有数据:
  6. // uniforms: 我们代码中传入给着色器的全局数据,该GLProgram的所有着色器都可以访问(CPU->GPU)
  7. // attributes: 传入顶点着色器的顶点数据的格式(顶点属性),就是顶点数据的结构。
  8. // textureUnits: 该GLProgram的片段着色器使用到的纹理单元
  9. BlendFunc blendType, // 混合的类型,混合的知识点见:https://www.yuque.com/tvvhealth/cs/kahsp8
  10. const Triangles& triangles, // 这条绘制命令需要用到的顶点数据
  11. const Mat4& mv, // 用于变换顶点坐标的模型视图矩阵
  12. uint32_t flags); // 用于区分3D还是2D
  13. ......; // init函数的其他重载版本
  14. ......; // 这里都是一些获取上面顶点数据的函数
  15. protected:
  16. void generateMaterialID() {
  17. // 哈希算法生成materialID,影响因子包括:
  18. // glProgramState:glProgram、uniforms、attributes
  19. // blendFunc:混合函数(src + dst)
  20. // textureID:片段着色器的采样纹理
  21. struct {
  22. void* glProgramState;
  23. GLuint textureId;
  24. GLenum blendSrc;
  25. GLenum blendDst;
  26. } hashMe;
  27. // NOTE: Initialize hashMe struct to make the value of padding bytes be filled with zero.
  28. // It's important since XXH32 below will also consider the padding bytes which probably
  29. // are set to random values by different compilers.
  30. memset(&hashMe, 0, sizeof(hashMe));
  31. hashMe.textureId = _textureID;
  32. hashMe.blendSrc = _blendType.src;
  33. hashMe.blendDst = _blendType.dst;
  34. hashMe.glProgramState = _glProgramState;
  35. _materialID = XXH32((const void*)&hashMe, sizeof(hashMe), 0);
  36. }
  37. };

QuadCommand

绘制矩形的指令。绘制矩形其实就是绘制两个三角形,因此本质上它是一个TrianglesCommand(它的继承类)。

  1. class CC_DLL QuadCommand : public TrianglesCommand
  2. {
  3. public:
  4. void init( float globalOrder, // 同上TrianglesCommand的参数解释
  5. GLuint textureID, // 同上TrianglesCommand的参数解释
  6. GLProgramState* glProgramState, // 同上TrianglesCommand的参数解释
  7. const BlendFunc& blendType, // 同上TrianglesCommand的参数解释
  8. V3F_C4B_T2F_Quad* quads, // 一个矩形的数据结构,4个顶点数据构成
  9. // {
  10. // V3F_C4B_T2F tl; // 左顶
  11. // V3F_C4B_T2F bl; // 左底
  12. // V3F_C4B_T2F tr; // 右顶
  13. // V3F_C4B_T2F br; // 右底
  14. // };
  15. ssize_t quadCount, // 同上TrianglesCommand的参数解释
  16. const Mat4& mv, // 同上TrianglesCommand的参数解释
  17. uint32_t flags); // 同上TrianglesCommand的参数解释
  18. ......; // init的重载版本
  19. protected:
  20. // 设一个矩形的结构如下,数字为顶点对应的索引
  21. // 0------2
  22. // | / |
  23. // | / |
  24. // |/ |
  25. // 1------3
  26. // 则绘制过程过程为:
  27. // 三角形1:0 -> 1 -> 2,逆时针
  28. // 三角形2:3 -> 2 -> 1,逆时针,统一为顺/逆时针方向,方便后续可能的glCullFace
  29. //
  30. // 假设这条指令要绘制3个矩形,如下;
  31. // 0------2 4------6 8------10
  32. // | / | | / | | / |
  33. // | / | | / | | / |
  34. // |/ | |/ | |/ |
  35. // 1------3 5------7 9------11
  36. // 绘制过程为:
  37. // 矩形1:
  38. // 三角形1:0 -> 1 -> 2
  39. // 三角形2:3 -> 2 -> 1
  40. // 矩形2:
  41. // 三角形1:4 -> 5 -> 6
  42. // 三角形2:7 -> 6 -> 5
  43. // 矩形3:
  44. // 三角形1:8 -> 9 -> 10
  45. // 三角形2:11 -> 10 -> 9
  46. // 根据上面的解释,我们计算出绘制n个矩形的顶点索引顺序,
  47. // 而且不同的QuadCommand绘制n个矩形时的顶点索引是完全一样的。
  48. // 因此我们可以提前创建一个索引池,支持最大连续绘制max个矩形。这样可以减少内存分配频率。
  49. // 现在需要使用indices个顶点索引,函数将分配合适大小的索引量足以满足当前需求,
  50. // 以及大概率满足后续需求
  51. void reIndex(int indices);
  52. ......;
  53. };

数据结构

  1. class CC_DLL QuadCommand : public TrianglesCommand
  2. {
  3. protected:
  4. int _indexSize; // 本条指令使用到的顶点索引数量
  5. std::vector<GLushort*> _ownedIndices; // 由本指令负责释放的索引数组。
  6. // shared across all instances
  7. static int __indexCapacity; // 索引最大数量
  8. static GLushort* __indices; // 当前正在共用的索引数组
  9. };

重要函数

  1. class CC_DLL QuadCommand : public TrianglesCommand
  2. {
  3. public:
  4. void init( float globalOrder, // 同上TrianglesCommand的参数解释
  5. GLuint textureID, // 同上TrianglesCommand的参数解释
  6. GLProgramState* glProgramState, // 同上TrianglesCommand的参数解释
  7. const BlendFunc& blendType, // 同上TrianglesCommand的参数解释
  8. V3F_C4B_T2F_Quad* quads, // 一个矩形的数据结构,4个顶点数据构成
  9. // {
  10. // V3F_C4B_T2F tl; // 左顶
  11. // V3F_C4B_T2F bl; // 左底
  12. // V3F_C4B_T2F tr; // 右顶
  13. // V3F_C4B_T2F br; // 右底
  14. // };
  15. ssize_t quadCount, // 同上TrianglesCommand的参数解释
  16. const Mat4& mv, // 同上TrianglesCommand的参数解释
  17. uint32_t flags); // 同上TrianglesCommand的参数解释
  18. ......; // init的重载版本
  19. protected:
  20. // 设一个矩形的结构如下,数字为顶点对应的索引
  21. // 0------2
  22. // | / |
  23. // | / |
  24. // |/ |
  25. // 1------3
  26. // 则绘制过程过程为:
  27. // 三角形1:0 -> 1 -> 2,逆时针
  28. // 三角形2:3 -> 2 -> 1,逆时针,统一为顺/逆时针方向,方便后续可能的glCullFace
  29. //
  30. // 假设这条指令要绘制3个矩形,如下;
  31. // 0------2 4------6 8------10
  32. // | / | | / | | / |
  33. // | / | | / | | / |
  34. // |/ | |/ | |/ |
  35. // 1------3 5------7 9------11
  36. // 绘制过程为:
  37. // 矩形1:
  38. // 三角形1:0 -> 1 -> 2
  39. // 三角形2:3 -> 2 -> 1
  40. // 矩形2:
  41. // 三角形1:4 -> 5 -> 6
  42. // 三角形2:7 -> 6 -> 5
  43. // 矩形3:
  44. // 三角形1:8 -> 9 -> 10
  45. // 三角形2:11 -> 10 -> 9
  46. // 根据上面的解释,我们计算出绘制n个矩形的顶点索引顺序,
  47. // 而且不同的QuadCommand绘制n个矩形时的顶点索引是完全一样的。
  48. // 因此我们可以提前创建一个索引池,支持最大连续绘制max个矩形。这样可以减少内存分配频率。
  49. // 现在需要使用indices个顶点索引,函数将分配合适大小的索引量足以满足当前需求,
  50. // 以及大概率满足后续需求
  51. void reIndex(int indices);
  52. ......;
  53. };

四、GroupCommand

GroupCommand指向一个RenderQueue,执行GroupCommand就是遍历RenderQueue中的RenderCommand。
init一个GroupCommand时,就已经在RenderQueue的stack栈顶压栈了一个新的RenderQueue,因此后续的RenderCommand都会进入这个新的RenderQueue。

  1. class GroupCommand : public RenderCommand
  2. {
  3. public:
  4. GroupCommand() {
  5. _type = RenderCommand::Type::GROUP_COMMAND;
  6. // 通过_renderQueueID指向绘制栈中闲置的RenderQueue
  7. _renderQueueID = Director::getInstance()->getRenderer()->getGroupCommandManager()->getGroupID();
  8. }
  9. ~GroupCommand(){
  10. // 回收RenderQueue,重新变为限制的RenderQueue
  11. Director::getInstance()->getRenderer()->getGroupCommandManager()->releaseGroupID(_renderQueueID);
  12. }
  13. void init(float globalOrder); // 重新初始化,重新分配一个闲置的RenderQueue
  14. {
  15. _globalOrder = globalOrder;
  16. auto manager = Director::getInstance()->getRenderer()->getGroupCommandManager();
  17. manager->releaseGroupID(_renderQueueID);
  18. _renderQueueID = manager->getGroupID();
  19. }
  20. int _renderQueueID; // 当前指向的RenderQueue
  21. };

五、CustomCommand

封装我们自己编写的OpenGL绘制指令。

  1. class CC_DLL CustomCommand : public RenderCommand
  2. {
  3. void init( float globalZOrder, // 决定了这条命令的绘制队列中的执行位次。
  4. const Mat4& modelViewTransform, // 以下两个参数,3D中才用到
  5. uint32_t flags);
  6. void init(float globalZOrder); // 2D中用这个就行了。
  7. void execute(); // 执行的func
  8. std::function<void()> func; // fuc里面写OpenGL绘制指令
  9. ......;
  10. };

六、BatchCommand

TextureAtlas的封装执行,为TextureAtlas的绘制准备好了GLProgramState,包括:

  • GLProgram:包含了编译链接好的顶点着色器、片段着色器等。
  • 纹理(集):一般是一张合成纹理(由多个小纹理组成,由外部工具预生成),本质就是一个普通2D纹理。
  • 混合函数(BlendFunc)

TextureAtlas的需求场景是,绘制大量的矩形区域,这些矩形区域都是从同一个纹理采样。比如从一张字符纹理中绘制多个子纹理组成文字,还有比如绘制大量的一样的草。
设计这个的好处是为了batching(批处理绘制),就是说将绘制的顶点数据集中起来,然后调用一次glDrawElements或glDrawArrays来完成所有的绘制,减少draw call。

  1. class CC_DLL BatchCommand : public RenderCommand
  2. {
  3. // 参数解释同上
  4. void init( float globalZOrder,
  5. GLProgram* shader,
  6. BlendFunc blendType,
  7. TextureAtlas* textureAtlas, // 可以看成是纹理和quad顶点数据的集合
  8. const Mat4& modelViewTransform,
  9. uint32_t flags);
  10. // 绘制大量的quad,
  11. void execute()
  12. {
  13. // Set material
  14. _shader->use();
  15. _shader->setUniformsForBuiltins(_mv);
  16. GL::bindTexture2D(_textureID);
  17. GL::blendFunc(_blendType.src, _blendType.dst);
  18. // Draw
  19. _textureAtlas->drawQuads();
  20. }
  21. ......; // 和上面的Command一样的成员
  22. };

七、PrimitiveCommand

绘制图元的指令。

  1. class PrimitiveCommand : public RenderCommand
  2. {
  3. void init( float globalOrder, // 同上TrianglesCommand的参数解释
  4. GLuint textureID, // 同上TrianglesCommand的参数解释
  5. GLProgramState* glProgramState, // 同上TrianglesCommand的参数解释
  6. BlendFunc blendType, // 同上TrianglesCommand的参数解释
  7. Primitive* primitive, // 图元数据,一个图元的数据结构如下:
  8. // VertexData* _verts; // 顶点数据
  9. // IndexBuffer* _indices; // 顶点索引(可选)
  10. // int _start; // 绘制的起始顶点索引
  11. // int _count; // 顶点数据数量
  12. // int _type; // 图元类型
  13. // GL_POINTS // 点
  14. // GL_LINES // 线段
  15. // GL_TRIANGLES // 三角形
  16. const Mat4& mv, // 同上TrianglesCommand的参数解释
  17. uint32_t flags); // 同上TrianglesCommand的参数解释
  18. ......; // 获取成员变量的函数
  19. void execute() const; // 画点:glDrawArrays(GL_POINTS, startIndex, count);
  20. // 画线:glDrawArrays(GL_LINES, startIndex, count);
  21. // 画三角形:glDrawArrays(GL_TRIANGLES, startIndex, count);
  22. ......; // init的参数 作为成员变量
  23. };