因為近大半年都是在VR的項目,里面好多地方都用到opengl和shader之前因為沒接觸過這方面的東西,感覺走了不少彎路蜜徽,剛開始感覺這東西晦澀難懂蹋砚,一旦入門了就會好很多粉寞。
與OpenGL ES1.x渲染管線相比,OpenGL ES 2.0渲染管線中“頂點著色器”取代了OpenGL ES 1.x渲染管線中的“變換和光照”;“片元著色器”取代了OpenGL ES 1.x渲染管線中的“紋理環(huán)境和顏色求和”诈泼、“霧”以及“Alpha測試”懂拾。這使得開發(fā)人員在使用OpenGL ES 2.0API進(jìn)行開發(fā)時,可以通過編寫頂點及片元著色器程序铐达,來完成一些頂點變換和紋理顏色計算工作岖赋,實現(xiàn)更加靈活、精細(xì)化的計算與渲染瓮孙。
一唐断、著色(Shader)語言
著色語言是一種類C的編程語言,但不像C語言一樣支持雙精度浮點型(double)杭抠、字節(jié)型(byte)脸甘、短整型(short)、長整型(long)偏灿,并且取消了C中的聯(lián)合體(union)丹诀、枚舉類型(enum)、無符號數(shù)(unsigned)以及位運算等特性翁垂。著色語言中有許多內(nèi)建的原生數(shù)據(jù)類型以及構(gòu)建數(shù)據(jù)類型铆遭,如:浮點型(float)、布爾型(bool)沿猜、整型(int)枚荣、矩陣型(matrix)以及向量型(vec2、vec3等)等啼肩」髋總體來說,這些數(shù)據(jù)類型可以分為標(biāo)量疟游、向量、矩陣痕支、采樣器颁虐、結(jié)構(gòu)體以及數(shù)組等。shader支持下面數(shù)據(jù)類型:
float bool int 基本數(shù)據(jù)類型
vec2 包含了2個浮點數(shù)的向量
vec3 包含了3個浮點數(shù)的向量
vec4 包含了4個浮點數(shù)的向量
ivec2 包含了2個整數(shù)的向量
ivec3 包含了3個整數(shù)的向量
ivec4 包含了4個整數(shù)的向量
bvec2 包含了2個布爾數(shù)的向量
bvec3 包含了3個布爾數(shù)的向量
bvec4 包含了4個布爾數(shù)的向量
mat2 2*2維矩陣
mat3 3*3維矩陣
mat4 4*4維矩陣
sampler1D 1D紋理采樣器
sampler2D 2D紋理采樣器
sampler3D 3D紋理采樣器
- 頂點著色器
1.1 頂點著色器示例代碼
uniform mat4 uMVPMatrix; // 應(yīng)用程序傳入頂點著色器的總變換矩陣
attribute vec4 aPosition; // 應(yīng)用程序傳入頂點著色器的頂點位置
attribute vec2 aTextureCoord; // 應(yīng)用程序傳入頂點著色器的頂點紋理坐標(biāo)
attribute vec4 aColor // 應(yīng)用程序傳入頂點著色器的頂點顏色變量
varying vec4 vColor // 用于傳遞給片元著色器的頂點顏色數(shù)據(jù)
varying vec2 vTextureCoord; // 用于傳遞給片元著色器的頂點紋理數(shù)據(jù)
void main()
{
gl_Position = uMVPMatrix * aPosition; // 根據(jù)總變換矩陣計算此次繪制此頂點位置
vColor = aColor; // 將頂點顏色數(shù)據(jù)傳入片元著色器
vTextureCoord = aTextureCoord; // 將接收的紋理坐標(biāo)傳遞給片元著色器
}
1.2 頂點著色器介紹
頂點著色器是一個可編程的處理單元卧须,執(zhí)行頂點變換另绩、紋理坐標(biāo)變換、光照花嘶、材質(zhì)等頂點的相關(guān)操作笋籽,每頂點執(zhí)行一次。替代了傳統(tǒng)渲染管線中頂點變換椭员、光照以及紋理坐標(biāo)的處理车海,開發(fā)人員可以根據(jù)自己的需求自行開發(fā),大大增加了程序的靈活性隘击。
頂點著色器主要是傳入相應(yīng)的Attribute變量侍芝、Uniforms變量研铆、采樣器以及臨時變量,經(jīng)過頂點著色器后生成Varying變量州叠。如下圖所示:
(1)attribute變量(屬性變量)只能用于頂點著色器中棵红,不能用于片元著色器。一般用該變量來表示一些頂點數(shù)據(jù)咧栗,如:頂點坐標(biāo)逆甜、紋理坐標(biāo)、顏色等致板。
(2)uniforms變量(一致變量)用來將數(shù)據(jù)值從應(yīng)用程其序傳遞到頂點著色器或者片元著色器交煞。該變量有點類似C語言中的常量(const),即該變量的值不能被shader程序修改可岂。一般用該變量表示變換矩陣错敢、光照參數(shù)、紋理采樣器等缕粹。
(3)varying變量(易變變量)是從頂點著色器傳遞到片元著色器的數(shù)據(jù)變量稚茅。頂點著色器可以使用易變變量來傳遞需要插值的顏色、法向量平斩、紋理坐標(biāo)等任意值亚享。在頂點與片元shader程序間傳遞數(shù)據(jù)是很容易的,一般在頂點shader中修改varying變量值绘面,然后片元shader中使用該值欺税,當(dāng)然,該變量在頂點及片元這兩段shader程序中聲明必須是一致的揭璃。例如:上面代碼中應(yīng)用程序中由頂點著色器傳入片元著色器中的vColor變量晚凿。
(4)gl_Position為內(nèi)建變量,表示變換后點的空間位置瘦馍。頂點著色器從應(yīng)用程序中獲得原始的頂點位置數(shù)據(jù)歼秽,這些原始的頂點數(shù)據(jù)在頂點著色器中經(jīng)過平移、旋轉(zhuǎn)情组、縮放等數(shù)學(xué)變換后燥筷,生成新的頂點位置。新的頂點位置通過在頂點著色器中寫入gl_Position傳遞到渲染管線的后繼階段繼續(xù)處理院崇。
2. 片元著色器
2.1 片元著色器示例代碼
precision mediump float; // 設(shè)置工作精度
varying vec4 vColor; // 接收從頂點著色器過來的頂點顏色數(shù)據(jù)
varying vec2 vTextureCoord; // 接收從頂點著色器過來的紋理坐標(biāo)
uniform sampler2D sTexture; // 紋理采樣器肆氓,代表一幅紋理
void main()
{
gl_FragColor = texture2D(sTexture, vTextureCoord) * vColor;// 進(jìn)行紋理采樣
}
此片元著色器的主要功能為根據(jù)接收的記錄片元紋理坐標(biāo)的易變變量中的紋理坐標(biāo),調(diào)用texture2D內(nèi)建函數(shù)從采樣器中進(jìn)行紋理采樣底瓣,得到此片元的顏色值谢揪。最后,將采樣到的顏色值傳給gl_FragColor內(nèi)建變量,完成片元的著色键耕。
2.2 片元著色器介紹
片元著色器是一個處理片元值及其相關(guān)聯(lián)數(shù)據(jù)的可編程單元寺滚,片元著色器可執(zhí)行紋理的訪問、顏色的匯總屈雄、霧化等操作村视,每片元執(zhí)行一次。片元著色器替代了紋理酒奶、顏色求和蚁孔、霧以及Alpha測試,這一部分是需要開發(fā)者自己開發(fā)的惋嚎。
(1)varying指的是從頂點著色器傳遞到片元著色器的數(shù)據(jù)變量
(2)gl_FragColor為內(nèi)置變量杠氢,用來保存片元著色器計算完成的片元顏色值,此顏色值將送入渲染管線的后繼階段進(jìn)行處理另伍。
二鼻百、加載著色器代碼示例
private int loadShader( int shaderType, String source)
{
// 創(chuàng)建一個新shader
int shader = GLES20.glCreateShader(shaderType);
// 若創(chuàng)建成功則加載shader
if (shader != 0)
{
// 加載shader源代碼
GLES20.glShaderSource(shader, source);
// 編譯shader
GLES20.glCompileShader(shader);
// 存放編譯成功shader數(shù)量的數(shù)組
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(),創(chuàng)建一個容納shader的容器摆尝,稱為shader容器温艇。
函數(shù)原型:
int glCreateShader (int type)
方法參數(shù):
GLES20.GL_VERTEX_SHADER (頂點shader)
GLES20.GL_FRAGMENT_SHADER (片元shader)
如果調(diào)用成功的話,函數(shù)將返回一個整形的正整數(shù)作為shader容器的id堕汞。如果對c++熟悉的話勺爱,該函數(shù)的返回值理解為指針或者句柄更合適。
2. GLES20.glShaderSource(shader, source)讯检,添加shader的源代碼琐鲁。源代碼應(yīng)該以字符串?dāng)?shù)組的形式表示。當(dāng)然人灼,也可以只用一個字符串來包含所有的源代碼围段。
函數(shù)原型:
void glShaderSource (int shader, String string)
參數(shù)含義:
shader是代表shader容器的id(由glCreateShader返回的整形數(shù));
string是包含源程序的字符串?dāng)?shù)組投放。
- GLES20.glCompileShader(shader)奈泪,對shader容器中的源代碼進(jìn)行編譯。
函數(shù)原型:
void glCompileShader (int shader)
參數(shù)含義:
shader是代表shader容器的id跪呈。
- 調(diào)試
調(diào)試一個shader是非常困難的。shader的世界里沒有printf取逾,無法在控制臺中打印調(diào)試信息耗绿,更沒有斷點,甚至很多編輯器對shader程序關(guān)鍵字砾隅、變量等連高亮顯示都不支持误阻。但是可以通過一些OpenGL提供的函數(shù)來獲取編譯和連接過程中的信息。
在編譯階段使用glGetShaderiv獲取編譯情況,在連接階段使用glGetProgramiv獲取連接情況究反。當(dāng)錯誤產(chǎn)生的時候寻定,還可以從InfoLog中獲得更多的信息。InfoLog中存儲了關(guān)于上一個操作執(zhí)行時的相關(guān)信息精耐,比如編譯階段的警告和錯誤狼速,以及連接階段產(chǎn)生的問題。不幸的是對于錯誤信息沒有統(tǒng)一的標(biāo)準(zhǔn)卦停,所以不同的硬件或驅(qū)動程序?qū)⑻峁┎煌腻e誤信息向胡。
4.1 編譯階段使用glGetShaderiv獲取編譯情況
函數(shù)原型:
void glGetShaderiv (int shader, int pname, int[] params, int offset)
參數(shù)含義:
shader是一個shader的id;
pname使用GL_COMPILE_STATUS惊完;
params是返回值僵芹,如果一切正常返回GL_TRUE代,否則返回GL_FALSE小槐。
4.2編譯階段使用glGetShaderInfoLog獲取編譯錯誤
函數(shù)原型:
String glGetShaderInfoLog (int shader)
參數(shù)含義:
shader是一個頂點shader或者片元shader的id拇派。
4.3 在連接階段使用glGetProgramiv獲取連接情況
函數(shù)原型:
void glGetProgramiv (int program, int pname, int[] params, int offset)
參數(shù)含義:
program是一個著色器程序的id;
pname是GL_LINK_STATUS凿跳;
param是返回值件豌,如果一切正常返回GL_TRUE代,否則返回GL_FALSE拄显。
4.4 在連接階段使用glGetProgramInfoLog獲取連接錯誤
函數(shù)原型:
String glGetProgramInfoLog (int program)
參數(shù)含義:
program是一個著色器程序的id苟径。
4.5 清理shader的glDeleteShader方法
當(dāng)不再需要某個shader或某個程序的時候,需要對其進(jìn)行清理躬审,以釋放資源棘街。前面,提到過如何向一個程序中添加一個shader承边。這里可調(diào)用下面的函數(shù)來將一個shader從一個程序中清除掉遭殉。
函數(shù)原型:
void glDeleteShader (int shader);
參數(shù)含義:
shader是要被排除的頂點shader或者片元shader的id博助。
如果险污,一個shader被刪除之前沒有從相應(yīng)的程序中排除,那么這個shader不會被實際刪除富岳,而只是被標(biāo)記為被刪除蛔糯;當(dāng)shader被從程序中排除的時候,才會被真正地刪除窖式。
三蚁飒、創(chuàng)建著色器程序代碼示例
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;
}
// 創(chuàng)建著色器程序
int program = GLES20.glCreateProgram();
// 若程序創(chuàng)建成功則向程序中加入頂點著色器與片元著色器
if (program != 0)
{
// 向程序中加入頂點著色器
GLES20.glAttachShader(program, vertexShader);
// 向程序中加入片元著色器
GLES20.glAttachShader(program, pixelShader);
// 鏈接程序
GLES20.glLinkProgram(program);
// 存放鏈接成功program數(shù)量的數(shù)組
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;
}
- glCreateProgram,在連接shader之前萝喘,首先要創(chuàng)建一個容納程序的容器淮逻,稱為著色器程序容器琼懊。可以通過glCreateProgram函數(shù)來創(chuàng)建一個程序容器爬早。
函數(shù)原型:
int glCreateProgram ()
如果函數(shù)調(diào)用成功將返回一個正整數(shù)作為該著色器程序的id哼丈。
- glAttachShader,接下來筛严,我們要將shader容器添加到程序中醉旦。這時的shader容器不一定需要被編譯,他們甚至不需要包含任何的代碼脑漫。我們要做的只是將shader容器添加到程序中髓抑。使用glAttachShader函數(shù)來為程序添加shader容器。
函數(shù)原型:
void glAttachShader (int program, int shader)
參數(shù)含義:
program是著色器程序容器的id优幸;
shader是要添加的頂點或者片元shader容器的id吨拍。
如果你同時擁有了,頂點shader和片元shader网杆,需要分別將他們各自的兩個shader容器添加的程序容器中羹饰。
3. glLinkProgram,鏈接程序碳却。
函數(shù)原型:
void glLinkProgram (int program)
參數(shù)含義:
program是著色器程序容器的id队秩。
在鏈接操作執(zhí)行以后,可以任意修改shader的源代碼昼浦,對shader重新編譯不會影響整個程序馍资,除非重新鏈接程序。
4. glUseProgram关噪,加載并使用鏈接好的程序鸟蟹。
函數(shù)原型:
void glUseProgram (int program)
參數(shù)含義:
program是要使用的著色器程序的id。
如果將program設(shè)置為0使兔,表示使用固定功能管線建钥。如果程序已經(jīng)在使用的時候,對程序進(jìn)行重新編譯虐沥,編譯后的應(yīng)用程序會自動替代以前的那個被調(diào)用熊经,這時你不需要再次調(diào)用這個函數(shù)。
四欲险、 向著色器程序中傳遞數(shù)據(jù)
1. 獲取著色器程序內(nèi)成員變量的id镐依,也可以理解為句柄、指針天试。
glGetAttribLocation方法:獲取著色器程序中槐壳,指定為attribute類型變量的id。
glGetUniformLocation方法:獲取著色器程序中秋秤,指定為uniform類型變量的id宏粤。
如:
// 獲取指向著色器中aPosition的index
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
// 獲取指向著色器中uMVPMatrix的index
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
2. 傳遞數(shù)據(jù)
使用上一節(jié)獲取的指向著色器相應(yīng)數(shù)據(jù)成員的各個id,就能將我們自己定義的頂點數(shù)據(jù)灼卢、顏色數(shù)據(jù)等等各種數(shù)據(jù)傳遞到著色器當(dāng)中了绍哎。
// 使用shader程序
GLES20.glUseProgram(mProgram);
// 將最終變換矩陣傳入shader程序
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
// 設(shè)置緩沖區(qū)起始位置
mRectBuffer.position(0);
// 頂點位置數(shù)據(jù)傳入著色器
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 20, mRectBuffer);
// 頂點顏色數(shù)據(jù)傳入著色器中
GLES20.glVertexAttribPointer(maColorHandle, 4, GLES20.GL_FLOAT, false, 4*4, mColorBuffer);
// 頂點坐標(biāo)傳遞到頂點著色器
GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, 20, mRectBuffer);
// 允許使用頂點坐標(biāo)數(shù)組
GLES20.glEnableVertexAttribArray(maPositionHandle);
// 允許使用頂點顏色數(shù)組
GLES20.glDisableVertexAttribArray(maColorHandle);
// 允許使用定點紋理數(shù)組
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,定義頂點屬性數(shù)組鞋真。
函數(shù)原型:
void glVertexAttribPointer (int index, int size, int type, boolean normalized, int stride, Buffer ptr )
參數(shù)含義:
index 指定要修改的頂點著色器中頂點變量id崇堰;
size 指定每個頂點屬性的組件數(shù)量。必須為1涩咖、2海诲、3或者4。如position是由3個(x,y,z)組成檩互,而顏色是4個(r,g,b,a))特幔;
type 指定數(shù)組中每個組件的數(shù)據(jù)類型≌⒆颍可用的符號常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT蚯斯,初始值為GL_FLOAT;
normalized 指定當(dāng)被訪問時饵较,固定點數(shù)據(jù)值是否應(yīng)該被歸一化(GL_TRUE)或者直接轉(zhuǎn)換為固定點值(GL_FALSE)拍嵌;
stride 指定連續(xù)頂點屬性之間的偏移量。如果為0循诉,那么頂點屬性會被理解為:它們是緊密排列在一起的横辆。初始值為0。如果normalized被設(shè)置為GL_TRUE茄猫,意味著整數(shù)型的值會被映射至區(qū)間[-1,1](有符號整數(shù))狈蚤,或者區(qū)間[0,1](無符號整數(shù)),反之募疮,這些值會被直接轉(zhuǎn)換為浮點值而不進(jìn)行歸一化處理炫惩;
ptr 頂點的緩沖數(shù)據(jù)。
2.2 啟用或者禁用頂點屬性數(shù)組阿浓。調(diào)用glEnableVertexAttribArray和glDisableVertexAttribArray傳入?yún)?shù)index他嚷。如果啟用,那么當(dāng)glDrawArrays或者glDrawElements被調(diào)用時芭毙,頂點屬性數(shù)組會被使用筋蓖。
2.3 glActiveTexture,選擇活動紋理單元退敦。
函數(shù)原型:
void glActiveTexture (int texture)
參數(shù)含義:
texture指定哪一個紋理單元被置為活動狀態(tài)粘咖。texture必須是GL_TEXTUREi之一,其中0 <= i < GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS侈百,初始值為GL_TEXTURE0瓮下。
glActiveTexture()確定了后續(xù)的紋理狀態(tài)改變影響哪個紋理翰铡,紋理單元的數(shù)量是依據(jù)該紋理單元所被支持的具體實現(xiàn)。