我所理解的cocos2dx - 全新的绘制系统
新绘制系统的特点
老系统的绘制都在node的draw方法里实现,这里的问题就是绘制的顺序受到节点遍历顺序的影响,不灵活,因为要遍历到节点才会执行它的draw。
新绘制系统在初设计上设定了4个要求:
1.绘制逻辑从主循环分离。改版前多种不同类型ui拥有相同的绘制方式,他们需要职责更清晰
2.采用应用程序级别的视口裁剪。在视窗外的ui元素不会发送绘制命令
3.采用自动批绘制技术。也就是使用同一个纹理,同一着色器程序等的元素,只调用一次绘制命令(子弹等)
4.更简单的实现绘制的自定义
绘制系统概览
新的绘制系统分三步:生成绘制命令,绘制命令的排序,执行绘制命令
生成绘制命令:通过ui树遍历元素生成绘制命令,实现绘制和ui元素分离。RenderCommand表示一个绘制命令,定义如何绘制ui。在draw方法执行的时候,他们会被加入到renderer下的command栈。
绘制命令的排序:当ui树遍历完成后,开始对栈里的命令进行排序。根据的是先globalZOrder排序,然后再按遍历顺序排,就能调整元素的绘制顺序
执行绘制命令:对使用QuadCommand的,如果2个相邻的QuadCommand且使用相同的纹理和着色器等,就组合在一起,称为自动批绘制,减少绘制次数。
RenderCommand
RenderCommand是一种特定绘制方式的封装。每个命令结束后都会还原opengl es的一些状态设置。一个元素可能使用1个或者多个命令绘制场景。
每个命令有globalZOrder属性,排序使用。Type属性,绘制命令的类型,大致有4种:
QUAD_COMMAND: 绘制矩形区域,相邻的符合条件的可合并进行批绘制
BATCH_COMMAND:绘制一个TextureAtlas,如label,tilemap等。理论上可用QUAD_Command代替,但貌似有一点不同,后面说
GROUP_COMMAND:多个renderCOmmand的集合,里面的命令不参与全局排序。可用于子元素裁剪,绘制元素到纹理等。
CUSTOM_COMMAND:我们自定义的命令都属于这个
RenderQueue
RenderCommand会被添加到RenderQueue里,定义如下:
class RenderQueue{ public: void push_back(command); ssize_t size() const; void sort(); RenderCommand* operator[](ssize_t index) const; void clear(); protected: str::vector<RenderCommand*> _queueNegZ, _queue0, _queuePosZ; };
这里有3个绘制命令的队列,分别对应于globalZOrder小于0,等于0和大于0.ui元素默认为0,不用排序,其他globalZOrder不为0的每一帧都会排序,所以应该少用globalZOrder属性。
Render实际上还维护着一个RenderQueue数组,每个Queue对应一组RenderCommand或者一个GroupCommand,这些Queue不是简单的线性关系,而是通过GroupCommand构成树状。
GroupCommand
GroupCommand通常是没有gl绘制指令, 它只是指向另一个RenderQueue,当执行它时候,会去找到它指向的RenderQueue,执行里面的绘制指令。RenderCommand是不知道自己处于哪一个queue里。
我们知道Render里有一个queue的栈,当开始一个GroupCOmmand时候会往栈里添加一个RenderQueue,而默认情况下,添加一个RenderCommand都会往这个栈的栈顶的Queue里添加,当GroupCommand完成分组时,groupcommand会从栈里移除自己,后续RenderCommand又可以添加到原来的queue里去了,这样每个command都不用知道自己身处哪个queue
Render
RenderCommand的排序:对globalZOrder非0的command进行排序,从小到大。每一个RenderQueue独立排序。所以command的globalZOrder排序只是和当前的RenderQueue里排序,不和其他的queue的命令一起排序。
QuadCommand:这是用来绘制一个或者多个矩形区域,每个矩形是纹理的一部分,一个命令包含4部分:
TextureID:纹理的id
Shader Program:渲染程序
BlendFunc:混合模式
Quads:需要绘制一个或者多个矩形区域的定义,包括每个点的坐标,颜色和纹理坐标。
原理:http://blog.csdn.net/h1051760124/article/details/41776931 (代码没看懂)
一个顶点的结构:
struct V3F_C4B_T2F { Vec3 vertices; //顶点位置 Color4B colors; //顶点颜色 Tex2F texCoords; //顶点纹理uv }
自动批绘制:相同纹理,着色器等绘制特征,会调用一次绘制命令,就可绘制多个QuadCommand,这样就不用手动添加到SpriteBatchNode里,也能实现效果。过程如下:
1.第一次遇到QuadCOmmand不会马上绘制,而是放入一个数组里缓存
2.遇到下一个RenderCommand仍是QuadCommand时,进行对比,如果符合批绘制的条件,则继续放入数组内缓存,如果不是quadcommand或者不符合条件,则绘制缓存数组内的command。
从QuadComman的generateMaterialID方法看,可以得知一些事情。
首先检查是否有自定义的着色器全局变量,如果有,那么要使用这些变量的话,就得提供自定义的着色器,这就不能参加批绘制。如果提供了自定义着色器,_materialID会设置为MATERIAL_ID_DO_NOT_BATCH。不然,会使用一些参数计算hash,相等则自动批绘制
还需要注意的是,批绘制的所有顶点数据使用一个vbo,最大容纳为10922(65536/6),当大于这个值则会马上绘制前面的命令
元素的可见性
cocos2dx 3.0介绍了一种自动裁剪的技术,它在遍历ui树时,发现其在屏幕外,则不发送绘制命令到Render,目前只在sprite元素上使用。Sprite在绘制时会先计算可见性。_insideBounds为false则表示在场景不可见,不绘制。这个计算在每一帧的每一个Sprite元素。这变量可能在元素扭曲,缩放,平移,旋转和父元素改变等情况下修改。
node也有一个visable属性,当它不为true时,遍历ui树的时候会忽略它,这个是可以手动设置的setVisable。它和上面的区别是这里会忽略子元素的遍历,而上面那个不会,只是不发送绘制命令而已。
还有一点,就是事件一般和node关联,事件分发器按层级分发,但是否可见并不会影响其分发,也就是你可能看不到一个元素,但事件被它获取到,可以在事件触发的时候判定元素的可见性做处理。
绘制时机
绘制从ui树分离后,我们就不知道元素在什么时候被绘制了,只有等下一帧才确定所有绘制命令被执行了。但如果要比较及时执行,有2个方法。
1.注册schedule,注册一帧的schedule,在下一帧被执行时读取绘制结果
2.自定义CustomCommand命令,在命令里设置了回调方法,当这命令放入的位置合适时,就可以实现及时的运算读取,例如saveToFile,保存图像到一个文件里,就比较方便的在绘制命令执行时执行回调。
自定义RenderCommand
1.继承node或者node的子类,建立一个新的ui元素
2.自定义一个RenderCommand,实现OpenGL Es部分
3.新建的node使用这个command