我们可以通过不同的纹理单元去一次性的去访问几个纹理,也就是说着色器可以一次性访问多个纹理对象,实际上还有中方法也能达到这种效果,就是使用纹理数组,也就是说我们可以将几个2D图像都去加载到一个单独的纹理对象中,其实Mip贴图中每个Mip层次都是一个不同的图像以及立方体贴图中立方体的每个面都有它们自己的图像也都是在一个纹理中添加多个图像的体现
对于纹理贴图,我们可以将整个数组的纹理图像绑定到一个纹理对象上,然后在着色器中对它们进行检索操作,这样的话就在很大程度上增加了着色器可用的纹理数据的数量。
生成纹理对象
//生成纹理
glGenTextures(1, &moonTexture);
//绑定纹理
glBindTexture(GL_TEXTURE_2D_ARRAY, moonTexture);
//设置如何从数据缓冲区去读取图像数据
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//设置纹理过滤的参数
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
加载纹理数据
参数1:target 纹理目标
参数2:level,所加载的Mip贴图的层次,一般为0
参数3:internalformat,颜色组件
参数4:width,宽度
参数5:height,高度
参数6:depth,深度
参数7:border,0 纹理贴图的边框大小
参数8:format, 变量指定pixels指向数据元素的颜色布局
参数9:type,存储数据的类型 告诉OpenGL是使用什么样的数据类型来存储颜色分量
参数10:pixels,指向纹理数据的指针
下面宽度和高度采用64的原因就是我们的月亮纹理图片就是64*64像素大小,深度29因为有29张图片,需要保留29个64*64的RGBA图像
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 64, 64, 29, 0,
GL_BGRA, GL_UNSIGNED_BYTE, NULL);
对于上面的函数,我们最后一个参数使用的是NULL,这样表示没有任何纹理数据需要复制,OpenGL会保留纹理存储空间,会将它们保持为未初始化状态,因为虽然上面的那个函数可以一次性加载整个2D图像数组。但是这个方程是需要一次性去加载一个2D图像数组,我们这里是需要加载29个独立的图像,为了我们得到我们想要的效果,这个方法是很麻烦的,所以我们可以采用更新纹理的方法
更新纹理
//加载图片
for(int i = 0; i < 29; i++) {
char cFile[32];
sprintf(cFile, "moon%02d.tga", i);
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// Read the texture bits 读取纹理数据
pBits = gltReadTGABits(cFile, &nWidth, &nHeight, &nComponents, &eFormat);
//更新纹理 这里把深度进行了修改,下面的i就代表深度
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, nWidth, nHeight, 1, GL_BGRA, GL_UNSIGNED_BYTE, pBits);
free(pBits);
}
关于glTexSubImage3D函数的原型是
void glTexSubImage3D(GLenum target,GLint Level,GLint xOffset,GLint yOffset,GLint zOffset,GLsizei width,GLsizei height,GLsizei depth,GLenum format,GLenum type,const GLvoid * data)
这个函数的绝大多数参数的含义都是和glTexImage3D中的参数是一一对应的,其中xOffset、yOffset、zOffset参数是指定了在原来纹理贴图中开始替换纹理数据的偏移量,width、height和depth参数指定了插入到原来那个纹理中的新的纹理的宽度、高度和深度
下面来看看我们是如何让着色器中进行图片的切换的,首先我们先来看看着色器中的内容
顶点着色器
//顶点属性
attribute vec4 vVertex;
//纹理坐标
attribute vec4 vTexCoords;
//mvp矩阵
uniform mat4 mvpMatrix;
//外面传入的时间也就是拿来当做深度来用
uniform float fTime;
//传给片元着色器使用的纹理坐标
varying vec3 vMoonCoords;
void main()
{
//三维纹理坐标的s和t坐标的值从外面传递过来
vMoonCoords.st = vTexCoords.st;
//p值就是根据时间来获取
vMoonCoords.p = fTime;
//变换顶点位置
gl_Position = mvpMatrix * vVertex;
}
片元着色器
//纹理采样器
uniform sampler2DArray moonImage;
//纹理坐标
varying vec3 vMoonCoords;
void main(void)
{
//纹理数组
gl_FragColor = texture2DArray(moonImage, vMoonCoords.stp);
}
这里需要注意的就是顶点着色器中的fTime值,就是这个值可以造成纹理的切换效果,也就是用这个拿来当做深度值,在外面我们是这样传入的,就是用CStopWatch的对象去获取时间间隔
static CStopWatch timer
float fTime = timer.GetElapsedSeconds();
//取余数
fTime = fmod(fTime, 28.0f);
随着时间的流逝会有这样的纹理切换的效果