現(xiàn)代OpenGL渲染管線嚴重依賴著色器來處理出入的數(shù)據(jù)榴都,如果不使用著色器,那么OpenGL可以處理的事情可能只有清除窗口了纵隔,可見著色器對OpenGL的重要性席函。在3.0版本(含3.0)以前,如果用到了兼容模式環(huán)境楚昭,OpenGL還包含一個固定渲染管線栖袋,可以在不使用著色器的情況下處理幾何與像素數(shù)據(jù)。自從3.1版本開始抚太,固定渲染管線從核心模式中去除塘幅,因此必須使用著色器來完成工作。
GLSL簡介
OpenGL著色語言(OpenGL Shading Language)是用來在OpenGL中著色編程的語言尿贫,也即開發(fā)人員寫的短小的自定義程序电媳,他們是在圖形卡的GPU (Graphic Processor Unit圖形處理單元)上執(zhí)行的,代替了固定的渲染管線的一部分庆亡,使渲染管線中不同層次具有可編程性匾乓。比如:視圖轉(zhuǎn)換、投影轉(zhuǎn)換等又谋。GLSL(GL Shading Language)的著色器代碼分成2個部分:Vertex Shader(頂點著色器)和Fragment(片斷著色器)拼缝,有時還會有Geometry Shader(幾何著色器)括享。負責運行頂點著色的是頂點著色器。它可以得到當前OpenGL 中的狀態(tài)珍促,GLSL內(nèi)置變量進行傳遞。GLSL其使用C語言作為基礎(chǔ)高階著色語言剩愧,避免了使用匯編語言或硬件規(guī)格語言的復雜性猪叙。----GLSL百度百科.
GLSL是OpenGL使用的著色器語言,它是一個C-like語言,語法和C極為相似,區(qū)別在于:
- 渲染用的著色器是由兩部分組成,用于控制頂點變換的頂點著色器和用于控制像素著色的片元著色器.
- 使用#version XXX來聲明版本,比如#version 120為使用1.2版GLSL規(guī)范.
- 數(shù)據(jù)類型有限,沒有指針,在早期版本(比如1.1,1.2)中標量只有float,int和bool,后期版本加入了uint,double.
- 由于沒有內(nèi)存分配和指針,所有數(shù)組必須是定長數(shù)組.
- 有矢量數(shù)據(jù)類型,比如vec2、vec3和vec4分別是二三四維float向量,mat2仁卷、mat3和mat4分別是2x2,3x3,4x4 float矩陣,matNxM為N*M矩陣,最大為4x4.對于向量類型,可以通過.xyzw穴翩、.rgba或.stpq來訪問分量,這三種寫法沒有實質(zhì)差別,比如對一個vec4變量somevec,somevec.xyz和somevec.rgb都是抽取前三個分量作為一個vec3,somevec.w和somevec.a都是取最后一個分量作為float變量,這三種寫法純粹是供人們閱讀方便的,但是不可以混合使用,比如somevec.xgpw就是不友善的寫法.
- 有采樣源類型,比如sampler2D就是一個2D紋理.
- 有特殊的變量修飾符,比如attribute定點變量,在頂點著色器中可用,每個頂點均有它獨特的值;uniform一致變量,任何著色器均可用,用于存儲常量,在一次渲染(即一次DrawCall)中uniform可以視為常量;varying可插值傳遞變量,由頂點著色器傳遞給片元著色器的變量,當一個三角形上不同頂點間的輸出值不同時,會被插值然后傳遞給其上的像素.
- 特殊的形參修飾符,in輸入?yún)?shù),也是什么都不寫時的默認選項,只可讀不可寫(wiki上說的是可寫但結(jié)果不會影響實參,就像C那樣,但在我這里有時可以這樣做,有時卻不行...鬧不明白);out輸出參數(shù),函數(shù)可以向這個參數(shù)輸出值,結(jié)果會被反饋給實參,以此可以實現(xiàn)多輸出函數(shù);inout引用參數(shù),即可寫又可讀,結(jié)果會被反饋給實參.
- 對int和相關(guān)的整數(shù)操作與位運算的支持到1.3才出現(xiàn).
- 沒有字符串.
共同點也是有不少的:
- 幾乎一摸一樣的語法,除了沒有字符串和指針以外.
- 同樣要求函數(shù)聲明必須在調(diào)用之前.
- 都支持預處理器,格式相同,包括讓編譯器程序員吐血的多行預處理.
- 支持結(jié)構(gòu)體.
在OpenGL中,GLSL的著色器shader使用的流程與C語言相似锦积,每個shader類似一個C模塊芒帕,首先需要單獨編譯(compile),然后一組編譯好的shader連接(link)成一個完整程序丰介。一般我們在創(chuàng)建著色器文件時的文件后綴為:.vsh和.fsh(verterx shader/ fragment shader)
在學習著色器代碼之前我們先來看下三種變量修飾符:
- uniform修飾符
外部(客戶端)程序傳遞到頂點著色器和片元著色器背蟆,
(1).在客戶端中會提供接口.glUniform 來傳遞相關(guān)的數(shù)據(jù),提供賦值功能哮幢。
(2).類似于const的作用带膀。即被uniform修飾的變量在頂點和片元著色器中就不會被修改,只能使用橙垢。
2.attribute修飾符
attribute是用來修飾屬性變量的修飾符垛叨,且只能在頂點著色器使用
- varying修飾符
中間傳遞功能,在頂點著色器和片元著色器之間傳遞柜某。
下面我們來看一下相關(guān)著色器代碼格式:
頂點著色器shaderv.vsh
//頂點坐標
attribute vec4 position; // vec4四維向量
//紋理坐標
attribute vec2 textCoordinate; // vec2 二維向量
//為了傳遞紋理坐標
varying lowp vec2 varyTextCoord;
void main() {
// 通過varying修飾的varyTextCoord嗽元,將紋理坐標傳遞到片元著色器
varyTextCoord = textCoordinate;
//給內(nèi)建變量gl_Position賦值(必須賦值)
gl_Position = position;
}
片元著色器shaderf.fsh
//傳遞過來的紋理坐標
varying lowp vec2 varyTextCoord;
// 紋理采樣器 (獲取對應的紋理ID)
uniform sampler2D colorMap;
void main() {
//將紋理顏色添加到對應的像素點上
gl_FragColor = texture2D(colorMap, varyTextCoord);
//返回值應該是一個vec4 即是RGBA--顏色值。
}
gl_FragColor GLSL內(nèi)建變量 (賦值像素點顏色值)GLSL語言已經(jīng)提前定義好的變量喂击,有相應的特殊含義剂癌。
內(nèi)建函數(shù) GLSL提前封裝好的函數(shù)
texture2D(紋理采樣器,紋理坐標)惭等,獲取對應坐標紋素(讀取紋素珍手,讀取每一個像素點的顏色值)。
著色器和程序
關(guān)于頂點著色器和片元著色器的內(nèi)容已經(jīng)在前面的文章中介紹過辞做,詳情請看OpenGL ES頂點著色器和片元著色器.
我們需要創(chuàng)建兩個對象才能用著色器進行渲染:著色器對象和程序?qū)ο?/strong>琳要,類似編譯器和鏈接程序。 1.創(chuàng)建著色器 type —創(chuàng)建著色器類型????????????????,GL_VERTEX_SHADER(頂點) ??和GL_FRAGMENT_SHADER ?????? (片元) 注:我們可以創(chuàng)建多個shader,但是所有的頂點shader只能有一個main函數(shù),片元shader也一樣润绎。 2. 刪除著色器 shader -- ????????????????????????????????????????????要刪除的著色器對象的句柄撬碟。 注:如意一個著色器對象調(diào)用了glDeleteShader之后還鏈接在程序中,則說明這個著色器并沒有被真正刪除莉撇,當調(diào)用glDetachShader呢蛤,將著色器從程序中分離之后,才會被真正刪除棍郎,所以在調(diào)用glDeleteShader之前應該先調(diào)用glDetachShader其障。 3. 鏈接源代碼到著色器對象 shader -- 指向著色器對象的句柄。 4. 編譯著色器對象 shader -- 需要編譯的著色器對象句柄 5. 獲取編譯結(jié)果 獲得編譯階段著色器的狀態(tài) 6. 獲取編譯錯誤日志 獲得鏈接階段著色器的狀態(tài)殊橙,如果發(fā)生錯誤,這個日志保存的是最后一次操作的信息狱从。 7. 創(chuàng)建程序?qū)ο?/strong> 返回值 -- 返回一個執(zhí)行新程序?qū)ο蟮木浔?/p>
注:也可以創(chuàng)建多個程序?qū)ο笕切常阡秩緯r,可以在不同程序切換驼卖。 8. 刪除一個程序?qū)ο?/strong> program -- 指向需要刪除的程序?qū)ο缶浔?/p>
9.鏈接(附著)著色器和程序 program -- 指向程序?qū)ο蟮木浔?br>
shader -- 指向程序鏈接的著色器對象句柄氨肌。 注:如果同時有頂點著色器和片元著色器,需要把它們都鏈接到同一個程序中酌畜。就如同一個C程序有多個功能模塊一樣怎囚,我們可以把很多個相同類型的頂點著色器或者片元著色器鏈接到一個程序中,但是它們只有一個main函數(shù)桥胞。當然恳守,也可以把一個著色器對象鏈接到不同的程序中考婴。 10. 斷開著色器與程序鏈接 即是 將著色器從程序中分離 11.連接程序?qū)ο?/strong> program -- 指向程序?qū)ο蟮木浔?/p>
12.檢查鏈接是否成功 檢查鏈接是否成功催烘,即檢查鏈接狀態(tài)沥阱。 13. 獲取程序信息日志 program -- 指向需要獲取信息日志的程序?qū)ο缶浔?br>
maxLength -- 存儲信息日志的緩存區(qū)大小伊群。 14. 使用程序?qū)ο?/strong> program -- 在程序?qū)ο箧溄映晒χ舐牛O(shè)置為活動程序的程序?qū)ο缶浔挝纾闯晒χ笫褂贸绦驅(qū)ο蟆?/p>
注:如果一個程序被使用后,如果被再次鏈接酬蹋,則程序被自動替代并使用及老,不需要再次調(diào)用使用函數(shù)。 下面我們用一份代碼來看一下這個創(chuàng)建和鏈接的過程(代碼比較簡陋,不足之處望見諒指正): 補充內(nèi)容:
著色器源代碼被編譯成一個目標形式(類似obj文件)秤茅,編譯之后稚补,著色器對象可以連接到一個程序?qū)ο螅绦驅(qū)ο罂梢赃B接多個著色器對象框喳。在OpenGLES中课幕,每個程序?qū)ο蟊仨氭溄右粋€頂點著色器和一個片元著色器厦坛。程序?qū)ο蟊绘溄訛橛糜阡秩镜淖詈蟆翱蓤?zhí)行程序”。
獲得連接后的著色器對象的過程一般包括6個步驟:
1.創(chuàng)建一個頂點著色器和一個片元著色器:
2.將源代碼連接到每個著色器對象
3.編譯著色器對象
4.創(chuàng)建一個程序?qū)ο?br>
5.將編譯后的著色器對象連接到程序?qū)ο?br>
6.連接程序?qū)ο?/p>
GLuint glCreateShader(GLenum type);
返回值 — ???????????????????????? 是指向新的著色器對象的句柄乍惊,可以調(diào)用??????glDeleteShader ????刪除杜秸。
void glDeleteShader(GLuint shader);
void glShaderSource(GLuint shader , GLSizei count ,const GLChar * con st *string, const GLint *length);
count -- 著色器源字符串的數(shù)量涂佃,著色器可以由多個源字符串組成励翼,但是每個著色器只有一個main函數(shù)。
string -- 指向保存數(shù)量的count的著色器源字符串的數(shù)組指針辜荠。
length -- 指向保存每個著色器源字符串大小且元素數(shù)量為count的整數(shù)數(shù)組指針抚笔。void glCompileShader(GLuint shader);
void glGetShaderiv(GLuint shader , GLenum pname , GLint *params );
shader -- 需要編譯的著色器對象句柄。
pname -- 獲取的信息參數(shù)?????? 可以為:GL_COMPILE_STATUS
GL_DELETE_STATUS
GL_INFO_LOG_LENGTH GL_SHADER_SOURCE_LENGTH GL_SHADER_TYPE
params -- 指向查詢結(jié)果的整數(shù)存儲位置的指針侨拦。void glGetShaderInfolog(GLuint shader , GLSizei maxLength, GLSizei *l ength , GLChar *infoLog);
shader -- 需要獲取信息日志的著色器對象句柄膨蛮。
maxLength -- 保存信息日志的緩存區(qū)大小。
length -- 寫入的信息日志的長度(減去null 終止符)季研;如果不需要知道長度敞葛,這個參數(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 -- 指向程序鏈接的著色器對象句柄。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ù)存儲位置的指針。void glGetPorgramInfoLog( GLuint program ,GLSizei maxLength, GLSize i *length , GLChar *infoLog )
length -- 寫入的信息日志的長度(減去null 終止符)喳钟;如果不需要知道長度,這個參數(shù)可以為Null在岂。
infoLog -- 指向保存信息日志的字符緩存區(qū)的指針。void glUseProgram(GLuint program)
下面我們根據(jù)上述函數(shù)繪制一個著色器和程序的流程圖:
如果使用的參數(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個零時著色器對象
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 錯誤處理
如果不正確使用OpenGL ES 命令匕垫,應用程序就會產(chǎn)生一個錯誤編碼僧鲁,這個錯誤編碼將會被記錄,可以調(diào)用glGetError查詢象泵,在應用程序調(diào)用glGetError查詢第一個錯誤代碼之前寞秃,不會記錄其他錯誤代碼,一旦查詢到錯誤代碼偶惠,當前錯誤代碼便會復位為GL_NO_ERROR.GLenum glGetError(void);