cocos2dx实现不同图片材质精灵渲染批次合并
一、 实现技术原理
多重纹理、 顶点合并
二、适用场景。
有大量的精灵,位于同一个图集或者不同图集,最多8张不同的图集(目前只是用了8重纹理)。可以执行帧动画,但是帧动画的所有帧必须在
同一个图集上。并且所有执行该帧动画的精灵动画完全同步,即必须整齐划一的步伐,比如军队士兵。不同步其实也可以实现,但是由于不能
共用同一个精灵的uv,所以需要所有精灵都执行一个动画来更新,会带来额外的动作更新开销。暂时我们项目中要求一致就可以了,因此未实现。
三、技术细节
1、SpriteCombine对象, 该对象代表一个精灵组合用来一次渲染所有需要合并到同一个批次的精灵。同时管理精灵组对象。该对象中最重要的函数
就是draw()函数。 该函数中将所有当前可见的需要渲染的精灵的顶点和UV信息提取出来合并到一个网格中,需要获取精灵的模型视图矩阵
_modelViewTransform,调用该矩阵的transformPoint()来转化精灵的顶点到相机空间。 最后提交一个渲染命令
2、CombineGroup对象, 该对象由SpriteCombine对象创建而来,代表一组使用相同图集或纹理的精灵。一个精灵组合对象中最多只能同时拥有8个该
对象。主要通过分组来确定每个精灵的纹理信息。 因为有8张纹理,但是只用一套uv, 需要给顶点加一个额外属性,来确定该顶点具体从哪张纹理进行
采样。但是给顶点增加属性即增加顶点带宽又要增加比较多的代码改动,后来使用了一个小技巧,由于是2d引擎,所以坐标的z其实是可以不要的,当然
也可以做深度检测,但是无需像3d场景那样有精确的位置关系,只需要能够表示层次关系就可以。所以决定将坐标z分为整数部分和浮点部分来使用。整数
部分用来确定深度(层次关系),需要打开深度检测,默认是不打开的。 浮点部分0-7 乘以0.1得到(0.0-0.7)来记录uv(只有8张纹理,精度足够), 但是由于考虑glsl的浮点精度特别
低,还有浮点数边界精度问题(3.0可能实际是2.99999),所以我对该浮点部分统一加了01得到一个(0.1-0.8)的数来表示uv,并且在比较的时候没有用等于而是用 < 原值+0.15 来判断。
3、CombineSprite对象, 该对象继承自精灵,表示一个需要合并的精灵对象,该精灵对象只能在SpiteCombine对象中渲染。 因为重载了精灵的OnDraw()函数,没有提交渲染命令
,只是把自己被渲染这个信号发给了SpiteCombine对象。 如何知道所有的CombineSprite被渲染(visist)完了,这里用到一个基数,每次开始的时候将SpriteCombine对象的待渲染精灵
数量设置为给对象中精灵列表的容器大小,在重载的visist函数中对该值减一。另外还需要一个开始遍历渲染的标志变量。 最后当该值被减到0则说明该精灵组合中的所有精灵被遍历
完成,然后执行真正的渲染。
4、TrianglesCombineCommand 对象,该对象继承了TrianglesCommand对象,重载了useMaterial() 方法,需要将父类中该函数申明为虚函数。 主要实现了增加多重纹理的绑定
四、源码分享
由于是工作研究成果,暂不公开全部源码。 这里附上shader部分:
顶点:
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
varying float texnum;
void main()
{
texnum = fract(a_position.z); //截取小数部分来判断uv, 整数部分做深度检测
gl_Position = CC_PMatrix * a_position;
v_fragmentColor = a_color;
v_texCoord = a_texCoord;
}
片段:
varying vec2 v_texCoord;
varying vec4 v_fragmentColor;
varying float texnum;
uniform sampler2D texture0;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D texture3;
uniform sampler2D texture4;
uniform sampler2D texture5;
uniform sampler2D texture6;
uniform sampler2D texture7;
uniform sampler2D texture8;
void main(void)
{
if(texnum < 0.15){ gl_FragColor = v_fragmentColor * texture2D(texture0, v_texCoord); }
else if(texnum < 0.25){ gl_FragColor = v_fragmentColor * texture2D(texture1, v_texCoord);}
else if(texnum < 0.35){ gl_FragColor = v_fragmentColor * texture2D(texture2, v_texCoord);}
else if(texnum < 0.45){ gl_FragColor = v_fragmentColor * texture2D(texture3, v_texCoord);}
else if(texnum < 0.55){ gl_FragColor = v_fragmentColor * texture2D(texture4, v_texCoord);}
else if(texnum < 0.65){ gl_FragColor = v_fragmentColor * texture2D(texture5, v_texCoord);}
else if(texnum < 0.75){ gl_FragColor = v_fragmentColor * texture2D(texture6, v_texCoord);}
else { gl_FragColor = v_fragmentColor * texture2D(texture7, v_texCoord);}
gl_FragColor += vec4(0.2, 0.0, 0.0, 0.2);
}
一些截图: