这是Opengl ES入门系列的第17篇博文啦...
今天的内容比较简单,给大家介绍一下纹理数组,它是OpenGL ES 3.0引入的一个新特性,它能让我们以数组的方式往shader中传递纹理。
2D纹理数组是OpenGL ES 3.0开始支持的纹理类型。通过使用2D纹理数组,在同一个着色器中使用多个2D纹理的情况下可以简化开发。
试想一下,如果没有2D纹理数组技术,当一个着色器中需要使用多个2D纹理时就需要声明多个采样器变量,而如果使用2D纹理数组则同样的功能只需声明一个sampler2DArray类型的变量即可,方便高效,
而且扩展性更强。
如果是单单使用2D纹理的话,一旦着色器程序确定了,那么内部可使用的纹理个数就确定了,而如果使用2D纹理数组的话,即使着色器程序确定了,我们依然可以按照不同的需求使用不同个数的纹理,
这就大大提高了我们编程的灵活性。
2D纹理数组的使用
2D纹理数组的使用和普通的纹理贴图步骤差不多,只是普通纹理使用绑定的是GL_TEXTURE_2D
,而2D纹理数组绑定的类型是GL_TEXTURE_2D_ARRAY
,同时2D纹理数组分配纹理空间时需要使用函数glTexImage3D
而不是glTexImage2D
。
另外在使用2D纹理数组进行采样时,需要提供的纹理坐标有3个分量也就是vec3
,前两个与普通2D纹理坐标含义相同,为S、T分量,第三个分量则为2D纹理数组中的索引。
下面是一个简单的使用2D纹理数组的片元着色器:
#version 300 es
precision mediump float;
precision mediump sampler2DArray;
out vec4 FragColor;
in vec3 TexCoord;
uniform sampler2DArray ourTexture;
void main()
{
FragColor = texture(ourTexture, TexCoord);
}
可以看到纹理坐标使用的是三个分量变量,而且需要声明sampler2DArray
精度类型。
以下是使用纹理数组绘制两张不同的图片的一个demo源码实例:
TextureArrayOpengl.cpp
// 顶点着色器
static const char *ver = "#version 300 es\n"
"in vec4 aPosition;\n"
"in vec3 aTexCoord;\n"
"out vec3 TexCoord;\n"
"void main() {\n"
" TexCoord = aTexCoord;\n"
" gl_Position = aPosition;\n"
"}";
// 片元着色器
static const char *fragment = "#version 300 es\n"
"precision mediump float;\n"
"precision mediump sampler2DArray;\n"
"out vec4 FragColor;\n"
"in vec3 TexCoord;\n"
"uniform sampler2DArray ourTexture; \n"
"void main()\n"
"{\n"
" FragColor = texture(ourTexture, TexCoord);\n"
"}";
// 使用绘制两个三角形组成一个矩形的形式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
0.5f,0.0f, // 右下
0.5f,0.5f, // 右上
0.0f,0.0f, // 左下
0.0f,0.5f, // 左上
0.0f,-0.5f, // 右下
0.0f,0.0f, // 右上
-0.5f,-0.5f, // 左下
-0.5f,0.0f // 左上
};
// 贴图纹理坐标(参考手机屏幕坐标系统,原点在左上角)
//由于对一个OpenGL纹理来说,它没有内在的方向性,因此我们可以使用不同的坐标把它定向到任何我们喜欢的方向上,然而大多数计算机图像都有一个默认的方向,它们通常被规定为y轴向下,X轴向右
const static GLfloat TEXTURE_COORD[] = {
1.0f,1.0f,0, // 右下
1.0f,0.0f,0, // 右上
0.0f,1.0f,0, // 左下
0.0f,0.0f,0, // 左上
1.0f,1.0f,1, // 右下
1.0f,0.0f,1, // 右上
0.0f,1.0f,1, // 左下
0.0f,0.0f,1 // 左上
};
TextureArrayOpengl::TextureArrayOpengl():BaseOpengl() {
initGlProgram(ver,fragment);
positionHandle = glGetAttribLocation(program,"aPosition");
textureHandle = glGetAttribLocation(program,"aTexCoord");
textureSampler = glGetUniformLocation(program,"ourTexture");
LOGD("program:%d",program);
LOGD("positionHandle:%d",positionHandle);
LOGD("textureHandle:%d",textureHandle);
LOGD("textureSample:%d",textureSampler);
}
void TextureArrayOpengl::setPixel(void *data, int width, int height, int length) {
LOGD("texture setPixel");
glGenTextures(1, &textureId);
// 绑定纹理
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 使用glTexStorage3D搭配glTexSubImage3D也是可以的
// 注意这里是GL_RGBA8而不是GL_RGBA
// glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, width, height,2);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, width, height,2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY,0,0,0,0,width,height,1,GL_RGBA,GL_UNSIGNED_BYTE,data);
// 解绑定
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
}
// 主要先调用setPixel设置第一张纹理,在setPixel中声明了一个容量为2的纹理数组,先调用setPixel2的话是不行的哦
void TextureArrayOpengl::setPixel2(void *data, int width, int height, int length) {
LOGD("texture setPixel2");
// 绑定纹理
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY,0,0,0,1,width,height,1,GL_RGBA,GL_UNSIGNED_BYTE,data);
// 解绑定
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
}
void TextureArrayOpengl::onDraw() {
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
// 激活纹理
glActiveTexture(GL_TEXTURE2);
glUniform1i(textureSampler, 2);
// 绑定纹理
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId);
/**
* size 几个数字表示一个点,显示是两个数字表示一个点
* normalized 是否需要归一化,不用,这里已经归一化了
* stride 步长,连续顶点之间的间隔,如果顶点直接是连续的,也可填0
*/
// 启用顶点数据
glEnableVertexAttribArray(positionHandle);
glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES);
// 纹理坐标
glEnableVertexAttribArray(textureHandle);
glVertexAttribPointer(textureHandle,3,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD);
// 4个顶点绘制两个三角形组成矩形
glDrawArrays(GL_TRIANGLE_STRIP,0,8);
glUseProgram(0);
// 禁用顶点
glDisableVertexAttribArray(positionHandle);
if(nullptr != eglHelper){
eglHelper->swapBuffers();
}
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
}
TextureArrayOpengl::~TextureArrayOpengl() {
LOGD("TextureArrayOpengl析构函数");
}
运行结果显示:
注意事项
查阅相关资料解说使用2D纹理数组时,一般会使数组中的纹理保持相同的尺寸。
但是笔者在某些机型上实测发现只要加载的纹理大小不要超过glTexImage3D
中声明的宽高也能正常加载使用,不知道如果尺寸不一只是否有兼容性问题,
实际开发中还是建议大家尽量保存纹理数组的尺寸大小都一致。
系列教程源码
https://github.com/feiflyer/NDK_OpenglES_Tutorial
Opengl ES系列入门介绍
Opengl ES之EGL环境搭建
Opengl ES之着色器
Opengl ES之三角形绘制
Opengl ES之四边形绘制
Opengl ES之纹理贴图
Opengl ES之VBO和VAO
Opengl ES之EBO
Opengl ES之FBO
Opengl ES之PBO
Opengl ES之YUV数据渲染
YUV转RGB的一些理论知识
Opengl ES之RGB转NV21
Opengl ES之踩坑记
Opengl ES之矩阵变换(上)
Opengl ES之矩阵变换(下)
Opengl ES之水印贴图
Opengl ES之文字渲染
关注我,一起进步,人生不止coding!!!