接下來探索紋理了。java
紋理,簡單的理解就是一副圖像。而把一副圖像映射到圖形上的過程,叫作紋理映射
。微信
好比有以下圖形和三角形,想要把圖形中的一部分映射到三角形上。函數
結果就是這樣的:ui
這就是紋理映射的一個小小例子。spa
要注意到,OpenGL 繪製的物體是 3D 的,而紋理是 2D 的,那麼紋理映射就是將 2D 的紋理映射到 3D 的物體上,能夠想象成用一張紙裹着一個物體同樣,不過要按照必定規律來。.net
OpenGL 中繪製的物體是有座標系的,每一個點都對應 x、y、z 座標,而紋理也有着它的座標,只要 3D 物體中的每一個點都對應了 2D 紋理中的某個點,那麼就能夠把紋理映射到 3D 物體上去了。3d
紋理的座標,叫作紋理座標系
。它的範圍只有 到 。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];
}
複製代碼
glGenTextures
建立紋理 ID。glDeleteTextures
刪除並退出。glBindTexture
函數將紋理 ID 和紋理目標綁定。texImage2D
將紋理目標和 Bitmap 圖片綁定。glGenerateMipmap
函數生成多級漸遠紋理和 MIP 紋理貼圖。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 位圖。
最後,若是以爲文章不錯,歡迎關注微信公衆號:【紙上淺談】