OpenGL 學習系列---紋理

接下來探索紋理了。java

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

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

結果就是這樣的:ui

這就是紋理映射的一個小小例子。spa

基本原理

要注意到,OpenGL 繪製的物體是 3D 的,而紋理是 2D 的,那麼紋理映射就是將 2D 的紋理映射到 3D 的物體上,能夠想象成用一張紙裹着一個物體同樣,不過要按照必定規律來。.net

OpenGL 中繪製的物體是有座標系的,每一個點都對應 x、y、z 座標,而紋理也有着它的座標,只要 3D 物體中的每一個點都對應了 2D 紋理中的某個點,那麼就能夠把紋理映射到 3D 物體上去了。3d

紋理的座標,叫作紋理座標系。它的範圍只有 [0,0][1,1]code

它的座標原點位於左下角,水平向右爲 S 軸,豎直向上爲 Y 軸。不論實際的紋理圖片尺寸大小如何,橫向、縱向座標最大值都是 1 。orm

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

紋理映射的基本思想就是:首先爲圖元中的每一個頂點指定恰當的紋理座標,而後經過紋理座標在紋理圖中能夠肯定選中的紋理區域,最後將選中紋理區域中的內容根據紋理座標映射到指定的圖元上。

紋理映射在 OpenGL 的渲染管線上的體現:在渲染管線中,先進行頂點着色器,繪製出物體的大體形狀,以後會進行光柵化,將物體光柵化爲許多片斷組成,而後再進行片斷着色器,將圖形的每一個片斷進行着色。

那麼就須要在 頂點着色器 中將紋理的座標傳入,在光柵化階段,紋理座標將根據 頂點着色器 對它的處理以及 片斷和各頂點的位置關係 插值產生,而後纔是將插值計算後的結果傳入到片斷着色器中。

着色器操做

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

頂點着色器:

attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;
uniform mat4 u_Matrix;

void main() {
    v_TextureCoordinates = a_TextureCoordinates ;
    gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
}
複製代碼

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

片斷着色器

precision mediump float;
uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;

void main(){
	// 未使用紋理的顏色賦值 : gl_FragColor = u_Color;
    gl_FragColor = texture2D(u_TextureUnit,v_TextureCoordinates);
}
複製代碼

v_TextureCoordinates1變量就是接受來自頂點着色器傳的值,u_TextureUnit變量就是使用的採樣器,類型是sampler2D

使用紋理後的片斷着色器要使用 texture2D 函數給顏色賦值。

texture2D函數的做用就是採樣,從紋理中採起像素賦值給 gl_FragColor變量,也就是最後的顏色。

上層代碼

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

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

/** * 返回加載圖像後的 OpenGl 紋理的 ID * @param context * @param resourceId * @return */
    public static int loadTexture(Context context, int resourceId) {
        final int[] textureObjectIds = new int[1];
        glGenTextures(1, textureObjectIds, 0);
        if (textureObjectIds[0] == 0) {
            Timber.d("Could not generate a new OpenGL texture object.");
            return 0;
        }
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = false;
        final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);

        if (bitmap == null) {
            Timber.d("resource Id could not be decoded");
            glDeleteTextures(1, textureObjectIds, 0);
            return 0;
        }

        glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);

        // 設置縮小的狀況下過濾方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        // 設置放大的狀況下過濾方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // 加載紋理到 OpenGL,讀入 Bitmap 定義的位圖數據,並把它複製到當前綁定的紋理對象
        // 當前綁定的紋理對象就會被附加上紋理圖像。
        texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);

        bitmap.recycle();
        
        // 爲當前綁定的紋理自動生成全部須要的多級漸遠紋理
        // 生成 MIP 貼圖
        glGenerateMipmap(GL_TEXTURE_2D);

        // 解除與紋理的綁定,避免用其餘的紋理方法意外地改變這個紋理
        glBindTexture(GL_TEXTURE_2D, 0);

        return textureObjectIds[0];
    }
複製代碼
  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以後,就須要綁定並設置在着色器語言中的變量了。

// 綁定着色器腳本中的變量
        uTextureUnitAttr = glGetUniformLocation(mProgram, U_TEXTURE_UNIT)
		mTextureId = TextureHelper.loadTexture(mContext,R.drawable.texture)
		// 激活紋理單元
        glActiveTexture(GL_TEXTURE0)
        // 綁定紋理目標
        glBindTexture(GL_TEXTURE_2D, mTextureId)
        // 給片斷着色器中的採樣器變量 sample2D 賦值
        glUniform1i(uTextureUnitAttr, 0)

複製代碼

在着色器腳本中定義了 uniform 類型的採樣器變量 sampler2D,在上層的應用代碼須要將它綁定並賦值。而 varying 類型的變量由頂點着色器傳過去,不須要另外賦值了。

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

經過 GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 常量能夠查詢到。

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

激活了紋理單元,還須要再綁定紋理目標。

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

由於紋理單元是紋理的一個別名,因此對紋理單元所作的操做,都至關於對紋理操做的。把一些對紋理所作的操做提取到函數裏,最後再加載紋理,並綁定到紋理目標上。

使用glUniform1i函數爲採樣器進行賦值爲 0 ,這是和激活紋理單元相對應的。由於激活的紋理單元爲 0 ,因此賦值也是爲 0 。若是這裏不一致,直接就看不到任何東西了。

實際效果

當綁定並設置好片斷着色器中的值以後,接下來的流程就和繪製基本圖形同樣了。

具體的繪製操做都在片斷着色器裏面定義了,而在上層代碼中就不用花費不少心思了,在頂點着色器不變的狀況下,甚至能夠只改變片斷着色器的值來繪製不一樣的紋理效果。

總結 & 名詞混淆點

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

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

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

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

紋理目標須要經過 texImage2D 函數附加上 Bitmap 位圖。

參考

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

最後,若是以爲文章不錯,歡迎關注微信公衆號:【紙上淺談】

相關文章
相關標籤/搜索