Opengl?ES之紋理貼圖使用示例
正文
紋理可以理解為一個二維數(shù)組,它可以存儲大量的數(shù)據(jù),這些數(shù)據(jù)可以發(fā)送到著色器上。一般情況下我們所說的紋理是表示一副2D圖,此時紋理存儲的數(shù)據(jù)就是這個圖的像素數(shù)據(jù)。
所謂的紋理貼圖,就是使用Opengl將這個紋理數(shù)據(jù)渲染出來,這個過程有點像裝修工人給墻體貼瓷磚,而瓷磚好比作紋理。
紋理坐標
如果為了將一副紋理圖貼到Opengl繪制的一個矩形上,那么就需要解決一個問題,如何知道矩形的具體某個點對應(yīng)紋理圖的某個點呢?為了解決這個問題就引出了紋理坐標, 通過矩形的頂點坐標與紋理坐標關(guān)聯(lián),這樣就明確了每個頂點應(yīng)該顯示紋理圖的那部分像素數(shù)據(jù)。
紋理坐標在x和y軸上,范圍為0到1之間。使用紋理坐標獲取紋理顏色叫做采樣(Sampling)。紋理坐標起始于(0, 0),也就是紋理圖片的左下角,終始于(1, 1),即紋理圖片的右上角,如下圖所示:
紋理環(huán)繞
紋理坐標的值介于0到1之間,如果我們把紋理坐標設(shè)置成大于1那么會發(fā)生什么呢?OpenGL默認的行為是重復(fù)這個紋理圖像,那么利用這個默認的特性我們能做些什么呢?那么比較火的抖音四分屏、九分屏濾鏡不就是可以用這個特性巧妙地實現(xiàn)嗎。
以下是通過改變紋理坐標實現(xiàn)四分屏和九分屏的一個小技巧:
//?4分屏 const?static?GLfloat?TEXTURE_COORD[]?=?{ ????????2.0f,2.0f,?//?右下 ????????2.0f,0.0f,?//?右上 ????????0.0f,2.0f,?//?左下 ????????0.0f,0.0f?//?左上 }; //?九分屏 const?static?GLfloat?TEXTURE_COORD[]?=?{ ????????3.0f,3.0f,?//?右下 ????????3.0f,0.0f,?//?右上 ????????0.0f,3.0f,?//?左下 ????????0.0f,0.0f?//?左上 };
當(dāng)然,當(dāng)紋理坐標超過1這個范圍時,Opengl也提供了其他的選擇,例如:
GL_REPEAT?//?對紋理的默認行為。重復(fù)紋理圖像。 GL_MIRRORED_REPEAT?//和GL_REPEAT一樣,但每次重復(fù)圖片是鏡像放置的。 GL_CLAMP_TO_EDGE?//紋理坐標會被約束在0到1之間,超出的部分會重復(fù)紋理坐標的邊緣,產(chǎn)生一種邊緣被拉伸的效果。 GL_CLAMP_TO_BORDER?//超出的坐標為用戶指定的邊緣顏色。
紋理環(huán)繞效果圖
以上特性可以通過函數(shù)glTexParameteri
設(shè)置:
glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_WRAP_S,?GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_WRAP_T,?GL_MIRRORED_REPEAT);
紋理過濾
紋理過濾實際就是紋理在放大縮小的過程中像素的處理方式。其中在Opengl ES常用的兩種紋理過濾方式是GL_NEAREST(鄰近過濾)和GL_LINEAR(也叫線性過濾)。
- GL_NEAREST是OpenGL默認的紋理過濾方式。當(dāng)設(shè)置為GL_NEAREST的時候,OpenGL會選擇中心點最接近紋理坐標的那個像素。
- GL_LINEAR(也叫線性過濾,(Bi)linear Filtering)它會基于紋理坐標附近的紋理像素,計算出一個插值,近似出這些紋理像素之間的顏色。一個紋理像素的中心距離紋理坐標越近,那么這個紋理像素的顏色對最終的樣本顏色的貢獻越大。
GL_NEAREST產(chǎn)生了顆粒狀的圖案,我們能夠清晰看到組成紋理的像素,而GL_LINEAR能夠產(chǎn)生更平滑的圖案,很難看出單個的紋理像素。
同理,紋理過濾特性也是通過函數(shù)glTexParameteri
設(shè)置:
glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_MIN_FILTER,?GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_MAG_FILTER,?GL_LINEAR);
紋理單元
紋理單元的主要目的是讓我們在著色器中可以使用多于一個的紋理。通過把紋理單元賦值給采樣器,我們可以一次綁定多個紋理,只要我們首先激活對應(yīng)的紋理單元。 例如使用Opengl ES對視頻解碼后的YUV進行渲染就需要用到紋理單元的相關(guān)知識點。
Opengl中紋理的使用
在Opengl中使用紋理主要有以下幾個步驟:
- 創(chuàng)建紋理
glGenTextures
- 激活紋理
glActiveTexture
- 綁定紋理
glBindTexture
,傳遞特定的紋理id進行綁定 - 上傳紋理數(shù)據(jù)
glTexImage2D
- 解除紋理綁定,
glBindTexture
,傳遞0進行解除綁定
紋理坐標映射關(guān)系
在了解紋理貼圖之前我們先回顧一下三個坐標系統(tǒng),分別是紋理坐標系統(tǒng)、手機屏幕坐標系統(tǒng)、Opengl坐標系統(tǒng)。這三個坐標系統(tǒng)的的原點各不相同,紋理坐標系統(tǒng)我們上面已經(jīng)介紹過了,這里不再重復(fù)。而手機屏幕坐標系統(tǒng)則是原點位于左上角,X軸向右為正,Y軸向下為正的坐標系統(tǒng)。 而Opengl坐標系統(tǒng)則是原點位于中心,X軸向右為正,Y軸向下為正,其值介于-1到1之間的一套坐標系統(tǒng)。
既然紋理貼圖就像裝修工人貼瓷磚一樣,那么直接將紋理坐標和Opengl的頂點坐標一一對應(yīng)起來即可,也就是如下圖:
我們按照這個映射關(guān)系建立貼圖:
//?頂點坐標,使用繪制兩個三角形組成一個矩形的形式(三角形帶) //?第一第二第三個點組成一個三角形,第二第三第四個點組成一個三角形 const?static?GLfloat?VERTICES[]?=?{ ????????0.5f,-0.5f,?//?右下 ????????0.5f,0.5f,?//?右上 ????????-0.5f,-0.5f,?//?左下 ????????-0.5f,0.5f?//?左上 }; //?紋理坐標(原點在左下角,這樣貼圖看到的會是倒置的 const?static?GLfloat?TEXTURE_COORD[]?=?{ ????????1.0f,0.0f,?//?右下 ????????1.0f,1.0f,?//?右上 ????????0.0f,0.0f,?//?左下 ????????0.0f,1.0f?//?左上 };
運行發(fā)現(xiàn)圖是貼上去了,但是看到的貼圖卻是倒置的,如下:
這是為什么呢?
因為紋理的生成是由圖片像素來生成的,而圖像的存儲是從左上角開始的,但是紋理坐標原點卻是在左下角的(筆者也不知道為什么要這么奇葩),所以就產(chǎn)生了倒置現(xiàn)象,因此正確的映射關(guān)系應(yīng)該是以圖片的左上角為原點做映射才對,而這也剛好與手機屏幕坐標系統(tǒng)匹配。
也就說正確的映射關(guān)系是需要先將以左下角為原點的紋理坐標進行倒置,然后再建立映射關(guān)系,這也是為什么有些博客說紋理坐標的原點是在左上角的原因(其實這是不對的,紋理坐標就是在圖片的左下角,說在左上角的就是一個技巧),那么紋理坐標倒置后再映射如圖: !
廢話少說,放碼過來...
#include?"TextureMapOpengl.h" #include?"../utils/Log.h" //?頂點著色器 static?const?char?*ver?=?"#version?300?es\n" ?????????????????????????"in?vec4?aPosition;\n" ?????????????????????????"in?vec2?aTexCoord;\n" ?????????????????????????"out?vec2?TexCoord;\n" ?????????????????????????"void?main()?{\n" ?????????????????????????"??TexCoord?=?aTexCoord;\n" ?????????????????????????"??gl_Position?=?aPosition;\n" ?????????????????????????"}"; //?片元著色器 static?const?char?*fragment?=?"#version?300?es\n" ??????????????????????????????"precision?mediump?float;\n" ??????????????????????????????"out?vec4?FragColor;\n" ??????????????????????????????"in?vec2?TexCoord;\n" ??????????????????????????????"uniform?sampler2D?ourTexture;\n" ??????????????????????????????"void?main()\n" ??????????????????????????????"{\n" ??????????????????????????????"????FragColor?=?texture(ourTexture,?TexCoord);\n" ??????????????????????????????"}"; //?使用繪制兩個三角形組成一個矩形的形式(三角形帶) //?第一第二第三個點組成一個三角形,第二第三第四個點組成一個三角形 const?static?GLfloat?VERTICES[]?=?{ ????????0.5f,-0.5f,?//?右下 ????????0.5f,0.5f,?//?右上 ????????-0.5f,-0.5f,?//?左下 ????????-0.5f,0.5f?//?左上 }; //?紋理坐標(原點在左下角,這樣貼圖看到的會是倒置的 //const?static?GLfloat?TEXTURE_COORD[]?=?{ //????????1.0f,0.0f,?//?右下 //????????1.0f,1.0f,?//?右上 //????????0.0f,0.0f,?//?左下 //????????0.0f,1.0f?//?左上 //}; //?貼圖紋理坐標(參考手機屏幕坐標系統(tǒng),原點在左上角) //由于對一個OpenGL紋理來說,它沒有內(nèi)在的方向性,因此我們可以使用不同的坐標把它定向到任何我們喜歡的方向上,然而大多數(shù)計算機圖像都有一個默認的方向,它們通常被規(guī)定為y軸向下,X軸向右 const?static?GLfloat?TEXTURE_COORD[]?=?{ ????????1.0f,1.0f,?//?右下 ????????1.0f,0.0f,?//?右上 ????????0.0f,1.0f,?//?左下 ????????0.0f,0.0f?//?左上 }; //?四分屏??GL_REPEAT環(huán)繞方式 //const?static?GLfloat?TEXTURE_COORD[]?=?{ //????????2.0f,2.0f,?//?右下 //????????2.0f,0.0f,?//?右上 //????????0.0f,2.0f,?//?左下 //????????0.0f,0.0f?//?左上 //}; //?九分屏?GL_REPEAT環(huán)繞方式 //const?static?GLfloat?TEXTURE_COORD[]?=?{ //????????3.0f,3.0f,?//?右下 //????????3.0f,0.0f,?//?右上 //????????0.0f,3.0f,?//?左下 //????????0.0f,0.0f?//?左上 //}; TextureMapOpengl::TextureMapOpengl():BaseOpengl()?{ ????initGlProgram(ver,fragment); ????positionHandle?=?glGetAttribLocation(program,"aPosition"); ????textureHandle?=?glGetAttribLocation(program,"aTexCoord"); ????textureSampler?=?glGetUniformLocation(program,"ourTexture"); ????LOGD("program:%d",program); ????LOGD("positionHandle:%d",positionHandle); ????LOGD("textureHandle:%d",textureHandle); ????LOGD("textureSample:%d",textureSampler); } void?TextureMapOpengl::setPixel(void?*data,?int?width,?int?height,?int?length)?{ ????LOGD("texture?setPixel"); ????glGenTextures(1,?&textureId); ????//?激活紋理,注意以下這個兩句是搭配的,glActiveTexture激活的是那個紋理,就設(shè)置的sampler2D是那個 ????//?默認是0,如果不是0的話,需要在onDraw的時候重新激活一下? //????glActiveTexture(GL_TEXTURE0); //????glUniform1i(textureSampler,?0); //?例如,一樣的 ????glActiveTexture(GL_TEXTURE2); ????glUniform1i(textureSampler,?2); ????//?綁定紋理 ????glBindTexture(GL_TEXTURE_2D,?textureId); ????//?為當(dāng)前綁定的紋理對象設(shè)置環(huán)繞、過濾方式 ????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); ????glTexImage2D(GL_TEXTURE_2D,?0,?GL_RGBA,?width,?height,?0,?GL_RGBA,?GL_UNSIGNED_BYTE,?data); ????//?生成mip貼圖 ????glGenerateMipmap(GL_TEXTURE_2D); ????glBindTexture(GL_TEXTURE_2D,?textureId); ????//?解綁定 ????glBindTexture(GL_TEXTURE_2D,?0); } void?TextureMapOpengl::onDraw()?{ ????glClearColor(0.0f,?1.0f,?0.0f,?1.0f); ????glClear(GL_COLOR_BUFFER_BIT); ????glUseProgram(program); ????//?激活紋理 ????glActiveTexture(GL_TEXTURE2); ????glUniform1i(textureSampler,?2); ????//?綁定紋理 ????glBindTexture(GL_TEXTURE_2D,?textureId); ????/** ?????*?size?幾個數(shù)字表示一個點,顯示是兩個數(shù)字表示一個點 ?????*?normalized?是否需要歸一化,不用,這里已經(jīng)歸一化了 ?????*?stride?步長,連續(xù)頂點之間的間隔,如果頂點直接是連續(xù)的,也可填0 ?????*/ ????//?啟用頂點數(shù)據(jù) ????glEnableVertexAttribArray(positionHandle); ????glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES); ????//?紋理坐標 ????glEnableVertexAttribArray(textureHandle); ????glVertexAttribPointer(textureHandle,2,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD); ????//?4個頂點繪制兩個三角形組成矩形 ?????glDrawArrays(GL_TRIANGLE_STRIP,0,4); ????glUseProgram(0); ????//?禁用頂點 ????glDisableVertexAttribArray(positionHandle); ????if(nullptr?!=?eglHelper){ ????????eglHelper->swapBuffers(); ????} ????glBindTexture(GL_TEXTURE_2D,?0); } TextureMapOpengl::~TextureMapOpengl()?{ ????LOGD("TextureMapOpengl析構(gòu)函數(shù)"); }
仔細看注釋多理解...
紋理貼圖運行結(jié)果
以上就是Opengl ES之紋理貼圖使用示例的詳細內(nèi)容,更多關(guān)于Opengl ES 紋理貼圖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android ExpandableRecyclerView使用方法詳解
這篇文章主要為大家詳細介紹了Android ExpandableRecyclerView的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08詳談Android中Matrix的set、pre、post的區(qū)別
下面小編就為大家?guī)硪黄斦凙ndroid中Matrix的set、pre、post的區(qū)別。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04Android 監(jiān)聽Notification 被清除實例代碼
本文主要介紹Android 監(jiān)聽Notification 事件,這里給大家提供實例代碼進行參考,有需要的小伙伴可以參考下2016-07-07