OpenGL ES 高级进阶:纹理数组
今天给大家介绍一下纹理数组,它是OpenGL ES 3.0
引入的一个新特性,它能让我们以数组的方式往shader
中传递纹理,我们先来看看一个普通的fragment shader
:
#version 300 es
precision mediump float;
layout(location = 0) out vec4 fragColor;
layout(location = 0) uniform sampler2D u_texture;
in vec2 v_textureCoordinate;
void main() {
fragColor = texture(u_texture, v_textureCoordinate);
}
在这个shader
中我们声明了一个uniform sampler2D
变量,即我们只能传递一个纹理,如果要传递多个纹理,就要声明多个uniform sampler2D
:
#version 300 es
precision mediump float;
...
layout(location = 0) uniform sampler2D u_texture0;
layout(location = 1) uniform sampler2D u_texture1;
layout(location = 2) uniform sampler2D u_texture2;
...
这样一方面会占用多个纹理单元,另一方面一旦shader
定了,里面支持的纹理数量也就定了,不利于各种数量的纹理,除非自己去生成shader
。
纹理数组的出现让我们可以像传递一个数组一样将纹理传给shader
,我们来看一下使用纹理数组时的shader
:
// vertex shader
#version 300 es
precision mediump float;
layout(location = 0) in vec4 a_Position;
layout(location = 1) in vec3 a_textureCoordinate;
out vec3 v_textureCoordinate;
void main() {
v_textureCoordinate = a_textureCoordinate;
gl_Position = a_Position;
}
// fragment shader
#version 300 es
precision mediump float;
precision mediump sampler2DArray;
layout(location = 0) out vec4 fragColor;
in vec3 v_textureCoordinate;
layout(location = 0) uniform sampler2DArray u_texture;
void main() {
fragColor = texture(u_texture, v_textureCoordinate);
}
我们先来看fragment shader
,可以看到,sampler2D
变成了sampler2DArray
,表示它是一个数组,然后使用的时候,也是texture(u_texture, v_textureCoordinate)
,似乎看越来和不使用纹理数组时一样?
其实不一样,也许细心的朋友发现了差别,v_textureCoordinate
此时是vec3
而不是vec2
了,我们知道纹理坐标是二维的,这里vec3
的第三维就是取对应的纹理,可以理解成是数组的下标。
我们还能看到,纹理数组不需要在shader
先声明数组的大小,它是在代码里控制的,这样就很灵活,我们来看看代码是怎样写的,大部分的操作和使用普通纹理时一样
(可参考我的另一篇文章:[《Android OpenGL ES 2.0 手把手教学(6)- 纹理》] https://juejin.cn/post/6844903834523811853)
不一样的地方是首先绑定纹理时的类型不一样:
// 创建图片纹理数组
// Create texture for image
val textures = IntArray(1)
GLES30.glGenTextures(textures.size, textures, 0)
imageTexture = textures[0]
// 注意这里是GL_TEXTURE_2D_ARRAY
// Note that the type is GL_TEXTURE_2D_ARRAY
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D_ARRAY, textures[0])
普通纹理是按GL_TEXTURE_2D
的类型绑定的,这里是GL_TEXTURE_2D_ARRAY
,另外,还要像给数组分配空间一样给纹理数组分配大小:
GLES30.glTexStorage3D(GLES30.GL_TEXTURE_2D_ARRAY, 1, GLES30.GL_RGBA8, 390, 270, 2)
比如在我们的例子中,我们创建了一个大小为2的纹理数组,每个纹理的大小是390*270,这里用的API
叫glTexStorage3D
,实际上还有一种纹理叫3D纹理,它和纹理数组有些类似,我们使用纹理数组的时候,有些API
就是使用了操作3D纹理时的API
。
接下来我们就把2张图片加载到我们的纹理数组中:
// 通过glTexSubImage3D指定每层的纹理
// Specify the texture of each layer via glTexSubImage3D
for (i in 0 until 2) {
val bitmap = Util.decodeBitmapFromAssets("image_$i.jpg")
val b = ByteBuffer.allocate(bitmap.width * bitmap.height * 4)
bitmap.copyPixelsToBuffer(b)
b.position(0)
GLES30.glTexSubImage3D(GLES30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, bitmap.width, bitmap.height, 1, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, b)
bitmap.recycle()
}
这步操作和我们使用单个纹理时很类似,单个纹理时是用glTexImage2D
,这里是用glTexSubImage3D
并且需要指定当前是给纹理数组的哪层纹理载入数据。
我们前面提到,v_textureCoordinate
此时是vec3
而不是vec2
了,因此我们传的纹理坐标也要相应的变化:
// 纹理坐标
// The texture coordinate
private val textureCoordinateData = floatArrayOf(
0f, 1f, 0f,
0f, 0f, 0f,
1f, 0f, 0f,
0f, 1f, 0f,
1f, 0f, 0f,
1f, 1f, 0f,
0f, 1f, 1f,
0f, 0f, 1f,
1f, 0f, 1f,
0f, 1f, 1f,
1f, 0f, 1f,
1f, 1f, 1f
)
另外,在这里我将顶点坐标设置为左下角和右上角,因此会在左下角和右上角分配渲染纹理数组中的第0个和第1个纹理,效果如下:
代码在我github
的OpenGLESPro
项目中,本文对应的是SampleTextureArray
,项目链接:https://github.com/kenneycode/OpenGLESPro
感谢阅读!
原文链接: https://juejin.cn/post/6844903846678888461
-- END --
进技术交流群,扫码添加我的微信:Byte-Flow
获取相关资料和源码
推荐:
全网最全的 Android 音视频和 OpenGL ES 干货,都在这了
面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?
项目疑难问题解答、大厂内部推荐、面试指导、简历指导、代码指导、offer 选择建议、学习路线规划,可以点击找我一对一解答。