大家好,這是我的OpenGL ES
高階進階系列文章,在我的github
上有一個與本系列文章對應的專案,歡迎關注,連結:github.com/kenneycode/…
今天給大家介紹一下紋理陣列,它是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)- 紋理》),不一樣的地方是首先繫結紋理時的型別不一樣:
// 建立圖片紋理陣列
// 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
,專案連結:github.com/kenneycode/…
感謝閱讀!