点击查看【processon】
根据绘制需求的不同,cocos引擎将OpenGL绘制封装成了以下几种类型:
- TrianglesCommand:绘制一个或多个三角形
- QuadCommand
- 绘制一个或多个四边形,比如绘制一张图片
- QuadCommand
- PrimitiveCommand
- 绘制图元。点(GL_POINTS)、线(GL_LINES)、三角形(GL_TRIANGLES)。
- BatchCommand
- 批量顶点绘制。
- 比如从一张字符纹理,绘制一系列quad,组成一行字。
- 比如SpriteBatchNode,绘制大量相同quad
- CustomCommand
- 集成我们自己写的OpenGL绘制指令到cocos的绘制队列中
- GroupCommand
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:
Type _type; // 绘制指令类型float _globalOrder; // global Z orderbool _isTransparent; // Transparent flag/**QuadCommand and TrianglesCommand could be auto batchedif there material ID is the same, however, ifa command is skip batching, it would be forced to drawin a separate function call, and break the batch.*/bool _skipBatching; // true,则强制调用一个gl draw call,这会break the batch/** Is the command been rendered on 3D pass. */bool _is3D;/** Depth from the model view matrix.*/float _depth;
};
<a name="7j54P"></a># 二、TrianglesCommand绘制三角形的指令,最常用到的绘制指令,比如绘制纹理,就是用的2个三角形构成一个的一个矩形区域。<br />封装的其实就是OpenGL通过顶点索引绘制三角形的指令glDrawElements:```cppglDrawElements(GL_TRIANGLES, // 根据顶点索引缓冲中的顶点索引来绘制三角形0, // 顶点索引数量,按顺序自动构建三角形GL_UNSIGNED_INT, // 索引类型(GLvoid*)(0 * sizeof(GLuint))) // 第一个索引在顶点索引缓冲中的偏移量
这里可以查看OpenGL如何渲染一张矩形纹理的代码。
数据结构
class TrianglesCommand : public RenderCommand{struct Triangles { // 顶点数据的封装V3F_C4B_T2F* verts; // 一个顶点数据的数据结构:// 1、顶点坐标:3 * sizeof(float)个字节大小// x: float// y: float// z: float// 2、颜色值:这是我们从应用程序代码传入给顶点着色器的顶点的颜色值,根据需要和纹理颜色值叠加// 4 * sizeof(GLubyte)个字节大小// r: GLubyte// g: GLubyte// b: GLubyte// a: GLubyte// 3、纹理坐标:顶点对应的纹理采样坐标,在片段着色器中要据此坐标进行纹理颜色值采样// 2 * sizeof(GLfloat)个字节大小// u: GLfloat,相当于x坐标// v: GLfloat,相当于y坐标unsigned short* indices; // 保存一些顶点的索引:通过这个索引就能在VBO管理的内存中找到对应的顶点数据int vertCount; // 顶点数量int indexCount; // 索引数量};protected:// 这里面设计了一个MaterialID的概念,// 相同MaterialID的绘制命令,以下数据都相同:// GLProgramState: 包含glProgram,以及它所用到的uniforms、attribs、samplers。// GLState: blend func// 假设两条连续执行的绘制指令的MaterialID相同,// 那么只需要一次gl draw call即可,减少了draw call,提高了渲染效率,这就是自动批处理(auto-batching)uint32_t _materialID; // materialIDGLuint _textureID; // 使用到的纹理ID,glGenTextures生成的,注意和纹理单元区分(GL_TEXTUREi)GLProgramState* _glProgramState; // 上面解释BlendFunc _blendType; // 采用的缓和函数Triangles _triangles; // 顶点数据 + 顶点索引数据Mat4 _mv; // 模型视图矩阵,用于顶点坐标的变换,获得顶点的最终坐标GLuint _alphaTextureID; // ANDROID ETC1 ALPHA supports.};
class TrianglesCommand : public RenderCommand{void init(float globalOrder, // globalZOrder,用于全局绘制排序GLuint textureID, // 使用到的纹理GLProgramState* glProgramState, // 保存了一个GLProgram极其用到的所有数据:// uniforms: 我们代码中传入给着色器的全局数据,该GLProgram的所有着色器都可以访问(CPU->GPU)// attributes: 传入顶点着色器的顶点数据的格式(顶点属性),就是顶点数据的结构。// textureUnits: 该GLProgram的片段着色器使用到的纹理单元BlendFunc blendType, // 混合的类型,混合的知识点见:https://www.yuque.com/tvvhealth/cs/kahsp8const Triangles& triangles, // 这条绘制命令需要用到的顶点数据const Mat4& mv, // 用于变换顶点坐标的模型视图矩阵uint32_t flags); // 用于区分3D还是2D......; // init函数的其他重载版本......; // 这里都是一些获取上面顶点数据的函数protected:void generateMaterialID() {// 哈希算法生成materialID,影响因子包括:// glProgramState:glProgram、uniforms、attributes// blendFunc:混合函数(src + dst)// textureID:片段着色器的采样纹理struct {void* glProgramState;GLuint textureId;GLenum blendSrc;GLenum blendDst;} hashMe;// NOTE: Initialize hashMe struct to make the value of padding bytes be filled with zero.// It's important since XXH32 below will also consider the padding bytes which probably// are set to random values by different compilers.memset(&hashMe, 0, sizeof(hashMe));hashMe.textureId = _textureID;hashMe.blendSrc = _blendType.src;hashMe.blendDst = _blendType.dst;hashMe.glProgramState = _glProgramState;_materialID = XXH32((const void*)&hashMe, sizeof(hashMe), 0);}};
QuadCommand
绘制矩形的指令。绘制矩形其实就是绘制两个三角形,因此本质上它是一个TrianglesCommand(它的继承类)。
class CC_DLL QuadCommand : public TrianglesCommand{public:void init( float globalOrder, // 同上TrianglesCommand的参数解释GLuint textureID, // 同上TrianglesCommand的参数解释GLProgramState* glProgramState, // 同上TrianglesCommand的参数解释const BlendFunc& blendType, // 同上TrianglesCommand的参数解释V3F_C4B_T2F_Quad* quads, // 一个矩形的数据结构,4个顶点数据构成// {// V3F_C4B_T2F tl; // 左顶// V3F_C4B_T2F bl; // 左底// V3F_C4B_T2F tr; // 右顶// V3F_C4B_T2F br; // 右底// };ssize_t quadCount, // 同上TrianglesCommand的参数解释const Mat4& mv, // 同上TrianglesCommand的参数解释uint32_t flags); // 同上TrianglesCommand的参数解释......; // init的重载版本protected:// 设一个矩形的结构如下,数字为顶点对应的索引// 0------2// | / |// | / |// |/ |// 1------3// 则绘制过程过程为:// 三角形1:0 -> 1 -> 2,逆时针// 三角形2:3 -> 2 -> 1,逆时针,统一为顺/逆时针方向,方便后续可能的glCullFace//// 假设这条指令要绘制3个矩形,如下;// 0------2 4------6 8------10// | / | | / | | / |// | / | | / | | / |// |/ | |/ | |/ |// 1------3 5------7 9------11// 绘制过程为:// 矩形1:// 三角形1:0 -> 1 -> 2// 三角形2:3 -> 2 -> 1// 矩形2:// 三角形1:4 -> 5 -> 6// 三角形2:7 -> 6 -> 5// 矩形3:// 三角形1:8 -> 9 -> 10// 三角形2:11 -> 10 -> 9// 根据上面的解释,我们计算出绘制n个矩形的顶点索引顺序,// 而且不同的QuadCommand绘制n个矩形时的顶点索引是完全一样的。// 因此我们可以提前创建一个索引池,支持最大连续绘制max个矩形。这样可以减少内存分配频率。// 现在需要使用indices个顶点索引,函数将分配合适大小的索引量足以满足当前需求,// 以及大概率满足后续需求void reIndex(int indices);......;};
数据结构
class CC_DLL QuadCommand : public TrianglesCommand{protected:int _indexSize; // 本条指令使用到的顶点索引数量std::vector<GLushort*> _ownedIndices; // 由本指令负责释放的索引数组。// shared across all instancesstatic int __indexCapacity; // 索引最大数量static GLushort* __indices; // 当前正在共用的索引数组};
重要函数
class CC_DLL QuadCommand : public TrianglesCommand{public:void init( float globalOrder, // 同上TrianglesCommand的参数解释GLuint textureID, // 同上TrianglesCommand的参数解释GLProgramState* glProgramState, // 同上TrianglesCommand的参数解释const BlendFunc& blendType, // 同上TrianglesCommand的参数解释V3F_C4B_T2F_Quad* quads, // 一个矩形的数据结构,4个顶点数据构成// {// V3F_C4B_T2F tl; // 左顶// V3F_C4B_T2F bl; // 左底// V3F_C4B_T2F tr; // 右顶// V3F_C4B_T2F br; // 右底// };ssize_t quadCount, // 同上TrianglesCommand的参数解释const Mat4& mv, // 同上TrianglesCommand的参数解释uint32_t flags); // 同上TrianglesCommand的参数解释......; // init的重载版本protected:// 设一个矩形的结构如下,数字为顶点对应的索引// 0------2// | / |// | / |// |/ |// 1------3// 则绘制过程过程为:// 三角形1:0 -> 1 -> 2,逆时针// 三角形2:3 -> 2 -> 1,逆时针,统一为顺/逆时针方向,方便后续可能的glCullFace//// 假设这条指令要绘制3个矩形,如下;// 0------2 4------6 8------10// | / | | / | | / |// | / | | / | | / |// |/ | |/ | |/ |// 1------3 5------7 9------11// 绘制过程为:// 矩形1:// 三角形1:0 -> 1 -> 2// 三角形2:3 -> 2 -> 1// 矩形2:// 三角形1:4 -> 5 -> 6// 三角形2:7 -> 6 -> 5// 矩形3:// 三角形1:8 -> 9 -> 10// 三角形2:11 -> 10 -> 9// 根据上面的解释,我们计算出绘制n个矩形的顶点索引顺序,// 而且不同的QuadCommand绘制n个矩形时的顶点索引是完全一样的。// 因此我们可以提前创建一个索引池,支持最大连续绘制max个矩形。这样可以减少内存分配频率。// 现在需要使用indices个顶点索引,函数将分配合适大小的索引量足以满足当前需求,// 以及大概率满足后续需求void reIndex(int indices);......;};
四、GroupCommand
GroupCommand指向一个RenderQueue,执行GroupCommand就是遍历RenderQueue中的RenderCommand。
init一个GroupCommand时,就已经在RenderQueue的stack栈顶压栈了一个新的RenderQueue,因此后续的RenderCommand都会进入这个新的RenderQueue。
class GroupCommand : public RenderCommand{public:GroupCommand() {_type = RenderCommand::Type::GROUP_COMMAND;// 通过_renderQueueID指向绘制栈中闲置的RenderQueue_renderQueueID = Director::getInstance()->getRenderer()->getGroupCommandManager()->getGroupID();}~GroupCommand(){// 回收RenderQueue,重新变为限制的RenderQueueDirector::getInstance()->getRenderer()->getGroupCommandManager()->releaseGroupID(_renderQueueID);}void init(float globalOrder); // 重新初始化,重新分配一个闲置的RenderQueue{_globalOrder = globalOrder;auto manager = Director::getInstance()->getRenderer()->getGroupCommandManager();manager->releaseGroupID(_renderQueueID);_renderQueueID = manager->getGroupID();}int _renderQueueID; // 当前指向的RenderQueue};
五、CustomCommand
封装我们自己编写的OpenGL绘制指令。
class CC_DLL CustomCommand : public RenderCommand{void init( float globalZOrder, // 决定了这条命令的绘制队列中的执行位次。const Mat4& modelViewTransform, // 以下两个参数,3D中才用到uint32_t flags);void init(float globalZOrder); // 2D中用这个就行了。void execute(); // 执行的funcstd::function<void()> func; // fuc里面写OpenGL绘制指令......;};
六、BatchCommand
TextureAtlas的封装执行,为TextureAtlas的绘制准备好了GLProgramState,包括:
- GLProgram:包含了编译链接好的顶点着色器、片段着色器等。
- 纹理(集):一般是一张合成纹理(由多个小纹理组成,由外部工具预生成),本质就是一个普通2D纹理。
- 混合函数(BlendFunc)
TextureAtlas的需求场景是,绘制大量的矩形区域,这些矩形区域都是从同一个纹理采样。比如从一张字符纹理中绘制多个子纹理组成文字,还有比如绘制大量的一样的草。
设计这个的好处是为了batching(批处理绘制),就是说将绘制的顶点数据集中起来,然后调用一次glDrawElements或glDrawArrays来完成所有的绘制,减少draw call。
class CC_DLL BatchCommand : public RenderCommand{// 参数解释同上void init( float globalZOrder,GLProgram* shader,BlendFunc blendType,TextureAtlas* textureAtlas, // 可以看成是纹理和quad顶点数据的集合const Mat4& modelViewTransform,uint32_t flags);// 绘制大量的quad,void execute(){// Set material_shader->use();_shader->setUniformsForBuiltins(_mv);GL::bindTexture2D(_textureID);GL::blendFunc(_blendType.src, _blendType.dst);// Draw_textureAtlas->drawQuads();}......; // 和上面的Command一样的成员};
七、PrimitiveCommand
绘制图元的指令。
class PrimitiveCommand : public RenderCommand{void init( float globalOrder, // 同上TrianglesCommand的参数解释GLuint textureID, // 同上TrianglesCommand的参数解释GLProgramState* glProgramState, // 同上TrianglesCommand的参数解释BlendFunc blendType, // 同上TrianglesCommand的参数解释Primitive* primitive, // 图元数据,一个图元的数据结构如下:// VertexData* _verts; // 顶点数据// IndexBuffer* _indices; // 顶点索引(可选)// int _start; // 绘制的起始顶点索引// int _count; // 顶点数据数量// int _type; // 图元类型// GL_POINTS // 点// GL_LINES // 线段// GL_TRIANGLES // 三角形const Mat4& mv, // 同上TrianglesCommand的参数解释uint32_t flags); // 同上TrianglesCommand的参数解释......; // 获取成员变量的函数void execute() const; // 画点:glDrawArrays(GL_POINTS, startIndex, count);// 画线:glDrawArrays(GL_LINES, startIndex, count);// 画三角形:glDrawArrays(GL_TRIANGLES, startIndex, count);......; // init的参数 作为成员变量};
