天天看點

OpenGL 學習系列 --- 紋理基本原理着色器操作上層代碼glBindTexture 函數綁定紋理中的值實際效果總結 & 名詞混淆點參考

作者:星隕

來源:

OpenGL 學習系列 --- 紋理

接下來探索紋理了。

紋理,簡單的了解就是一副圖像。而把一副圖像映射到圖形上的過程,叫做紋理映射。

比如有如下圖形和三角形,想要把圖形中的一部分映射到三角形上。

OpenGL 學習系列 --- 紋理基本原理着色器操作上層代碼glBindTexture 函數綁定紋理中的值實際效果總結 & 名詞混淆點參考
OpenGL 學習系列 --- 紋理基本原理着色器操作上層代碼glBindTexture 函數綁定紋理中的值實際效果總結 & 名詞混淆點參考
結果就是這樣的:
OpenGL 學習系列 --- 紋理基本原理着色器操作上層代碼glBindTexture 函數綁定紋理中的值實際效果總結 & 名詞混淆點參考
這就是紋理映射的一個小小例子。

基本原理

要注意到,OpenGL 繪制的物體是 3D 的,而紋理是 2D 的,那麼紋理映射就是将 2D 的紋理映射到 3D 的物體上,可以想象成用一張紙裹着一個物體一樣,不過要按照一定規律來。

OpenGL 中繪制的物體是有坐标系的,每個點都對應 x、y、z 坐标,而紋理也有着它的坐标,隻要 3D 物體中的每個點都對應了 2D 紋理中的某個點,那麼就可以把紋理映射到 3D 物體上去了。

紋理的坐标,叫

做紋理坐标系

。它的範圍隻有﹝0,0﹞到﹝1,1﹞ 。

OpenGL 學習系列 --- 紋理基本原理着色器操作上層代碼glBindTexture 函數綁定紋理中的值實際效果總結 & 名詞混淆點參考

它的坐标原點位于左下角,水準向右為 S 軸,豎直向上為 Y 軸。不論實際的紋理圖檔尺寸大小如何,橫向、縱向坐标最大值都是 1 。

例如:實際圖為 512 x 256 像素分辨率,則橫向第 512 個像素對應紋理坐标為 1 ,縱向第 256 個像素對應紋理坐标為 1 。不過,紋理圖最好是采用像素為 2 的 n 次方的紋理圖。

紋理映射的基本思想就是:首先為圖元中的每個頂點指定恰當的紋理坐标,然後通過紋理坐标在紋理圖中可以确定選中的紋理區域,最後将選中紋理區域中的内容根據紋理坐标映射到指定的圖元上。

紋理映射在 OpenGL 的渲染管線上的展現:在渲染管線中,先進行頂點着色器,繪制出物體的大緻形狀,之後會進行

光栅化

,将物體光栅化為許多片段組成,然後再進行片段着色器,将圖形的每個片段進行着色。

那麼就需要在 頂點着色器 中将紋理的坐标傳入,在光栅化階段,紋理坐标将根據 頂點着色器 對它的處理以及 片段和各頂點的位置關系 插值産生,然後才是将插值計算後的結果傳入到片段着色器中。

着色器操作

相比直接繪制圖形,使用紋理後,着色器也要改變了。

頂點着色器:

1attribute vec4 a_Position;
 2attribute vec2 a_TextureCoordinates;
 3varying vec2 v_TextureCoordinates;
 4uniform mat4 u_ModelMatrix;
 5uniform mat4 u_ViewMatrix;
 6uniform mat4 u_ProjectionMatrix;
 7uniform mat4 u_Matrix;
 8
 9void main() {
10    v_TextureCoordinates = a_TextureCoordinates ;
11    gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
12}           

在頂點着色器中多了 v_TextureCoordinates 變量,它是 varying 類型,意思為可變類型,在光栅化處理時會對該變量進行處理,随後傳入到片段着色器中。

片段着色器

1precision mediump float;
2uniform sampler2D u_TextureUnit;
3varying vec2 v_TextureCoordinates;
4
5void main(){
6    // 未使用紋理的顔色指派 : gl_FragColor = u_Color;
7    gl_FragColor = texture2D(u_TextureUnit,v_TextureCoordinates);
8}           

v_TextureCoordinates1

變量就是接受來自頂點着色器傳的值,

u_TextureUnit

變量就是使用的采樣器,類型是

sampler2D

使用紋理後的片段着色器要使用

texture2D

函數給顔色指派。

texture2D

函數的作用就是采樣,從紋理中采取像素指派給

gl_FragColor

變量,也就是最後的顔色。

上層代碼

大緻了解了着色器代碼,接着就是上層的 Java 代碼了。

和要建立一個 OpenGL ProgramId 類似,使用紋理也需要建立一個紋理 ID。

1     /**
 2     * 傳回加載圖像後的 OpenGl 紋理的 ID
 3     * @param context
 4     * @param resourceId
 5     * @return
 6     */
 7    public static int loadTexture(Context context, int resourceId) {
 8        final int[] textureObjectIds = new int[1];
 9        glGenTextures(1, textureObjectIds, 0);
10        if (textureObjectIds[0] == 0) {
11            Timber.d("Could not generate a new OpenGL texture object.");
12            return 0;
13        }
14        final BitmapFactory.Options options = new BitmapFactory.Options();
15        options.inScaled = false;
16        final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
17
18        if (bitmap == null) {
19            Timber.d("resource Id could not be decoded");
20            glDeleteTextures(1, textureObjectIds, 0);
21            return 0;
22        }
23
24        glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);
25
26        // 設定縮小的情況下過濾方式
27        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
28        // 設定放大的情況下過濾方式
29        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
30
31        // 加載紋理到 OpenGL,讀入 Bitmap 定義的位圖資料,并把它複制到目前綁定的紋理對象
32        // 目前綁定的紋理對象就會被附加上紋理圖像。
33        texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
34
35        bitmap.recycle();
36
37        // 為目前綁定的紋理自動生成所有需要的多級漸遠紋理
38        // 生成 MIP 貼圖
39        glGenerateMipmap(GL_TEXTURE_2D);
40
41        // 解除與紋理的綁定,避免用其他的紋理方法意外地改變這個紋理
42        glBindTexture(GL_TEXTURE_2D, 0);
43
44        return textureObjectIds[0];
45    }           
  1. 首先使用

    glGenTextures

    建立紋理 ID。
  2. 如果建立失敗,則使用

    glDeleteTextures

    删除并退出。
  3. 建立成功之後,使用

    glBindTexture

    函數将紋理 ID 和紋理目标綁定。
  4. 之後會設定紋理在縮小和放大情況下的過濾方式。
  5. 再使用

    texImage2D

    将紋理目标和

    Bitmap

    圖檔綁定。
  6. 使用

    glGenerateMipmap

    函數生成多級漸遠紋理和

    MIP

    紋理貼圖。
  7. glBindTexture

    函數解除綁定。

glBindTexture 函數

這裡要重點說一下 glBindTexture 函數。

它的作用是綁定紋理名到指定的目前活動紋理單元,當一個紋理綁定到一個目标時,目标紋理單元先前綁定的紋理對象将被自動斷開。紋理目标預設綁定的是 0 ,是以要斷開時,也再将紋理目标綁定到 0 就好了。

是以在代碼的最後調用了

glBindTexture(GL_TEXTURE_2D, 0) 來解除綁定

當一個紋理被綁定時,在綁定的目标上的 OpenGL 操作将作用到綁定的紋理上,并且,對綁定的目标的查詢也将傳回其上綁定的紋理的狀态。

也就是說,這個紋理目标成為了被綁定到它上面的紋理的别名,而紋理名稱為 0 則會引用到它的預設紋理。是以,當後續對紋理目标調用

glTexParameteri

函數設定過濾方式,其實也是對紋理設定的過濾方式。

綁定紋理中的值

建立并且設定了紋理着色器ID之後,就需要綁定并設定在着色器語言中的變量了。

1        // 綁定着色器腳本中的變量
2        uTextureUnitAttr = glGetUniformLocation(mProgram, U_TEXTURE_UNIT)
3        mTextureId = TextureHelper.loadTexture(mContext,R.drawable.texture)
4        // 激活紋理單元
5        glActiveTexture(GL_TEXTURE0)
6        // 綁定紋理目标
7        glBindTexture(GL_TEXTURE_2D, mTextureId)
8        // 給片段着色器中的采樣器變量 sample2D 指派
9        glUniform1i(uTextureUnitAttr, 0)           

在着色器腳本中定義了

uniform

類型的采樣器變量

sampler2D

,在上層的應用代碼需要将它綁定并指派。而

varying

類型的變量由頂點着色器傳過去,不需要另外指派了。

接下來要使用 glActiveTexture 函數激活紋理單元。在一個系統中,紋理單元的資料是有限的,在源碼中從 GL_TEXTURE0 到 GL_TEXTURE31 共定義了三十二個紋理單元,但具體數量根據機型而定。

通過 GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 常量可以查詢到。

1   var intBuffer:IntBuffer = IntBuffer.allocate(1)
2   glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,intBuffer)
3   LogUtil.d("max combined texture image units " + intBuffer[0])           

激活了紋理單元,還需要再綁定紋理目标。

一個紋理單元包含了多個類型紋理目标,如:GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP 等等。

因為紋理單元是紋理的一個别名,是以對紋理單元所做的操作,都相當于對紋理操作的。把一些對紋理所做的操作提取到函數裡,最後再加載紋理,并綁定到紋理目标上。

glUniform1i

函數為采樣器進行指派為 0 ,這是和激活紋理單元相對應的。因為激活的紋理單元為 0 ,是以指派也是為 0 。如果這裡不一緻,直接就看不到任何東西了。

實際效果

當綁定并設定好片段着色器中的值之後,接下來的流程就和繪制基本圖形一樣了。

OpenGL 學習系列 --- 紋理基本原理着色器操作上層代碼glBindTexture 函數綁定紋理中的值實際效果總結 & 名詞混淆點參考

具體的繪制操作都在片段着色器裡面定義了,而在上層代碼中就不用花費很多心思了,在頂點着色器不變的情況下,甚至可以隻改變片段着色器的值來繪制不同的紋理效果。

總結 & 名詞混淆點

在上面既是紋理單元又是紋理目标的很容易搞混,梳理一下概念:

形如 GL_TEXTURE0、GL_TEXTURE1、GL_TEXTURE2 的就是紋理單元,一台機子上紋理單元數量是有限的,依具體機型而定。而 glActiveTexture 則是激活具體的紋理單元。

一個紋理單元又包含多個類型的紋理目标,有:GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP 等等。

通過 glGenTextures 函數生成的 int 類型的值就是紋理,通過 glBindTexture 函數将紋理目标和紋理綁定後,對紋理目标所進行的操作都反映到對紋理上。

紋理目标需要通過 texImage2D 函數附加上 Bitmap 位圖。

參考

http://blog.csdn.net/opengl_es/article/details/19852277 http://blog.csdn.net/artisans/article/details/76695614

具體代碼詳情,可以參考我的 Github 項目:

https://github.com/glumes/AndroidOpenGLTutorial
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。
OpenGL 學習系列 --- 紋理基本原理着色器操作上層代碼glBindTexture 函數綁定紋理中的值實際效果總結 & 名詞混淆點參考