OpenGL ES 3.0(五)紋理

我們可以爲每個頂點添加顏色來增加圖形的細節,從而創建出有趣的圖像。但是,如果想讓圖形看起來更真實,我們就必須有足夠多的頂點,從而指定足夠多的顏色。這將會產生很多額外開銷,因爲每個模型都會需求更多的頂點,每個頂點又需求一個顏色屬性。因此,一般情況下會使用紋理達到相同的效果。

紋理可以認爲是一張可以貼到物體(圖元)表面上的圖片/花紋/貼紙,使物體(圖元)變得生動、精細。

紋理的使用

創建並設置紋理

// 紋理對象的句柄
GLuint textureId;

// 一張寬高爲 2 x 2 的圖片, 每個像素用 (R, G, B) 表示,佔 3 bytes
GLubyte pixels[4 * 3] =
{
    255, 0, 0,  // Red
    0, 255, 0,  // Green
    0, 0, 255,  // Blue
    255, 255, 0 // Yellow
};

// 指定像素的對其方式
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

// 生成一個紋理對象
glGenTextures(1, &textureId);

// 綁定該紋理對象到 GL_TEXTURE_2D 中
glBindTexture(GL_TEXTURE_2D, textureId);

// 加載紋理對象並填充像素數據
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);

// 設置紋理參數
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

片段着色器

在着色器代碼中,使用 sampler 代表一個紋理,比如 2D 紋理,就是 sampler2D。

#version 300 es

precision mediump float;

uniform sampler2D sTexture;

in vec2 vTexCoord;

layout(location=0) out vec4 fragColor;

void main() {
    fragColor = texture(sTexture,  vTexCoord);
}

vTexCoord 是從頂點着色器傳過來的座標矩陣,自定義一個就好,比如鋪滿屏幕的座標矩陣如下:

static GLfloat TEXTURE_COORDS[] = {
        0.0f, 1.0f,
        1.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f
};

和 OpenGL [-1, 1] 的座標系不同,紋理的座標系如下:

紋理座標

繪製紋理

    glViewport(0, 0, mWidth, mHeight);
    ...

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureId);
    glUniform1i(textureLoc, 0); // 0 代表 GL_TEXTURE0

    ... // 填充頂點座標

    glDrawArrays(...); // 繪製

textureLoc 是使用 glGetUniformLocation(program, “sTexture”) 得到的。

函數詳解

使用紋理對象

void glGenTextures(GLsizei n, GLuint *textures);

void glBindTexture(GLenum target, GLuint texture);

void glDeleteTextures(GLsizei n, GLuint *textures);

glPixelStorei

OpenGL 默認採用四字節的對齊方式,因此存儲一個圖像所需的字節數並不完全等於 “寬像素字節數” ,比如,圖像的寬度爲 199,包含 RGB 3 個顏色分量,如果硬件結構是 4 字節排列的話,此時一行圖像需要的存儲空間就不等於:199*3=597,而應該在末尾填充 3 個空字節,因此是 600 字節。可以使用下列函數改變像素的存儲方式:

void glPixelStorei(GLenum pname, GLint param);
void glPixelStoref(GLenum pname, GLfloat param);

比如:

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

其中 GL_UNPACK_ALIGNMENT 指定 OpenGL 如何從數據緩衝區中解包圖像數據。

glTexImage2D

從緩衝區中載入數據:

// level: mip 貼圖層次(mip 貼圖稍後介紹)
void glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, void *data);

void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, void *data);

void glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, void *data);

width、height、depth 最好使用 2 的次冪,以獲取更高的性能。

glTexParameterf

// target: GL_TEXTURE_2D、GL_TEXTURE_3D 等
// pname: 參數名
// param: 參數值
void glTexParameterf(GLenum target, GLenum pname, GLfloat param);
void glTexParameteri(GLenum target, GLenum pname, GLint param);
void glTexParameterfv(GLenum target, GLenum pname, GLfloat *params);
void glTexParameteriv(GLenum target, GLenum pname, GLint *params);

基本過濾

紋理圖像中的紋理單元和屏幕上的像素幾乎從來不會形成一對一的對應關係,因此,當紋理圖像應用於幾乎圖形的表面時,紋理圖像不是被拉伸就是被收縮。根據一個被拉伸/被收縮的紋理貼圖計算顏色片段的過程稱爲紋理過濾,可以同時設置放大和縮小過濾器,即 GL_TEXTURE_MAG_FILTER 和 GL_TEXTURE_MIN_FILTER,取值爲 GL_NEAREST 或 GL_LINEAR,分別對應最鄰近過濾和線性過濾。確保總是爲 GL_TEXTURE_MIN_FILTER 選擇這兩種過濾器的一種,因爲默認的過濾器不適用於 mip 貼圖(參考下面)。

最鄰近過濾是最簡單、最快速的過濾方法,最顯著的特性是當紋理被拉伸到特別大時所出現的大片斑駁狀像素。

線性過濾不是把最鄰近的紋理單元應用到紋理中,而是把這個紋理周圍的紋理單元的加權平均值應用到這個紋理座標上。線性過濾最顯著的特徵是當紋理被拉伸時所出現的“失真”圖形。

紋理過濾

和最鄰近過濾相比,線性過濾會帶來一些額外的開銷,但在如今的高速硬件上,相比它所實現的效果,這些開銷幾乎可以忽略不計。

紋理環繞

正常情況下,紋理座標範圍爲 0.0 ~ 1.0,如果座標在這個範圍之外,OpenGL 則根據紋理環繞模式處理這個問題,可設置參數有 GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, 或 GL_TEXTURE_WRAP_R,可選值有:GL_REPEAT, GL_CLAMP, GL_CLAMP_TO_EDGE, 或 GL_CLAMP_TO_BORDER。

環繞方式 描述
GL_REPEAT 對紋理的默認行爲,重複紋理圖像
GL_MIRRORED_REPEAT 和GL_REPEAT一樣,但每次重複圖片是鏡像放置的
GL_CLAMP_TO_EDGE 紋理座標會被約束在 0 到 1 之間,超出的部分會重複紋理座標的邊緣,產生一種邊緣被拉伸的效果
GL_CLAMP_TO_BORDER 超出的座標爲用戶指定的邊緣顏色

紋理環繞

Mip 貼圖

Mip 貼圖是一種強大的紋理技巧,不僅可以提高渲染性能,還可以改善場景的顯示質量。常用於處理兩個問題:

1) 閃爍(鋸齒假影),當被渲染物體的表面和它所應用的紋理圖像相比顯得非常小時,就會出現這個問題

2) 性能,必須加載大量的紋理內存對紋理圖像進行過濾處理,但屏幕上時機顯示的只是很少的一部分片段

使用 Mip 貼圖,本質上,不是把單個圖像加載到紋理狀態中,而是把一系列從最大到最小的圖像加載到單個“Mip貼圖”紋理狀態,然後,OpenGL 使用一組新的過濾模式,爲一個特定的幾何圖形選擇具有最佳過濾效果的紋理,在付出一些額外內存的代價之後,不進可以消除閃爍效果,還可以大大降低對遠處物體進行紋理貼圖時所需要的內存及處理開銷,並且還可以維護一組具有更高分辨率的可用紋理。

Mip 貼圖紋理由一系列紋理圖像組成,每個圖像大小在每個軸的方向上都縮小一半,即爲原來的 1/4,知道最後一個圖像大小是 1x1 爲止(一個軸上的大小到達 1 時,不會再減半),使用一組正方形的 Mip 貼圖所要求的內存比不使用 Mip 貼圖要多出三分之一。

Mip 貼圖紋理

Mip 貼圖層是通過 glTexImage 函數加載的,level 參數指定了圖像數據用於哪個 Mip 層,第一層爲 0,如果 Mip 貼圖未被使用,那麼就只有第 0 層會被加載,默認情況下,所有的 Mip 層都必須加載,但可以設置需要使用的基層和最大層:

// 只加載第 0 層至第 4 層
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4);

還可以使用 GL_TEXTURE_MIN_LOD、GL_TEXTURE_MAX_LOD 限制已加載的 Mip 層的使用範圍。

紋理壓縮

用於減小存儲紋理圖像所需的內存。壓縮格式有:

Compressed Format Base Internal Format
GL_COMPRESSED_RGB GL_RGB
GL_COMPRESSED_RGBA GL_RGBA
GL_COMPRESSED_SRGB GL_RGB
GL_COMPRESSED_SRGB_ALPHA GL_RGBA
GL_COMPRESSED_RED GL_RED
GL_COMPRESSED_RG GL_RG (Red Green)

判斷紋理是否被成功壓縮:

GLint compFlag;
. . .
// 成功返回 1,否則返回 0
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compFlag);

使用 glHint 指定壓縮算法(最快速度或最佳質量):

glHint(GL_TEXTURE_COMPRESSION_HINT, GL_FASTEST);
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_DONT_CARE);

可以使用原始壓縮數據,從而極大地提到紋理的加載速度(這個技巧只有在完全遵循標準的硬件實現中才是可行的):

void glCompressedTexImage1D(GLenum target, GLint level, GLenum internalFormat,
GLsizei width, GLint border, GLsizei imageSize, void *data);

void glCompressedTexImage2D(GLenum target, GLint level, GLenum internalFormat,
GLsizei width, GLsizei height, GLint border, GLsizei imageSize, void *data);

void glCompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat,
GLsizei width, GLsizei height, GLsizei depth, GLint border, Glsizei imageSize, GLvoid *data);

對於有些紋理,壓縮可能會導致圖像質量的損失,而具有大量細節的紋理在視覺上幾乎與未壓縮的源紋理相同,紋理壓縮方法的選擇可能極大地依賴於底層圖像的本質。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章