引自:http://blog.csdn.net/wl_soft50/article/details/7916720
http://blog.sina.com.cn/s/blog_923fdd9b0102vbe0.html
與OpenGL ES1.x渲染管線相比,OpenGL ES 2.0渲染管線中“頂點着色器”取代了OpenGL ES 1.x渲染管線中的“變換和光照”;“片元着色器”取代了OpenGL ES 1.x渲染管線中的“紋理環境和顏色求和”、“霧”以及“Alpha測試”。 這使得開發人員在使用OpenGL ES 2.0API進行開發時,可以通過編寫頂點及片元着色器程序,來完成一些頂點變換和紋理顏色計算工作,實現更加靈活、精細化的計算與渲染。
一、着色(Shader)語言
着色語言是一種類C的編程語言,但不像C語言一樣支持雙精度浮點型(double)、字節型(byte)、短整型(short)、長整型(long),並且取消了C中的聯合體(union)、枚舉類型(enum)、無符號數(unsigned)以及位運算等特性。 着色語言中有許多內建的原生數據類型以及構建數據類型,如:浮點型(float)、布爾型(bool)、整型(int)、矩陣型(matrix)以及向量型(vec2、vec3等)等。總體來說,這些數據類型可以分為標量、向量、矩陣、采樣器、結構體以及數組等。 shader支持下面數據類型:
float bool int 基本數據類型 vec2 包含了2個浮點數的向量 vec3 包含了3個浮點數的向量 vec4 包含了4個浮點數的向量 ivec2 包含了2個整數的向量 ivec3 包含了3個整數的向量 ivec4 包含了4個整數的向量 bvec2 包含了2個布爾數的向量 bvec3 包含了3個布爾數的向量 bvec4 包含了4個布爾數的向量 mat2 2*2維矩陣 mat3 3*3維矩陣 mat4 4*4維矩陣 sampler1D 1D紋理采樣器 sampler2D 2D紋理采樣器 sampler3D 3D紋理采樣器
1. 頂點着色器
1.1 頂點着色器示例代碼
uniform mat4 uMVPMatrix; // 應用程序傳入頂點着色器的總變換矩陣 attribute vec4 aPosition; // 應用程序傳入頂點着色器的頂點位置 attribute vec2 aTextureCoord; // 應用程序傳入頂點着色器的頂點紋理坐標 attribute vec4 aColor // 應用程序傳入頂點着色器的頂點顏色變量 varying vec4 vColor // 用於傳遞給片元着色器的頂點顏色數據 varying vec2 vTextureCoord; // 用於傳遞給片元着色器的頂點紋理數據 void main() { gl_Position = uMVPMatrix * aPosition; // 根據總變換矩陣計算此次繪制此頂點位置 vColor = aColor; // 將頂點顏色數據傳入片元着色器 vTextureCoord = aTextureCoord; // 將接收的紋理坐標傳遞給片元着色器 }
1.2 頂點着色器介紹
頂點着色器是一個可編程的處理單元,執行頂點變換、紋理坐標變換、光照、材質等頂點的相關操作,每頂點執行一次。替代了傳統渲染管線中頂點變換、光照以及紋理坐標的處理,開發人員可以根據自己的需求自行開發,大大增加了程序的靈活性。
頂點着色器主要是傳入相應的Attribute變量、Uniforms變量、采樣器以及臨時變量,經過頂點着色器后生成Varying變量。如下圖所示:
(1)attribute變量(屬性變量)只能用於頂點着色器中,不能用於片元着色器。 一般用該變量來表示一些頂點數據,如:頂點坐標、紋理坐標、顏色等。
(2)uniforms變量(一致變量)用來將數據值從應用程其序傳遞到頂點着色器或者片元着色器。 該變量有點類似C語言中的常量(const),即該變量的值不能被shader程序修改。一般用該變量表示變換矩陣、光照參數、紋理采樣器等。
(3)varying變量(易變變量)是從頂點着色器傳遞到片元着色器的數據變量。頂點着色器可以使用易變變量來傳遞需要插值的顏色、法向量、紋理坐標等任意值。 在頂點與片元shader程序間傳遞數據是很容易的,一般在頂點shader中修改varying變量值,然后片元shader中使用該值,當然,該變量在頂點及片元這兩段shader程序中聲明必須是一致的 。例如:上面代碼中應用程序中由頂點着色器傳入片元着色器中的vColor變量。
(4)gl_Position 為內建變量,表示變換后點的空間位置。 頂點着色器從應用程序中獲得原始的頂點位置數據,這些原始的頂點數據在頂點着色器中經過平移、旋轉、縮放等數學變換后,生成新的頂點位置。新的頂點位置通過在頂點着色器中寫入gl_Position傳遞到渲染管線的后繼階段繼續處理。
2. 片元着色器
2.1 片元着色器示例代碼
precision mediump float; // 設置工作精度 varying vec4 vColor; // 接收從頂點着色器過來的頂點顏色數據 varying vec2 vTextureCoord; // 接收從頂點着色器過來的紋理坐標 uniform sampler2D sTexture; // 紋理采樣器,代表一幅紋理 void main() { gl_FragColor = texture2D(sTexture, vTextureCoord) * vColor;// 進行紋理采樣 }
此片元着色器的主要功能為根據接收的記錄片元紋理坐標的易變變量中的紋理坐標,調用texture2D內建函數從采樣器中進行紋理采樣,得到此片元的顏色值。最后,將采樣到的顏色值傳給gl_FragColor內建變量,完成片元的着色。
2.2 片元着色器介紹
片元着色器是一個處理片元值及其相關聯數據的可編程單元,片元着色器可執行紋理的訪問、顏色的匯總、霧化等操作,每片元執行一次。片元着色器替代了紋理、顏色求和、霧以及Alpha測試,這一部分是需要開發者自己開發的。

(1)varying指的是從頂點着色器傳遞到片元着色器的數據變量
(2)gl_FragColor為內置變量,用來保存片元着色器計算完成的片元顏色值,此顏色值將送入渲染管線的后繼階段進行處理。
二、加載着色器代碼示例
private int loadShader( int shaderType, String source) { // 創建一個新shader int shader = GLES20.glCreateShader(shaderType); // 若創建成功則加載shader if (shader != 0) { // 加載shader源代碼 GLES20.glShaderSource(shader, source); // 編譯shader GLES20.glCompileShader(shader); // 存放編譯成功shader數量的數組 int[] compiled = new int[1]; // 獲取Shader的編譯情況 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] == 0) { //若編譯失敗則顯示錯誤日志並刪除此shader Log.e(TAG, "Could not compile shader " + shaderType + ":"); Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); shader = 0; } } return shader; }
上述示例代碼中主要用到了三個方法:
GLES20.glCreateShader(shaderType);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
1. GLES20.glCreateShader(), 創建一個容納shader的容器,稱為shader容器。
函數原型: int glCreateShader (int type) 方法參數: GLES20.GL_VERTEX_SHADER (頂點shader) GLES20.GL_FRAGMENT_SHADER (片元shader)
如果調用成功的話,函數將返回一個整形的正整數作為shader容器的id。 如果對c++熟悉的話,該函數的返回值理解為指針或者句柄更合適。
2. GLES20.glShaderSource(shader, source),添加shader的源代碼。源代碼應該以字符串數組的形式表示。當然,也可以只用一個字符串來包含所有的源代碼。
函數原型: void glShaderSource (int shader, String string) 參數含義: shader是代表shader容器的id(由glCreateShader返回的整形數); string是包含源程序的字符串數組。
3. GLES20.glCompileShader(shader),對shader容器中的源代碼進行編譯。
函數原型: void glCompileShader (int shader) 參數含義: shader是代表shader容器的id。
4. 調試
調試一個shader是非常困難的。shader的世界里沒有printf,無法在控制台中打印調試信息, 更沒有斷點,甚至很多編輯器對shader程序關鍵字、變量等連高亮顯示都不支持。 但是可以通過一些OpenGL提供的函數來獲取編譯和連接過程中的信息。
在編譯階段使用glGetShaderiv獲取編譯情況,在連接階段使用glGetProgramiv獲取連接情況。當錯誤產生的時候,還可以從InfoLog中獲得更多的信息。InfoLog中存儲了關於上一個操作執行時的相關信息,比如編譯階段的警告和錯誤,以及連接階段產生的問題。不幸的是對於錯誤信息沒有統一的標准,所以不同的硬件或驅動程序將提供不同的錯誤信息。
4.1 編譯階段使用glGetShaderiv獲取編譯情況
函數原型: void glGetShaderiv (int shader, int pname, int[] params, int offset) 參數含義: shader是一個shader的id; pname使用GL_COMPILE_STATUS; params是返回值,如果一切正常返回GL_TRUE代,否則返回GL_FALSE。
4.2編譯階段使用glGetShaderInfoLog獲取編譯錯誤
函數原型: String glGetShaderInfoLog (int shader) 參數含義: shader是一個頂點shader或者片元shader的id。
4.3 在連接階段使用glGetProgramiv獲取連接情況
函數原型: void glGetProgramiv (int program, int pname, int[] params, int offset) 參數含義: program是一個着色器程序的id; pname是GL_LINK_STATUS; param是返回值,如果一切正常返回GL_TRUE代,否則返回GL_FALSE。
4.4 在連接階段使用glGetProgramInfoLog獲取連接錯誤
函數原型: String glGetProgramInfoLog (int program) 參數含義: program是一個着色器程序的id。
4.5 清理shader的glDeleteShader方法
當不再需要某個shader或某個程序的時候,需要對其進行清理,以釋放資源。前面,提到過如何向一個程序中添加一個shader。這里可調用下面的函數來將一個shader從一個程序中清除掉。
函數原型: void glDeleteShader (int shader); 參數含義: shader是要被排除的頂點shader或者片元shader的id。
如果,一個shader被刪除之前沒有從相應的程序中排除,那么這個shader不會被實際刪除,而只是被標記為被刪除;當shader被從程序中排除的時候,才會被真正地刪除。
三、創建着色器程序代碼示例
private int createProgram(String vertexSource, String fragmentSource) { // 加載頂點着色器 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); if (vertexShader == 0) { return 0; } // 加載片元着色器 int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); if (pixelShader == 0) { return 0; } // 創建着色器程序 int program = GLES20.glCreateProgram(); // 若程序創建成功則向程序中加入頂點着色器與片元着色器 if (program != 0) { // 向程序中加入頂點着色器 GLES20.glAttachShader(program, vertexShader); // 向程序中加入片元着色器 GLES20.glAttachShader(program, pixelShader); // 鏈接程序 GLES20.glLinkProgram(program); // 存放鏈接成功program數量的數組 int[] linkStatus = new int[1]; // 獲取program的鏈接情況 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); // 若鏈接失敗則報錯並刪除程序 if (linkStatus[0] != GLES20.GL_TRUE) { Log.e(TAG, "Could not link program: "); Log.e(TAG, GLES20.glGetProgramInfoLog(program)); GLES20.glDeleteProgram(program); program = 0; } } // 釋放shader資源 GLES20.glDeleteShader(vertexShader ); GLES20.glDeleteShader(pixelShader); return program; }
1. glCreateProgram,在連接shader之前,首先要創建一個容納程序的容器,稱為着色器程序容器。可以通過glCreateProgram函數來創建一個程序容器。
函數原型: int glCreateProgram () 如果函數調用成功將返回一個正整數作為該着色器程序的id。
2. glAttachShader,接下來,我們要將shader容器添加到程序中。這時的shader容器不一定需要被編譯,他們甚至不需要包含任何的代碼。我們要做的只是將shader容器添加到程序中。使用glAttachShader函數來為程序添加shader容器。
函數原型: void glAttachShader (int program, int shader) 參數含義: program是着色器程序容器的id; shader是要添加的頂點或者片元shader容器的id。
如果你同時擁有了,頂點shader和片元shader,需要分別將他們各自的兩個shader容器添加的程序容器中。
3. glLinkProgram,鏈接程序。
函數原型: void glLinkProgram (int program) 參數含義: program是着色器程序容器的id。
在鏈接操作執行以后,可以任意修改shader的源代碼,對shader重新編譯不會影響整個程序,除非重新鏈接程序。
4. glUseProgram,加載並使用鏈接好的程序。
函數原型: void glUseProgram (int program) 參數含義: program是要使用的着色器程序的id。
如果將program設置為0,表示使用固定功能管線。如果程序已經在使用的時候,對程序進行重新編譯,編譯后的應用程序會自動替代以前的那個被調用,這時你不需要再次調用這個函數。
四、 向着色器程序中傳遞數據
1. 獲取着色器程序內成員變量的id,也可以理解為句柄、指針。 glGetAttribLocation方法:獲取着色器程序中,指定為attribute類型變量的id。 glGetUniformLocation方法:獲取着色器程序中,指定為uniform類型變量的id。 如: // 獲取指向着色器中aPosition的index maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); // 獲取指向着色器中uMVPMatrix的index muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
2. 傳遞數據
使用上一節獲取的指向着色器相應數據成員的各個id,就能將我們自己定義的頂點數據、顏色數據等等各種數據傳遞到着色器當中了。
// 使用shader程序 GLES20.glUseProgram(mProgram); // 將最終變換矩陣傳入shader程序 GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0); // 設置緩沖區起始位置 mRectBuffer.position(0); // 頂點位置數據傳入着色器 GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 20, mRectBuffer); // 頂點顏色數據傳入着色器中 GLES20.glVertexAttribPointer(maColorHandle, 4, GLES20.GL_FLOAT, false, 4*4, mColorBuffer); // 頂點坐標傳遞到頂點着色器 GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, 20, mRectBuffer); // 允許使用頂點坐標數組 GLES20.glEnableVertexAttribArray(maPositionHandle); // 允許使用頂點顏色數組 GLES20.glDisableVertexAttribArray(maColorHandle); // 允許使用定點紋理數組 GLES20.glEnableVertexAttribArray(maTextureHandle); // 綁定紋理 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); // 圖形繪制 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);
2.1 glVertexAttribPointer,定義頂點屬性數組。
函數原型: void glVertexAttribPointer (int index, int size, int type, boolean normalized, int stride, Buffer ptr ) 參數含義: index 指定要修改的頂點着色器中頂點變量id; size 指定每個頂點屬性的組件數量。必須為1、2、3或者4。如position是由3個(x,y,z)組成,而顏色是4個(r,g,b,a)); type 指定數組中每個組件的數據類型。可用的符號常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值為GL_FLOAT; normalized 指定當被訪問時,固定點數據值是否應該被歸一化(GL_TRUE)或者直接轉換為固定點值(GL_FALSE); stride 指定連續頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0。如果normalized被設置為GL_TRUE,意味着整數型的值會被映射至區間[-1,1](有符號整數),或者區間[0,1](無符號整數),反之,這些值會被直接轉換為浮點值而不進行歸一化處理; ptr 頂點的緩沖數據。
2.2 啟用或者禁用頂點屬性數組。 調用glEnableVertexAttribArray和glDisableVertexAttribArray傳入參數index。如果啟用,那么當glDrawArrays或者glDrawElements被調用時,頂點屬性數組會被使用。
2.3 glActiveTexture, 選擇活動紋理單元。
函數原型: void glActiveTexture (int texture) 參數含義: texture指定哪一個紋理單元被置為活動狀態。texture必須是GL_TEXTUREi之一,其中0 <<span class="title">= i < GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,初始值為GL_TEXTURE0。
glActiveTexture()確定了后續的紋理狀態改變影響哪個紋理,紋理單元的數量是依據該紋理單元所被支持的具體實現。