Android OpenGL ES 2.0 手把手教學(6)- 紋理

你們好,下面和大學一塊兒學習紋理,在個人github上有一個項目OpenGLES2.0SamplesForAndroid,我會不斷地編寫學習樣例,文章和代碼同步更新,歡迎關注,連接:github.com/kenneycode/…git

在前面的例子中,咱們渲染的都是一些比較簡單的顏色,若是咱們要渲染一張圖片,該怎麼作呢?這就須要用到紋理,咱們須要建立一個紋理並把圖片加載到紋理中,而後在fragment shader中對紋理進行採樣,從而將紋理渲染出來。github

咱們先經過glGenTextures建立一個紋理,而後設置一些參數,這裏獲得的紋理只是一個idbash

// 建立圖片紋理
// Create texture
val textures = IntArray(1)
GLES20.glGenTextures(textures.size, textures, 0)
val imageTexture = textures[0]

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imageTexture)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
複製代碼

建立好紋理以後,它仍是空的,咱們要給這個紋理填充內容,咱們先將一張圖片解碼成bitmap,並將像素數據copy到一個ByteBuffer中,由於將bitmp加載到紋理中的方法接受的是一個ByteBufferpost

val bitmap = Util.decodeBitmapFromAssets("image_0.jpg")
val b = ByteBuffer.allocate(bitmap.width * bitmap.height * 4)
bitmap.copyPixelsToBuffer(b)
b.position(0)
複製代碼

下面經過glTexImage2D方法將上面獲得的ByteBuffer加載到紋理中:學習

GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 
                    0, 
                    GLES20.GL_RGBA, 
                    bitmap.width, 
                    bitmap.height, 
                    0, 
                    GLES20.GL_RGBA, 
                    GLES20.GL_UNSIGNED_BYTE, 
                    b)
複製代碼

這時紋理中才真正有了內容,接下來須要將紋理傳遞給fragment shader進行採樣,從而渲染出來,咱們先來看看如何在fragment shader中使用紋理:ui

// vertex shader
precision mediump float;
attribute vec4 a_position;
attribute vec2 a_textureCoordinate;
varying vec2 v_textureCoordinate;
void main() {
    v_textureCoordinate = a_textureCoordinate;
    gl_Position = a_position;
}

// fragment shader
precision mediump float;
varying vec2 v_textureCoordinate;
uniform sampler2D u_texture;
void main() {
    gl_FragColor = texture2D(u_texture, v_textureCoordinate);
}
複製代碼

關鍵點是uniform sampler2D u_texture;這句,它聲明瞭一個2D的採樣器用於採樣紋理。spa

varying vec2 v_textureCoordinate;則是從vertex shader中傳遞過來的一個通過插值的紋理座標值,關於varying變量,在以前的一個篇文章《Android OpenGL ES 2.0 手把手教學(4)- 片斷着色器 fragment shader》中有涉及到。3d

gl_FragColor = texture2D(u_texture, v_textureCoordinate);就是從紋理中採樣出v_textureCoordinate座標所對應的顏色做爲fragment shader的輸出,咱們能夠看到,fragment shader的輸出實際上就是一個顏色,在以前的文章中,是咱們本身去控制這個顏色,而當這個顏色若是是來自紋理的採樣,那最終渲染出來就是紋理的樣子。code

如今,紋理和shader都準備好了,若是把它們聯繫越來呢?首先須要像以前同樣先獲取shader中紋理變量的locationorm

val uTextureLocation = GLES20.glGetAttribLocation(programId, "u_texture")
複製代碼

而後給這個location指定對應哪一個紋理單元,這裏咱們使用0號紋理單元:

GLES20.glUniform1i(uTextureLocation, 0)
複製代碼

看到這裏,可能有些朋友有點懵,紋理單元又是個什麼東西?是這樣的,紋理單元能夠想像成是一種相似寄存器的東西,在OpenGL使用紋理前,咱們先要把紋理放到某個紋理單元上,以後的操做OpenGL就會去咱們指定的紋理單元上去取對應的紋理。

咱們剛纔讓location對應0號紋理單元,可是咱們好像沒並無哪裏說咱們把紋理放在了0號紋理單元,這是怎麼回事呢?由於默認狀況下,OpenGL是使用0號紋理單元的,咱們由於沒有更改過使用的紋理單元,所以默認就是0號了,咱們若是想使用其它紋理單元,能夠經過glActiveTexture來指定,例如:

GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
複製代碼

就會指定使用1號紋理單元,若是咱們的例子改成使用1號紋理單元,那麼uTextureLocation就要相應地改成讓它對應1號紋理單元:

GLES20.glUniform1i(uTextureLocation, 1)
複製代碼

下面我看來看看頂點座標和紋理座標:

private val vertexData = floatArrayOf(-1f, -1f, -1f, 1f, 1f, 1f, -1f, -1f, 1f, 1f, 1f, -1f)
private val textureCoordinateData = floatArrayOf(0f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, 1f, 0f, 1f, 1f)
複製代碼

如何給shader傳遞頂點座標,方法你們如今應該比較熟悉了,其實紋理座標的傳遞也幾乎是同樣的,只是值不同而已。

以前提到過,頂點座標的做用是告訴OpenGL要渲染到什麼區域,頂點座標的座標系是每一個軸的範圍都是-1~1,其實也能夠超出-11,只不過超出就不在渲染的範圍內了,就看不見了,並非就算錯誤,頂點座標系的原點在中間。

紋理座標的座標原點在左下角,每一個軸的範圍是0~1,一樣的也能夠超出01,超出以後的表現會根據設置的紋理參數有所不一樣。

在這個例子中,咱們使用GL_TRIANGLES的繪製模式進行渲染,關於渲染模式,能夠參考個人上一篇文章《Android OpenGL ES 2.0 手把手教學(5)- 繪製模式》,在這種繪製模式下,每三個點構成一個獨立三形,所以紋理座標構成的兩個三角形會對應渲染到頂點座標指定的兩個三角形中,咱們來看一下效果:

細心的朋友可能會發現,咱們的頂點座標和紋理座標上下是顛倒的,好比頂點座標(-1,-1)對應紋理座標(0,1),也就是渲染區域的左下角對應紋理的左上角,這樣同樣,渲染出來的圖像不是應該倒過來嗎?但咱們看到的效果倒是正確的。

這是由於咱們的紋理來自於bitmap,而bitmap的座標原點是左上角,也就是它和OpenGL中的紋理座標系是上下顛倒的,因此咱們把紋理座標再顛倒一次,就是正的了。

咱們剛纔建立紋理時給紋理設置了幾個參數,咱們來看一下:

GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
複製代碼

其中GL_TEXTURE_MIN_FILTERGL_TEXTURE_MAG_FILTER是紋理過濾參數,指定當咱們渲染出來的紋理比原來的紋理小或者大時要如何處理,這裏咱們使用了GL_LINEAR的方式,這是一種線性插值的方式,獲得的結果會更平滑,除此以外,還有其它不少選項,還有另外一個比較經常使用的是GL_NEAREST,它會選擇和它最近的像素,獲得的結果鋸齒感比GL_LINEAR要大,咱們將頂點座標擴大5倍,即變成-5~5,來獲得放大的效果,能夠來看看放大時的這兩種效果:

GL_TEXTURE_WRAP_SGL_TEXTURE_WRAP_T則是指定紋理座標超出了紋理範圍以後,該如何填充,比較經常使用的有GL_CLAMP_TO_EDGEGL_REPEAT,它們的效果分別是填充邊緣像素和重複這個紋理,咱們將紋理座標改成0~3,看看效果:

以上就是關於紋理的一些基礎知識,代碼在我github的OpenGLES2.0SamplesForAndroid項目中,本文對應的是SampleTexture,項目連接:github.com/kenneycode/…

感謝閱讀!

相關文章
相關標籤/搜索