Cocos2dx实现多重纹理

简介: Cocos2dx实现多重纹理

完成效果

2个动画分别使用不同的Plist,最终1个DC完成绘制

最终实现效果:

网络异常,图片无法展示
|

网络异常,图片无法展示
|

原理剖析

关于多重纹理,在江南百景图的技术分析里面也有提到

网络异常,图片无法展示
|

关于PASS是什么?请原谅我不知道,因为我知道Shader里面并没有PASS这玩意,很显然,这是game engine层面抽象出来的概念,同样MESH也是这个道理。

要合批,必须同时满足以下3个条件,缺一不可

  • 相同的Blend
  • 相同的Texture
  • 相同的Shader

如何实现嘞?当然要从OpenGL程序的角度考虑这个问题。

最简单的绘制图片过程

理解OpenGL绘制图片的过程,对理解多重纹理如何实现非常重要!!!

以下都是伪代码,是为了方便理解

  • vertex.shader
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec4 v_fragmentColor;
void main() {
    gl_Position = CC_PMatrix * a_position;
    v_fragmentColor = a_color;
    v_texCoord = a_texCoord;
    v_position = a_position;
}
复制代码
  • fragment.shader
varying vec2 v_texCoord;
varying vec4 v_fragmentColor;
varying vec4 v_position;
uniform sampler2D texture1;
void main() {
    gl_FragColor = v_fragmentColor * texture2D(texture1, v_texCoord);
}
复制代码
  • 逻辑代码
// 顶点数据
Vertices vertices=[
    [color,position,texCoord],
    [color,position,texCoord]
]; 
// 获取属性
int positionLocation = glGetAttribLocation(shader, "a_positon");
// 启用这个属性
glEnableVertextAttribArray(positionLocation); 
// 从vertices中提取属于postion的数据,绑定到shader中定义的属性
// 我这么写隐藏了非常多的细节,主要是为了方便理解
glVertexAttriPointer(positionLocation, vertices, vertices.position); 
// ... texCoord, color 属性同理也按照上边的步骤进行设置即可
// 至此,GPU已经知道如何解析顶点数据了,其实这么描述不太准确,但是方便理解
int textureUnit = 0;
// 其实texture->getName()就是这个东西
GLint textureName; 
// 分配纹理单元
glGenTextures(1, &textureName); 
// 激活纹理单元,注意这里是0号单元
// 如果要使用其他纹理单元,向下边这样累加就行,实际上cocos2dx就是这么玩的
glActiveTexture(GL_TEXTURE0 + textureUnit); 
// 绑定分配的纹理单元到激活的纹理单元,虽然这时是textureName啥都没得
glBindTexture(GL_TEXTURE2D, textureName);
// 以上这几步,纯粹是为了建立映射关系
Bytes imageData="", imageWidth=100, imageHeight=100;
glEnable(GL_TEXTURE_2D);
// 将纹理数据绑定到上边的纹理单元:textureName
// 这个api的用法不是这样子,这样写也是为了方便理解
glTexImage2D(GL_TEXTURE_2D,imageWidth,imageHeight,imageData);
// 从fragment中获取texture1变量 
int texture = glGetUniformLocation(shader, "texture1"); 
// 设置每个采样器使用哪个纹理单元,这里我们从头到尾一直都是在操作纹理单元0
glUniform1i(texture, textureUnit);
// 完成最终的绘制
glDrawArrays(GL_TRIANGLES);
复制代码

以上的流程挺长的,但这个已经是最小的绘制图片流程了,总结下就是:

  1. glVertexAttriPointe将顶点数据正确的分配个vertex.shader中的attribute,通过varying将数据传递给fragment.shader
  2. 激活指定的纹理单元,并填充纹理数据
  3. 将fragment.shader中的uniform sampler2D,通过glUniform1i和刚刚操作的纹理单元建立映射,这样texture2D的的结果就是我们刚刚填充的纹理数据了
  4. draw

合批到底是什么?

理解了以上的图片绘制,我们再从OpenGL的角度理解下,到底什么是合批?

上边的绘制图片过程我们封装为一个drawImage函数,如果我绘制2个图片,那么我们可能会这样做

drawImage();
drawImage();
复制代码

调用了2次,也就触发了2次OpenGL的draw函数,但是如果一帧调用了大量的draw函数,就会产生性能压力,主要是gl的各种操作、CPU和GPU之间的数据交换,其实都是有代价的,所以你也会发现OpenGL出现了各种buffer的概念,有点类似缓存的味道。

那么有没有办法1个draw,绘制2个图片呢?

肯定有!我们如果查阅OpenGL的绘制形状,你会发现绘制三角形能满足我们的需求,因为图片也是由2个三角形组成,给你4个三角形,你肯定能拼凑出2个不同位置的四边形。

如果你仔细阅读源码,你也会发现,engine的绘制形状一定是GL_TRIANGLES

那该如何做呢?

只需要顶点数据一次传递4个三角形顶点即可!

这样,我们就一次draw,绘制了2个不同位置的图片,这个骚操作,在游戏引擎里面,称作合批,也可以理解为,使用最少的draw完成目标效果,前提是我们需要保证绘制结果正常。

多重纹理又是怎么回事

我们需要知道的是,多重纹理也是合批的一个手段,目的都是在通过一定的手段,达到合批效果。

多重纹理,顾名思义就是可以在多个预设的纹理之间切换,我们仔细观察下fragment.shader

gl_FragColor = v_fragmentColor * texture2D(texture1, v_texCoord);
复制代码

因为shader可以自己编写,如果我们想在多个纹理之间切换,那么shader可能就会是这样子

int unit=0;
if( 0 == unit ){
    gl_FragColor = v_fragmentColor * texture2D(texture0, v_texCoord);
}else if( 1 == unit ){
    gl_FragColor = v_fragmentColor * texture2D(texture1, v_texCoord);
}
复制代码

那么问题就变成我们怎么控制unit达到切换纹理的效果。

我们首先会想到将unit定义为uniform,这样我们在代码中通过glUniform1i(unit, 3)修改。

至此,你已经理解了,多重纹理的实现思路,本质上就是在fragment.shader中定义多个sampler2D ,在代码中修改shader中定义的变量,影响fragment采样不同的texture即可。

实现多重纹理如何合批

让我们再看下条件

  • 相同的Blend: 这个就不用解释了,肯定得一致
  • 相同的Shader:这个也不用解释了,肯定得一致
  • 相同的Texture

网络异常,图片无法展示
|

问题就在相同的Texture上,多重纹理使用的是多个纹理,这怎么搞?

其实仔细思考下,这个条件其实对多纹理并没有多大意义。

因为不同组合的多纹理,我们可以生成不同的Shader实例,通过这个Shader实例,就可以判断,是否满足合批条件。

当然还有另外一个办法,Spire使用的shader实例是同一个,通过shader绑定的纹理Array的md5、hash值,也可以区分是否满足合批条件。

unit怎么传递呢?

上文说的uniform方式其实是有问题的。

2个图片的顶点数据最终是揉到了一个顶点数据里面,OpenGL在绘制的时候,如何在fragment里面知道当前绘制的顶点应该采用哪个 texture unit呢?

很显然unit其实是和顶点数据相关,所以必须放在顶点数据里面。

在creator中,你可以重新定义vertex format,但是在2dx中,并没有开放vertex format,那么unit数据应该放到哪里呢?

哈哈!一个2d engine,目前并没有采用positon.z,所以就hack到这里了!最重要的是这样子不用改渲染结构,算是勉强实现了需求。

至此,整个实现思路就完全剖析完毕,开始撸代码了。

扩展知识

获取fragment支持的最大纹理单元数量

GLint maxTextureUnits;
    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &maxTextureUnits);
复制代码

实际测试发现GL_MAX_TEXTURE_UNITS总是返回4,后来发现这个属性在OpenGL3中是废弃了,应该使用GL_MAX_TEXTURE_IMAGE_UNITS

属性 说明 取值
GL_MAX_TEXTURE_UNITS 支持的常规纹理(纹理坐标集+纹理图像单元)单元数量,这个数值返回的总是4,因为在OpenGL3中被弃用了 4
GL_MAX_TEXTURE_IMAGE_UNITS 片段着色器访问纹理贴图单元的数量 32
GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 32
GL_MAX_TEXTURE_COORDS 获取最大纹理坐标 8
GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 所有阶段着色器可以绑定的纹理单元,每个阶段可使用GL_MAX_TEXTURE_IMAGE_UNITS,一共6个阶段,所有32*6=192 192


目录
相关文章
|
6月前
cocos2dx GLProgram
cocos2dx GLProgram
18 0
|
Android开发 C++ 开发者
《Cocos2D-x权威指南》——1.3 Cocos2D-x与Cocos2D-iPhone的比较
本节书摘来自华章计算机《Cocos2D-x权威指南》一书中的第1章,第1.3节,作者:满硕泉著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
992 0
|
Android开发 iOS开发 开发者
《Cocos2D-x权威指南》——1.1 什么是Cocos2D
本节书摘来自华章计算机《Cocos2D-x权威指南》一书中的第1章,第1.1节,作者:满硕泉著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1709 0
|
定位技术 C++ 开发者
《Cocos2D权威指南》——1.1 什么是Cocos2D
本节书摘来自华章计算机《Cocos2D权威指南》一书中的第1章,第1.1节,作者:王寒,屈光辉,周雪彬著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1346 0
|
JavaScript Android开发 C++
《Cocos2D-x权威指南》——1.2 什么是Cocos2D-x
本节书摘来自华章计算机《Cocos2D-x权威指南》一书中的第1章,第1.2节,作者:满硕泉著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1282 0
《Cocos2D-x权威指南》——第1章 认识Cocos2D-x
本节书摘来自华章计算机《Cocos2D-x权威指南》一书中的第1章,作者:满硕泉著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1062 0