一、紋理概念
紋理可以簡單理解爲物體表面的圖案,OpenGL ES 3.0 中紋理有:2D紋理、2D紋理數組、3D紋理和立方圖紋理。一個紋理的單獨數據元素稱爲“紋素”(texture pixel 紋理像素的縮寫)。
1. 紋理的座標系
2. 2D紋理
2D紋理是最基本和常用的紋理,可以把2D紋理想象爲一個圖像數據的二維數組。2D紋理的紋理座標用一對2D座標 (s, t) 指定,有時也稱作 (u, v) 座標。
紋理圖像座標的左下角爲 (0.0, 0.0),右上角爲 (1.0, 1.0)。在 [0.0, 1.0] 區間之外的座標也是允許的,在區間外的紋理讀取行爲由紋理包裝模式定義。
3. 2D紋理數組
2D紋理數組常用於存儲2D圖像的一個動畫,數組的每個切片表示紋理動畫的一幀。2D紋理數組使用與3D紋理一樣的紋理座標 (s, t, r),其中 r
座標選擇 2D 紋理數組中要使用的切片,(s, t) 座標選擇的方法和 2D 紋理完全一致。
二、紋理對象和紋理加載
1. 創建紋理
首先我們需要創建一個紋理對象,紋理對象就是一個容器,包含渲染所需的紋理數據,例如圖像數據、過渡模式、包裝模式等。
在 OpenGL ES 中,紋理對象使用一個無符號的整數表示,該整數即紋理對象的一個句柄/ID。
/**
* @param n 指定要生成的紋理對象數量
* @param textures 一個保存n個紋理對象ID的無符號整數數組
*/
void glGenTextures(GLsizei n, GLuint *textures);
2. 刪除紋理
紋理對象在不再需要時必須刪除,這一步驟通常在應用程序關閉或者遊戲級別改變時完成。
/**
* @param n 要刪除的紋理對象數量
* @param textures 一個保存n個紋理對象ID的無符號整數數組
*/
void glDeleteTextures(GLsizei n, const GLuint *textures);
3. 綁定紋理
當我們生成並得到紋理對象ID後,如果想對其進行操作,就必須先綁定這個紋理對象,這樣後續的操作影響的殭屍我們綁定的這個紋理對象。
/**
* @param target 目標紋理,即 GL_TEXTURE_2D、GL_TEXTURE_3D、GL_TEXTURE_2D_ARRAY 或 GL_TEXTURE_CUBE_MAP
* @param texture 要綁定的紋理對象ID
*/
void glBindTexture(GLenum target, GLuint texture);
4. 解綁紋理
當我們不想再操作這個紋理對象時,可以解綁這個紋理。綁定 0
就是解綁了,同樣是使用 glBindTexture
函數,參數傳入固定的值 0
即可。
5. 加載圖像數據
用於加載2D和立方圖紋理的基本函數是 glTexImage2D
。此外在 3.0 中還有多種替代方法指定 2D 紋理,如不可變紋理 glTexStorage2D
和 glTexSubImage2D
的結合,將在後續介紹。
/**
* @param target 目標紋理,即 GL_TEXTURE_2D、GL_TEXTURE_3D、GL_TEXTURE_2D_ARRAY 或 GL_TEXTURE_CUBE_MAP
* @param level 指定要加載的mip級別。第一個級別爲0,後續的mip貼圖級別遞增
* @param internalformat 紋理存儲的內部格式
* @param width 圖像的像素寬度
* @param height 圖像的像素高度
* @param border 在OpenGL ES中忽略,保留是爲了兼容桌面的OpenGL,傳入0
* @param format 輸入的紋理數據格式
* @param type 輸入像素數據的類型
* @param pixels 包含圖像的實際像素數據。像素行必須對齊到用glPixelStroei設置的GL_UNPACK_ALIGNMENT
*/
void glTexImage2D(
GLenum target, GLint level, GLint internalformat,
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type, const GLvoid *pixels)
關於紋理格式的介紹,可以參考:
https://blog.csdn.net/afei__/article/details/96158388
6. 相關設置
紋理的相關設置大都是通過 glTexParameter[i|f][v]
接口指定。這裏介紹兩個最常見的設置。
紋理過濾模式
紋理過濾模式,簡單的說就是當紋理需要放大和縮小時,應該怎麼處理。有關紋理過濾模式的介紹,可以參考:https://blog.csdn.net/afei__/article/details/96484772
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 縮小的情況
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 放大的情況
紋理座標包裝
前面介紹過,紋理座標系是 [0, 0, 1, 1],那麼當座標超過這個範圍時,應該發生怎樣的行爲。
紋理座標包裝有三種不同的模式:
- GL_REPEAT : 重複紋理
- GL_CLAMP_TO_EDGE : 限定讀取紋理的邊緣
- GL_MIRRORED_REPEAT : 重複紋理並鏡像
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // s軸
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // t軸
7. 示例
創建一個RGB圖像紋理爲例:
// 紋理對象的句柄
GLuint textureId;
// 一個2*2的圖像,四個像素依次爲紅、綠、藍、黃,每個像素大小爲3個字節
GLubyte pixels[4 * 3] = {
255, 0, 0, // Red
0, 255, 0, // Green
0, 0, 255, // Blue
255, 255, 0 // Yellow
};
// 創建一個紋理對象
glGenTextures(1, &textureId);
// 綁定紋理對象
glBindTexture(GL_TEXTURE_2D, textureId);
// 加載紋理
glTexImage2D(
GL_TEXTURE_2D, // target
0, // level
GL_RGB, // internalformat
2, // width
2, // height
0, // border
GL_RGB, // format
GL_UNSIGNED_BYTE, // type
pixels); // pixels
// 設置紋理過濾模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 設置紋理座標包裝
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
上述數據由無符號字節 RGB 三元組組成,範圍爲 [0, 255]
三、使用紋理
上面我們已經瞭解到如何創建和加載一個紋理,並得到這樣一個紋理id。那麼如何在代碼中使用這個紋理呢?通過一個實例來一步步瞭解吧。
1. 着色器腳本
頂點着色器
很簡單的一個頂點着色器,僅僅把輸入的數據再傳給片段着色器即可。
#version 300 es
layout(location = 0) in vec4 vPosition; // 輸入的頂點座標
layout(location = 1) in vec2 vTexCoor; // 輸入的紋理座標
out vec2 v_texCoor; // 輸出到片段着色器的紋理座標
void main() {
gl_Position = vPosition;
v_texCoor = vTexCoor;
}
片段着色器
使用紋理的真正地方,片段着色器中,使用到的內建函數 texture
作用是從紋理中指定位置讀取一個顏色的 vec4。
#version 300 es
precision mediump float; // 指定精度
uniform sampler2D s_texture; // 採樣器對象,當前綁定和激活的紋理
in vec2 v_texCoor; // 頂點着色器中輸出的紋理座標
out vec4 fragColor; // 該片段的顏色
void main() {
fragColor = texture ( s_texture, v_texCoor ); // 在對應紋理座標處進行採樣並得到對應的顏色
}
2. 創建一個紋理對象
就使用上面的那個示例即可
GLuint loadTexture() {
GLuint textureId;
// 2*2 RGB data for test
GLfloat pixels[4 * 3] = {
1.0, 0.0, 0.0, // Red
0.0, 1.0, 0.0, // Green
0.0, 0.0, 1.0, // Blue
1.0, 1.0, 0.0 // Yellow
};
// 創建一個紋理對象
glGenTextures(1, &textureId);
// 綁定紋理
glBindTexture(GL_TEXTURE_2D, textureId);
// 加載數據
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_FLOAT, pixels);
// 相關設置
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 解除綁定
glBindTexture(GL_TEXTURE_2D, 0);
// 返回紋理id
return textureId;
}
3. 使用這個紋理對象
我們以繪製一個矩形爲例,即繪製兩個三角形。
// 1. 創建一個程序(這裏省略了詳細代碼,可在最後的工程地址中找到)
GLuint g_program = CreateProgram(vertexShaderSource, fragmentShaderSource);
glUseProgram(g_program);
// 2. 初始化我們的頂點數據,即傳給頂點着色器的數據
// OpenGL的世界座標系是 [-1, -1, 1, 1],紋理的座標系爲 [0, 0, 1, 1]
// 又由於本例運行在安卓設備上,在安卓中 y 軸是向下的,想用正確的方向觀看圖像的話,也要注意這一點
GLfloat vertices[] = {
// 前三個數字爲頂點座標(x, y, z),後兩個數字爲紋理座標(s, t)
// 第一個三角形
1.0, 1.0, 0.0, 1.0, 0.0,
1.0, -1.0, 0.0, 1.0, 1.0,
-1.0, -1.0, 0.0, 0.0, 1.0,
// 第二個三角形
1.0, 1.0, 0.0, 1.0, 0.0,
-1.0, -1.0, 0.0, 0.0, 1.0,
-1.0, 1.0, 0.0, 0.0, 0.0
};
// 3. 加載紋理
GLuint textureId = loadTexture();
glActiveTexture(GL_TEXTURE0); // 激活TEXTURE0
glBindTexture(GL_TEXTURE_2D, textureId);
// 找到片段着色器中s_texture的位置,並給它賦值
GLint location = glGetUniformLocation(g_program, "s_texture");
glUniform1i(location, 0); // 因爲激活的是TEXTURE0,所以要給這個紋理賦值0
// 4. 加載頂點數據
// 給vPosition賦值,它的location是0,然後size是3,stride是5個float,起始指針是vertices
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), vertices);
glEnableVertexAttribArray(0);
// 給vTexCoor賦值,它的location是1,然後size是2,stride是5個float,起始指針是vertices+3
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), vertices + 3);
glEnableVertexAttribArray(1);
// 5. 繪製
glDrawArrays(GL_TRIANGLES, 0, 6); // 繪製兩個三角形,一共是6個點
4. 效果圖
由於放大的關係,過渡的顏色信息都是插值生成的,顏色分別爲紅、綠、藍、黃四個像素的 2*2 大小的紋理放大後的效果如下:
四、工程地址
上面沒有列出的代碼,可以在下面地址中找到:
https://github.com/afei-cn/OpenGLSample/tree/master/texturedemo
另外,這是一個可運行的 Android App 工程。上例中使用的是 C++ 的 API,還有一個使用 Java API 加載圖片紋理的例子也在上面的地址中,效果圖如下:
最後,一些 OpenGL 的相關文章: https://blog.csdn.net/afei__/article/category/8502759