現(xiàn)代OpenGL渲染管線嚴(yán)重依賴著色器來(lái)處理出入的數(shù)據(jù),如果不使用著色器,那么OpenGL可以處理的事情可能只有清除窗口了,可見著色器對(duì)OpenGL的重要性。在3.0版本(含3.0)以前,如果用到了兼容模式環(huán)境,OpenGL還包含一個(gè)固定渲染管線,可以在不使用著色器的情況下處理幾何與像素?cái)?shù)據(jù)。自從3.1版本開始,固定渲染管線從核心模式中去除,因此必須使用著色器來(lái)完成工作。
GLSL簡(jiǎn)介
OpenGL著色語(yǔ)言(OpenGL Shading Language)是用來(lái)在OpenGL中著色編程的語(yǔ)言,也即開發(fā)人員寫的短小的自定義程序,他們是在圖形卡的GPU (Graphic Processor Unit圖形處理單元)上執(zhí)行的,代替了固定的渲染管線的一部分,使渲染管線中不同層次具有可編程性。比如:視圖轉(zhuǎn)換、投影轉(zhuǎn)換等。GLSL(GL Shading Language)的著色器代碼分成2個(gè)部分:Vertex Shader(頂點(diǎn)著色器)和Fragment(片斷著色器),有時(shí)還會(huì)有Geometry Shader(幾何著色器)。負(fù)責(zé)運(yùn)行頂點(diǎn)著色的是頂點(diǎn)著色器。它可以得到當(dāng)前OpenGL 中的狀態(tài),GLSL內(nèi)置變量進(jìn)行傳遞。GLSL其使用C語(yǔ)言作為基礎(chǔ)高階著色語(yǔ)言,避免了使用匯編語(yǔ)言或硬件規(guī)格語(yǔ)言的復(fù)雜性。----GLSL百度百科.
GLSL是OpenGL使用的著色器語(yǔ)言,它是一個(gè)C-like語(yǔ)言,語(yǔ)法和C極為相似,區(qū)別在于:
- 渲染用的著色器是由兩部分組成,用于控制頂點(diǎn)變換的頂點(diǎn)著色器和用于控制像素著色的片元著色器.
- 使用#version XXX來(lái)聲明版本,比如#version 120為使用1.2版GLSL規(guī)范.
- 數(shù)據(jù)類型有限,沒有指針,在早期版本(比如1.1,1.2)中標(biāo)量只有float,int和bool,后期版本加入了uint,double.
- 由于沒有內(nèi)存分配和指針,所有數(shù)組必須是定長(zhǎng)數(shù)組.
- 有矢量數(shù)據(jù)類型,比如vec2、vec3和vec4分別是二三四維float向量,mat2、mat3和mat4分別是2x2,3x3,4x4 float矩陣,matNxM為N*M矩陣,最大為4x4.對(duì)于向量類型,可以通過(guò).xyzw、.rgba或.stpq來(lái)訪問(wèn)分量,這三種寫法沒有實(shí)質(zhì)差別,比如對(duì)一個(gè)vec4變量somevec,somevec.xyz和somevec.rgb都是抽取前三個(gè)分量作為一個(gè)vec3,somevec.w和somevec.a都是取最后一個(gè)分量作為float變量,這三種寫法純粹是供人們閱讀方便的,但是不可以混合使用,比如somevec.xgpw就是不友善的寫法.
- 有采樣源類型,比如sampler2D就是一個(gè)2D紋理.
- 有特殊的變量修飾符,比如attribute定點(diǎn)變量,在頂點(diǎn)著色器中可用,每個(gè)頂點(diǎn)均有它獨(dú)特的值;uniform一致變量,任何著色器均可用,用于存儲(chǔ)常量,在一次渲染(即一次DrawCall)中uniform可以視為常量;varying可插值傳遞變量,由頂點(diǎn)著色器傳遞給片元著色器的變量,當(dāng)一個(gè)三角形上不同頂點(diǎn)間的輸出值不同時(shí),會(huì)被插值然后傳遞給其上的像素.
- 特殊的形參修飾符,in輸入?yún)?shù),也是什么都不寫時(shí)的默認(rèn)選項(xiàng),只可讀不可寫(wiki上說(shuō)的是可寫但結(jié)果不會(huì)影響實(shí)參,就像C那樣,但在我這里有時(shí)可以這樣做,有時(shí)卻不行...鬧不明白);out輸出參數(shù),函數(shù)可以向這個(gè)參數(shù)輸出值,結(jié)果會(huì)被反饋給實(shí)參,以此可以實(shí)現(xiàn)多輸出函數(shù);inout引用參數(shù),即可寫又可讀,結(jié)果會(huì)被反饋給實(shí)參.
- 對(duì)int和相關(guān)的整數(shù)操作與位運(yùn)算的支持到1.3才出現(xiàn).
- 沒有字符串.
共同點(diǎn)也是有不少的:
- 幾乎一摸一樣的語(yǔ)法,除了沒有字符串和指針以外.
- 同樣要求函數(shù)聲明必須在調(diào)用之前.
- 都支持預(yù)處理器,格式相同,包括讓編譯器程序員吐血的多行預(yù)處理.
- 支持結(jié)構(gòu)體.
在OpenGL中,GLSL的著色器shader使用的流程與C語(yǔ)言相似,每個(gè)shader類似一個(gè)C模塊,首先需要單獨(dú)編譯(compile),然后一組編譯好的shader連接(link)成一個(gè)完整程序。一般我們?cè)趧?chuàng)建著色器文件時(shí)的文件后綴為:.vsh和.fsh(verterx shader/ fragment shader)
在學(xué)習(xí)著色器代碼之前我們先來(lái)看下三種變量修飾符:
- uniform修飾符
外部(客戶端)程序傳遞到頂點(diǎn)著色器和片元著色器,
(1).在客戶端中會(huì)提供接口.glUniform 來(lái)傳遞相關(guān)的數(shù)據(jù),提供賦值功能。
(2).類似于const的作用。即被uniform修飾的變量在頂點(diǎn)和片元著色器中就不會(huì)被修改,只能使用。
2.attribute修飾符
attribute是用來(lái)修飾屬性變量的修飾符,且只能在頂點(diǎn)著色器使用
- varying修飾符
中間傳遞功能,在頂點(diǎn)著色器和片元著色器之間傳遞。
下面我們來(lái)看一下相關(guān)著色器代碼格式:
頂點(diǎn)著色器shaderv.vsh
//頂點(diǎn)坐標(biāo)
attribute vec4 position; // vec4四維向量
//紋理坐標(biāo)
attribute vec2 textCoordinate; // vec2 二維向量
//為了傳遞紋理坐標(biāo)
varying lowp vec2 varyTextCoord;
void main() {
// 通過(guò)varying修飾的varyTextCoord,將紋理坐標(biāo)傳遞到片元著色器
varyTextCoord = textCoordinate;
//給內(nèi)建變量gl_Position賦值(必須賦值)
gl_Position = position;
}
片元著色器shaderf.fsh
//傳遞過(guò)來(lái)的紋理坐標(biāo)
varying lowp vec2 varyTextCoord;
// 紋理采樣器 (獲取對(duì)應(yīng)的紋理ID)
uniform sampler2D colorMap;
void main() {
//將紋理顏色添加到對(duì)應(yīng)的像素點(diǎn)上
gl_FragColor = texture2D(colorMap, varyTextCoord);
//返回值應(yīng)該是一個(gè)vec4 即是RGBA--顏色值。
}
gl_FragColor GLSL內(nèi)建變量 (賦值像素點(diǎn)顏色值)GLSL語(yǔ)言已經(jīng)提前定義好的變量,有相應(yīng)的特殊含義。
內(nèi)建函數(shù) GLSL提前封裝好的函數(shù)
texture2D(紋理采樣器,紋理坐標(biāo)),獲取對(duì)應(yīng)坐標(biāo)紋素(讀取紋素,讀取每一個(gè)像素點(diǎn)的顏色值)。
著色器和程序
關(guān)于頂點(diǎn)著色器和片元著色器的內(nèi)容已經(jīng)在前面的文章中介紹過(guò),詳情請(qǐng)看OpenGL ES頂點(diǎn)著色器和片元著色器.
我們需要?jiǎng)?chuàng)建兩個(gè)對(duì)象才能用著色器進(jìn)行渲染:著色器對(duì)象和程序?qū)ο?/strong>,類似編譯器和鏈接程序。 1.創(chuàng)建著色器 type —?jiǎng)?chuàng)建著色器類型????????????????,GL_VERTEX_SHADER(頂點(diǎn)) ??和GL_FRAGMENT_SHADER ?????? (片元) 注:我們可以創(chuàng)建多個(gè)shader,但是所有的頂點(diǎn)shader只能有一個(gè)main函數(shù),片元shader也一樣。 2. 刪除著色器 shader -- ????????????????????????????????????????????要?jiǎng)h除的著色器對(duì)象的句柄。 注:如意一個(gè)著色器對(duì)象調(diào)用了glDeleteShader之后還鏈接在程序中,則說(shuō)明這個(gè)著色器并沒有被真正刪除,當(dāng)調(diào)用glDetachShader,將著色器從程序中分離之后,才會(huì)被真正刪除,所以在調(diào)用glDeleteShader之前應(yīng)該先調(diào)用glDetachShader。 3. 鏈接源代碼到著色器對(duì)象 shader -- 指向著色器對(duì)象的句柄。 4. 編譯著色器對(duì)象 shader -- 需要編譯的著色器對(duì)象句柄 5. 獲取編譯結(jié)果 獲得編譯階段著色器的狀態(tài) 6. 獲取編譯錯(cuò)誤日志 獲得鏈接階段著色器的狀態(tài),如果發(fā)生錯(cuò)誤,這個(gè)日志保存的是最后一次操作的信息。 7. 創(chuàng)建程序?qū)ο?/strong> 返回值 -- 返回一個(gè)執(zhí)行新程序?qū)ο蟮木浔?/p>
注:也可以創(chuàng)建多個(gè)程序?qū)ο?,在渲染時(shí),可以在不同程序切換。 8. 刪除一個(gè)程序?qū)ο?/strong> program -- 指向需要?jiǎng)h除的程序?qū)ο缶浔?/p>
9.鏈接(附著)著色器和程序 program -- 指向程序?qū)ο蟮木浔?br>
shader -- 指向程序鏈接的著色器對(duì)象句柄。 注:如果同時(shí)有頂點(diǎn)著色器和片元著色器,需要把它們都鏈接到同一個(gè)程序中。就如同一個(gè)C程序有多個(gè)功能模塊一樣,我們可以把很多個(gè)相同類型的頂點(diǎn)著色器或者片元著色器鏈接到一個(gè)程序中,但是它們只有一個(gè)main函數(shù)。當(dāng)然,也可以把一個(gè)著色器對(duì)象鏈接到不同的程序中。 10. 斷開著色器與程序鏈接 即是 將著色器從程序中分離 11.連接程序?qū)ο?/strong> program -- 指向程序?qū)ο蟮木浔?/p>
12.檢查鏈接是否成功 檢查鏈接是否成功,即檢查鏈接狀態(tài)。 13. 獲取程序信息日志 program -- 指向需要獲取信息日志的程序?qū)ο缶浔?br>
maxLength -- 存儲(chǔ)信息日志的緩存區(qū)大小。 14. 使用程序?qū)ο?/strong> program -- 在程序?qū)ο箧溄映晒χ?,設(shè)置為活動(dòng)程序的程序?qū)ο缶浔闯晒χ笫褂贸绦驅(qū)ο蟆?/p>
注:如果一個(gè)程序被使用后,如果被再次鏈接,則程序被自動(dòng)替代并使用,不需要再次調(diào)用使用函數(shù)。 下面我們用一份代碼來(lái)看一下這個(gè)創(chuàng)建和鏈接的過(guò)程(代碼比較簡(jiǎn)陋,不足之處望見諒指正): 補(bǔ)充內(nèi)容:
著色器源代碼被編譯成一個(gè)目標(biāo)形式(類似obj文件),編譯之后,著色器對(duì)象可以連接到一個(gè)程序?qū)ο?,程序?qū)ο罂梢赃B接多個(gè)著色器對(duì)象。在OpenGLES中,每個(gè)程序?qū)ο蟊仨氭溄右粋€(gè)頂點(diǎn)著色器和一個(gè)片元著色器。程序?qū)ο蟊绘溄訛橛糜阡秩镜淖詈蟆翱蓤?zhí)行程序”。
獲得連接后的著色器對(duì)象的過(guò)程一般包括6個(gè)步驟:
1.創(chuàng)建一個(gè)頂點(diǎn)著色器和一個(gè)片元著色器:
2.將源代碼連接到每個(gè)著色器對(duì)象
3.編譯著色器對(duì)象
4.創(chuàng)建一個(gè)程序?qū)ο?br>
5.將編譯后的著色器對(duì)象連接到程序?qū)ο?br>
6.連接程序?qū)ο?/p>
GLuint glCreateShader(GLenum type);
返回值 — ???????????????????????? 是指向新的著色器對(duì)象的句柄,可以調(diào)用??????glDeleteShader ????刪除。
void glDeleteShader(GLuint shader);
void glShaderSource(GLuint shader , GLSizei count ,const GLChar * con st *string, const GLint *length);
count -- 著色器源字符串的數(shù)量,著色器可以由多個(gè)源字符串組成,但是每個(gè)著色器只有一個(gè)main函數(shù)。
string -- 指向保存數(shù)量的count的著色器源字符串的數(shù)組指針。
length -- 指向保存每個(gè)著色器源字符串大小且元素?cái)?shù)量為count的整數(shù)數(shù)組指針。void glCompileShader(GLuint shader);
void glGetShaderiv(GLuint shader , GLenum pname , GLint *params );
shader -- 需要編譯的著色器對(duì)象句柄。
pname -- 獲取的信息參數(shù)?????? 可以為:GL_COMPILE_STATUS
GL_DELETE_STATUS
GL_INFO_LOG_LENGTH GL_SHADER_SOURCE_LENGTH GL_SHADER_TYPE
params -- 指向查詢結(jié)果的整數(shù)存儲(chǔ)位置的指針。void glGetShaderInfolog(GLuint shader , GLSizei maxLength, GLSizei *l ength , GLChar *infoLog);
shader -- 需要獲取信息日志的著色器對(duì)象句柄。
maxLength -- 保存信息日志的緩存區(qū)大小。
length -- 寫入的信息日志的長(zhǎng)度(減去null 終止符);如果不需要知道長(zhǎng)度,這個(gè)參數(shù)可以為Null。
infoLog -- 指向保存信息日志的字符緩存區(qū)的指針。GLUint glCreateProgram( )
void glDeleteProgram( GLuint program )
void glAttachShader( GLuint program , GLuint shader );
void glDetachShader(GLuint program, GLuint shader);
program -- 指向程序?qū)ο蟮木浔?br>
shader -- 指向程序鏈接的著色器對(duì)象句柄。void glLinkProgram(GLuint program)
void glGetProgramiv (GLuint program,GLenum pname, GLint *params);
program -- 指向需要獲取信息的程序?qū)ο蟮木浔?br>
pname -- 獲取信息的參數(shù)
可以為:
GL_ACTIVE_ATTRIBUTES GL_ACTIVE_ATTRIBUTES_MAX_LENGTH GL_ACTIVE_UNIFORM_BLOCK GL_ACTIVE_UNIFORM_BLOCK_MAX_LENGTH GL_ACTIVE_UNIFROMS GL_ACTIVE_UNIFORM_MAX_LENGTH GL_ATTACHED_SHADERS GL_DELETE_STATUS GL_INFO_LOG_LENGTH
GL_LINK_STATUS GL_PROGRAM_BINARY_RETRIEVABLE_HINT GL_TRANSFORM_FEEDBACK_BUFFER_MODE GL_TRANSFORM_FEEDBACK_VARYINGS GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH GL_VALIDATE_STATUS
params -- 指向查詢結(jié)果整數(shù)存儲(chǔ)位置的指針。void glGetPorgramInfoLog( GLuint program ,GLSizei maxLength, GLSize i *length , GLChar *infoLog )
length -- 寫入的信息日志的長(zhǎng)度(減去null 終止符);如果不需要知道長(zhǎng)度,這個(gè)參數(shù)可以為Null。
infoLog -- 指向保存信息日志的字符緩存區(qū)的指針。void glUseProgram(GLuint program)
下面我們根據(jù)上述函數(shù)繪制一個(gè)著色器和程序的流程圖:
如果使用的參數(shù)是0,則表示使用固定功能流水線。void setShaders() {
NSString *vert = [[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];
NSString *frag = [[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];
NSString* vertcontent = [NSString stringWithContentsOfFile:vert encoding:NSUTF8StringEncoding error:nil];
const GLchar* vertsource = (GLchar *)[vertcontent UTF8String];
NSString* fragcontent = [NSString stringWithContentsOfFile:frag encoding:NSUTF8StringEncoding error:nil];
const GLchar* fragsource = (GLchar *)[fragcontent UTF8String];
//定義2個(gè)零時(shí)著色器對(duì)象
GLuint verShader, fragShader;
verShader = glCreateShader(GL_VERTEX_SHADER);
fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(verShader, 1, &vertsource, NULL);
glShaderSource(fragShader, 1, &fragcontent, NULL);
glCompileShader(verShader);
glCompileShader(fragShader);
//3.創(chuàng)建program
GLint program = glCreateProgram();
//4.創(chuàng)建最終的程序
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
//5.釋放不需要的shader
glDeleteShader(verShader);
glDeleteShader(fragShader);
//6.鏈接
glLinkProgram(self.myPrograme);
GLint linkStatus;
//獲取鏈接狀態(tài)
glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
GLchar message[512];
glGetProgramInfoLog(self.myPrograme, sizeof(message), 0, &message[0]);
NSString *messageString = [NSString stringWithUTF8String:message];
NSLog(@"Program Link Error:%@",messageString);
return;
}
NSLog(@"Program Link Success!");
//7.使用program
glUseProgram(self.myPrograme);
}
OpenGL ES 錯(cuò)誤處理
如果不正確使用OpenGL ES 命令,應(yīng)用程序就會(huì)產(chǎn)生一個(gè)錯(cuò)誤編碼,這個(gè)錯(cuò)誤編碼將會(huì)被記錄,可以調(diào)用glGetError查詢,在應(yīng)用程序調(diào)用glGetError查詢第一個(gè)錯(cuò)誤代碼之前,不會(huì)記錄其他錯(cuò)誤代碼,一旦查詢到錯(cuò)誤代碼,當(dāng)前錯(cuò)誤代碼便會(huì)復(fù)位為GL_NO_ERROR.GLenum glGetError(void);

