1. 程式人生 > >OpenGLEs 紋理的基礎知識

OpenGLEs 紋理的基礎知識

紋理的基礎知識

2D 紋理

2d紋理是OpenGlES中最基礎和普遍的一種紋理結構。一個2d紋理,就是圖片的資料的一個二維陣列。紋理中每一個獨立的資料單元被稱為texels(“texture pixels”的縮寫)。OpenGLES中紋理影象資料可變被許多種不同的基本格式描繪。

根據影象的基本型別和影象的資料型別,決定了影象中的每一個texel。

當渲染2D紋理是,紋理的座標將成為索引。2D紋理的座標軸別是(s,t)或者是(u,v)。這個座標軸是規範化的,即座標的大小在0.0~1.0之間。取紋理影象的左下角為座標原點(0.0,0.0)

座標超過[0.0,1.0]的範圍是允許的,而對與超出範圍的內容的操作,取決於紋理的包裝方式(wrapping mode)

立方體貼圖紋理(Cubemap Texttures

OpenGL ES 3.0 支援立方體貼圖紋理。最基本的,一個立方體紋理由6個獨立的2D紋理面組成。每一個2D紋理都是立方體的一個平面。雖然立方體貼圖在3D渲染中有了多元化的進步,但是最普遍的還是被用在環境貼圖中。典型的,在環境貼圖中,將一臺攝像機放置場景的中心點,拍攝6個方位的影象並儲存。

立體貼圖中的Texels通過使用一個3D的向量(s,t,r)來定位。定位時,先根據r確定是6個平面的哪一個平面,在根據(s,t)確定平面上的一點。

立體貼圖紋理的每一個平面都必須是正方形的。

3D紋理

3D紋理可以看做是2D紋理多個切片的一個數組。將2D紋理當做的一個面,將面疊加起來,便是一個立體。這個立體就是3D紋理。3D紋理使用一個3元座標(s,t,r),r座標決定哪一個切面,(s,t)決定在這個切面中的座標。

2D紋理陣列

2D紋理陣列和3D紋理很類似,當時一個3D紋理是表示一張影象,而一個2D紋理陣列,則是表示一組2D影象形成的動畫序列。

紋理物件和載入紋理

一個紋理物件是一個儲存將要被渲染的紋理資料的容器,包括了圖片資料,過濾方式和封裝方式。在OpenGLES中,一個紋理物件通過一個非負整數來標識,這個數是這個紋理物件的一個控制代碼。生產紋理的函式是glGenTextures

void glGenTextures(GLsizei n, GLuint *textures)

n   指定生產的紋理物件的數量
textures 一個非負整數陣列,用了儲存n個紋理物件的ID

通過glGenTextures

建立了空的容器,將會被拿去載入紋理資料和引數。當程式不在需要時,需要將紋理物件刪除。通過glDeleteTextures實現。

void glDeleteTextures(GLsizei n, Gluint *textures)
n   指定刪除的紋理物件的數量
textures 一個非負整數陣列,用了儲存n個紋理物件的ID

當一個紋理物件的id被glGenTextures建立後,程式必須將該紋理進行繫結。只有該紋理被繫結後,後續的操作例如glTexImage2DglTexParameter才能影響到繫結的紋理物件。執行繫結功能的函式是glBindTexture

void glBindTexture(GLenum target, GLuint texture)

target  將紋理物件和目標GL_TEXTURE_2D,              GL_TEXTURE_3D,GL_TEXTURE_2D_ARRAY或GL_TEXTURE_CUBE_MAP進行綁            定
texture     進行繫結的紋理的控制代碼

一旦紋理被繫結到一個指定的紋理目標,這個紋理物件將會一直維持對這個目標的繫結直到被刪除。在建立一個紋理目標並繫結後,下一步便是使用紋理去載入圖片資料。一個基本的載入2D和立體貼圖紋理的函式是glTexImage2D。另外,在OpenGl ES 3.0 中有幾個可以替代的函式,這些函式被用於指定的2D紋理,包括使用不可變紋理glTexStorage2DglTexSubImage2D的結合。

void glTextImage2D (GLenum target, GLint level,
                        GLenum internalFormat, GLsizei width,
                        GLsizei height, GLint border,
                        Glenum formate, Glenum type
                        const void * pixels)

target  指定紋理的目標,包括GL_TEXTURE_2D或立體貼圖面目標中的一個(GL_TEXTURE_CUBE_MAP_POSITIVE_X,GL_TEXTURE_CUBE_MAP_NEGATIVE_X,等等).
level       指定哪一個級別的mip將被載入。第一個等級被指定為0
internalFormat 紋理儲存的內部格式。包括了無大小限制的內部格式和有大小限制的格式。無大小內部格式包括 GL_RGBA, GL_RGB, GL_LUMINANCE_ALPHA GL_LUMINANCE, GL_ALPHA 有大小的內部格式包括 GL_R8, GL_R8_SNORM, GL_R16F, GL_R32F
GL_R8UI, GL_R16UI, GL_R32UI, GL_R32I
GL_RG8, GL_RG8_SNORM, GL_RG16F, GL_RG32F GL_RG8UI, GL_RG8I, GL_RG16UI, GL_RG32UI GL_RG32I, GL_RGB8, GL_SRGB8, GL_RGB565 GL_RGB8_SNORM, GL_R11F_G11F_B10F
GL_RGB9_E5, GL_RGB16F, GL_RGB32F
GL_RGB8UI, GL_RGB16UI, GL_RGB16I, GL_RGB32UI GL_RGB32I, GL_RGBA8, GL_SRGB8_ALPHA8 GL_RGBA8_SNORM, GL_RGB5_A1, GL_RGBA4 GL_RGB10_A2, GL_RGBA16F, GL_RGBA32F GL_RGBA8UI, GL_RGBA8I, GL_RGB10_A2UI GL_RGBA16UI, GL_RGBA16I, GL_RGBA32I GL_RGBA32UI, GL_DEPTH_COMPONENT16 GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32F GL_DEPTH24_STENCIL8, GL_DEPTH24F_STENCIL8

width  圖片的畫素寬度
heigth 圖片的畫素高度
border 必須為0
formate 輸入紋理資料的格式,可能是GL_RED 
GL_RED_INTEGER
GL_RG
GL_RG_INTEGER
GL_RGB
GL_RGB_INTEGER
GL_RGBA
GL_RGBA_INTEGER
GL_DEPTH_COMPONENT
GL_DEPTH_STENCIL
GL_LUMINANCE_ALPHA
GL_ALPHA

type 輸入的畫素資料的型別;可能是
                  GL_UNSIGNED_BYTE
               GL_BYTE
               GL_UNSIGNED_SHORT
               GL_SHORT
               GL_UNSIGNED_INT
               GL_INT
               GL_HALF_FLOAT
               GL_FLOAT
               GL_UNSIGNED_SHORT_5_6_5
               GL_UNSIGNED_SHORT_4_4_4_4
               GL_UNSIGNED_SHORT_5_5_5_1
               GL_UNSIGNED_INT_2_10_10_10_REV
               GL_UNSIGNED_INT_10F_11F_11F_REV
               GL_UNSIGNED_INT_5_9_9_9_REV
               GL_UNSIGNED_INT_24_8
               GL_FLOAT_32_UNSIGNED_INT_24_8_REV
               GL_UNSIGNED_SHORT_5_6_5

pixels 包含了圖片的畫素資料。這個資料必須包含了(width*height)個畫素,每個畫素根據指定的格式和型別佔用一定的位元組,畫素行必須用GL_UNPACK_ALIGNMENT設定glPixelStorei設定對齊

和紋理載入相關的函式,還有一個glPixelStorei,該函式決定了畫素資料儲存方式。


void glPixelStorei(GLenum pname , GLint param)

pname 指定畫素儲存型別。下面的可選引數將影響當glTexImage2D, glTexImage3D, glTexSubImage2D, 和 glTexSubImage3D被呼叫時,資料如何從記憶體中被解包:GL_UNPACK_ROW_LENGTH, GL_UNPACK_IMAGE_HEIGHT, GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_IMAGES, GL_UNPACK_ALIGNMENT
    下列可選引數將影響glReadPixels被呼叫時,資料如何被打包讀入記憶體:GL_PACK_ROW_LENGTH, GL_PACK_IMAGE_HEIGHT, GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS, GL_PACK_SKIP_IMAGES, GL_PACK_ALIGNMENT
    
param 為打包或解包指定的整形數值。

GL_PACK_xxxxx對紋理的更新沒有任何影響。實際上,除了GL_UNPACK_ALIGNMENT,其他的可選選項很少被使用

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

意味著每一個畫素行從位元組邊界開始,換而言之,資料被緊密打包。該值的預設值是4,表示畫素行被認定從4位元組的邊界開始。

紋理過濾和mip貼圖

紋理座標用於生成一個2D索引,從紋理貼圖中讀取。當縮小和放大過濾器設定為GL_NEAREST時,就會發生這樣的情況:一個texels將在提供的紋理座標位置上讀取。這樣稱作點取樣或最近取樣。

然而,最近取樣可能產生視覺偽像。一個偽像的產生是因為一個影象在螢幕中變小,導致畫素之間的紋理座標產生了巨大跳躍。這造成了從一個張巨大的紋理圖中讀取了少量的採用點,從而導致了鋸齒偽像的產生。

解決這一問題的方法是,採用mip貼圖。所謂的mip貼圖,就是通過當前的螢幕解析度,生成較低的螢幕解析度的圖片,形成一個圖片鏈。比如現在有一張64*64的圖片,採用mip貼圖,會生成32*32,16*16,8*8,4*4,2*2,1*1,等低解析度的圖片。這些圖片中,最原始的被稱為0級層。每低一個低階的層,都是由上等級的圖片生成的,畫素合併規則是,將每個圖層的2*2的畫素點相加取平均值,作為下一層的一個畫素點。

縮小過濾發生在螢幕中的多邊形小於紋理大大小時。放大過濾發生在螢幕中多邊大於紋理的大小時。對於放大過濾,mip貼圖沒有影響。對於縮小過濾,mip貼圖可以保證畫素的平滑

通過glTexParameter[i|f][v]來設定紋理的的引數

void glTexParameteri(GLenum target, GLenum pname, GLint param)
void glTexParameteriv(GLenum target, GLenum pname, const GLint *params)
void glTexParameterf(GLenum target, GLenum pname, GLfloat param)
void glTexParameterfv(GLenum target, GLenum pname, const GLfloat *params)

target 紋理目標
pname 進行設定的紋理屬性,可以包括
        GL_TEXTURE_BASE_LEVEL
GL_TEXTURE_COMPARE_FUNC
GL_TEXTURE_COMPARE_MODE
GL_TEXTURE_MIN_FILTER
GL_TEXTURE_MAG_FILTER
GL_TEXTURE_MIN_LOD
GL_TEXTURE_MAX_LOD
GL_TEXTURE_MAX_LEVEL
GL_TEXTURE_SWIZZLE_R
GL_TEXTURE_SWIZZLE_G
GL_TEXTURE_SWIZZLE_B
GL_TEXTURE_SWIZZLE_A
GL_TEXTURE_WRAP_S
GL_TEXTURE_WRAP_T
GL_TEXTURE_WRAP_R

params 指定屬性的引數

對於放大過濾GL_TEXTURE_MAG_FILTER,可以設定為GL_NEAREST或者是GL_LINEAR
對於縮小過濾GL_TEXTURE_MIN_FILTER,可以設定的值如下

  • GL_NEAREST 表示根據紋理座標,獲取最近的一個紋理樣本
  • GL_LINEAR 根據紋理座標,採用2次取樣方法,獲取紋理樣本
  • GL_NEAREST_MIPMAP_NEAREST 根據紋理座標,從最近的mip貼圖層中獲取一個點樣本
  • GL_NEAREST_MIPMAP_LINEAR 根據紋理座標,獲取2個最近的mip圖層的取樣點,並取2個樣本之間的插值
  • GL_LINEAR_MIPMAP_NEAREST 根據紋理座標,採用二次線性插值從最近的mip層中獲取一個點樣本
  • GL_LINEAR_MIPMAP_LINEAR 根據紋理座標,獲取最近2個mip圖層中採用二次線性獲取的採用點,並取2個採用點之間的插值
  •  

自動生成Mip貼圖

OpenGL ES 3.0 中提供了一個自動生成mip貼圖的方法glGenerateMipmap;

紋理座標的封裝

紋理封裝方式(Texture wrap modes)被用於指定超出紋理座標限定範圍[0.0,1.0]的行為。通過glTexParameter[i|f][v]可以設定紋理封裝方式。這些mode可以獨立設定s座標軸,t座標軸和r座標軸。例如GL_TEXTURE_WRAP_Smode定義了s座標超出[0.0,1.0]的行為。同樣的GL_TEXTURE_WRAP_TGL_TEXTURE_WRAP_R分別定義了t座標和r座標的超出[0.0,1.0]的行為。

在OpenGL ES 3.0 中,有3中mode值可以設定。

GL_REPEAT      重複紋理
GL_CLAMP_TO_EDGE  限定讀取紋理的邊緣
GL_MIRRORED_REPEAT 重複紋理和映象

紋理的調配

紋理調配控制輸入的R,RG,RGB或RBGA紋理中的顏色分量在著色器中讀取時如何對映到分量。通過呼叫glTexParameter[i|f][v]來設定紋理調配,key可以設定為GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G, GL_TEXTURE_ SWIZZLE_B, 或 GL_TEXTURE_SWIZZLE_A,而value可能分別從R,G,B,A分量讀取的GL_RED, GL_GREEN, GL_BLUE, 或 GL_ALPHA。也可以通過GL_ZERO和GL_ONE將值設定為0或1

紋理細節級別

在一些程式中,在所有的紋理mip貼圖都可以使用前就能夠開始在螢幕上進行顯示的功能是很有用的。比如GPS的程式,可以先顯示低等級的mip貼圖,等到所有的mip貼圖都下載後,在顯示高清的mip貼圖。可以通過glTexParameter[i|f][v]中的GL_TEXTURE_BASE_LEVEL來設定可以被使用的最大mip貼圖等級。預設是0。同樣的,GL_TEXTURE_MAX_LEVEL設定了最小的mip貼圖等級,預設是1000。

為了選擇用於渲染的mip貼圖級別,OpenGL ES自動計算一個細節級別(LOD)的值。這個浮點值決定了哪一mip貼圖級別被篩選出來。一個程式能夠控制LOD值的最大和最小值。通過設定GL_TEXTURE_MIN_LODGL_TEXTURE_MAX_LOD

在著色器中使用紋理

// Vertex shader
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
void main()
{
   gl_Position = a_position;
   v_texCoord = a_texCoord;
}
// Fragment shader
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_texture;
void main() {
   outColor = texture( s_texture, v_texCoord );
}

碎片著色器中的texture函式,表示從指定的紋理單元中,根據輸入的座標,映射出紋理中相應的vec4顏色。

vec4 texture(sampler2D sampler, vec2 coord[, float bias])

sampler 取樣器,指定的紋理單元的編號

coord  2D紋理的座標
bias   可選引數,提供用於紋理讀取的mip貼圖偏值。這執行著色器明確偏置用於mip貼圖選擇的LOD計算值

在sampler引數需要傳入的紋理單元編號前,需要先啟用一個紋理單元,並將紋理繫結到該紋理單元上。使用glActiveTexture進行啟用

void glActiveTexture(GLenum texture)
texture  啟用紋理單元,GL_TEXTURE0, GL_TEXTURE1, ... , GL_TEXTURE31分別代表紋理單元0到31

使用glActiveTexture啟用一個紋理單元后,便可使用glBindTexture將指定的紋理繫結到該紋理單元上。具體程式碼如下:

// Get the sampler locations
userData->samplerLoc = glGetUniformLocation(
                              userData->programObject,
                              “s_texture”);
// ...
// Bind the texture
glActiveTexture(GL_TEXTURE0); 
glBindTexture(GL_TEXTURE_2D, userData->textureId);

// Set the sampler texture unit to 0
glUniformli(userData->samplerLoc, 0);

載入立體貼圖,3D紋理和2D紋理陣列,也是同樣的操作,對應的texture分別為

vec4 texture(samplerCube sampler, vec3 coord[, float bias])
vec4 texture(sampler3D sampler, vec3 coord[, float bias])
vec4 texture(sampler2DArray sampler, vec3 coord[, float bias])

壓縮紋理

OpenGL ES 3.0 支援2中壓縮演算法-EAC和ETC2。EAC版本用於壓縮1到2頻道的資料。ETC2版本用於壓縮3到4頻道的資料。使用glCompressedTexImage2D可以讀取壓縮的2D紋理和立體貼圖紋理。使用glCompressedTexImage3D讀取已壓縮的2D紋理陣列。注意:ETC2/EAC不支援3D紋理壓縮,但是使用glCompressedTexImage3D可以載入指定供應商的3D紋理壓縮格式

void glCompressedTexImage2D(GLenum target, GLint level, 
                                GLenum internalFormat,
                                GLsizei width, GLsizei height,
                                GLint border, GLsizei imageSize, 
                                const void *data)
void glCompressedTexImage3D(GLenum target, GLint level, 
                                GLenum internalFormat,
                                GLsizei width, GLsizei height,
                                GLsizei depth, GLint border, 
                                GLsizei imageSize,
                                const void *data)
                                
target  指定紋理目標
level     指定載入的mip級別,預設是0
internalFormat   紋理的內部儲存格式
width           圖片的畫素寬度
height          圖片的畫素高度
depth           圖片的畫素深度
border          必須設定為0
imageSize       圖片的位元組大小
data            壓縮後的圖片資料

可以通過glGetIntegerv傳入GL_COMPRESSED_TEXTURE_FORMATS進行查詢支援的壓縮格式,該函式會返回一個GLenum的陣列。

紋理子影象規範

在使用glTexImage2D更新一張紋理圖片後,可能需要更新圖片的一部分。這時候可以使用glTexSubImage2D來來自2D紋理影象的一部分。

void glTexSubImage2D (GLenum target, GLint level,
                            GLInt xoffset, GLint yoffset
                            GLsizei width, GLsizei height,
                            GLenum format, Glenum type,
                            const void *pixels)
                            
target 紋理目標
level  mip貼圖級別
xoffset 開始進行更新的x序列號
yoffset 開始進行更新的y序列號
width     進行更新的影象子區域的寬度
heigth   進行更新的影象子區域的高度
formate  輸入紋理資料的格式
type    輸入畫素資料的型別
pixels  包含影象子區域的畫素資料

這個函式將會更新區域(xoffset,yoffset)到(xoffset+width-1,yoffset+height-1)。注意:使用這個函式,紋理必須依據被指定,而且子圖片的範圍必須在指定的紋理圖片之內,pixels的對其方式必須被指定為GL_UNPACK_ALIGNMENT

同樣的原理,可以使用glCompressedTexSubImage2D更新壓縮過的圖片,使用它glTexSubImage3D更新3D紋理或2D紋理陣列,使用glCompressedTexSubImage3D更新壓縮過的2D紋理陣列。

從顏色緩衝區複製紋理資料

glReadBuffer指定被複制的顏色快取區。然後通過glCopyTexImage2D, glCopyTexSubImage2D,和 glCopyTexSubImage3D從指定的顏色緩衝區張讀取紋理資料

取樣器物件

為了減少大量紋理上使用相同的設定的開銷。引入了取樣器物件,將取樣器狀態與紋理狀態分離。簡而言之,所有可用glTexParameter[i|f][v]設定都可以對取樣器物件進行設定。可以在一次函式呼叫中與紋理單元繫結使用。

glGenSamplers用於建立採用器,使用glDeleteSamplers刪除取樣器。使用glBindSampler將取樣器與紋理單元進行繫結。使用glSamplerParameter[f|i][v]設定取樣器引數。

不可變紋理

由於應用程式使用glTexImage2DglTexImage3D等函式獨立指定紋理的每個mip貼圖的級別。這導致了驅動程式無法在繪圖之前確定紋理是否完全指定。也就是說,它必須檢查每一個mip貼圖級別或者子影象的格式是否相符、每一個級別的大小是否正確以及是否有足夠的記憶體。這種繪圖時檢查可能代價很高,而使用不可變紋理可以避免這種情形。

不可變紋理的思路很簡單:程式在載入資料之前指定紋理的格式和大小。OpenGL ES的驅動可以提前進行一致性和記憶體檢查。一旦紋理不可變,它的格式和尺寸也就不能改變。然而程式仍然可以通過使用glTexSubImage2D, glTexSubImage3D,或 glGenerateMipMap來載入紋理。

為了建立不可變紋理,程式必須先使用glBindTexture綁定當前紋理,在呼叫glTexStorage2DglTexStorage3D建立不可變的儲存空間。



作者:Zsj_Sky
連結:https://www.jianshu.com/p/45181657e339
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。