OpenGL ES 入門之旅 -- GLSL初識著色器語言

現(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ū)別在于:

  1. 渲染用的著色器是由兩部分組成,用于控制頂點變換的頂點著色器和用于控制像素著色的片元著色器.
  2. 使用#version XXX來聲明版本,比如#version 120為使用1.2版GLSL規(guī)范.
  3. 數(shù)據(jù)類型有限,沒有指針,在早期版本(比如1.1,1.2)中標量只有float,int和bool,后期版本加入了uint,double.
  4. 由于沒有內(nèi)存分配和指針,所有數(shù)組必須是定長數(shù)組.
  5. 有矢量數(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就是不友善的寫法.
  6. 有采樣源類型,比如sampler2D就是一個2D紋理.
  7. 有特殊的變量修飾符,比如attribute定點變量,在頂點著色器中可用,每個頂點均有它獨特的值;uniform一致變量,任何著色器均可用,用于存儲常量,在一次渲染(即一次DrawCall)中uniform可以視為常量;varying可插值傳遞變量,由頂點著色器傳遞給片元著色器的變量,當一個三角形上不同頂點間的輸出值不同時,會被插值然后傳遞給其上的像素.
  8. 特殊的形參修飾符,in輸入?yún)?shù),也是什么都不寫時的默認選項,只可讀不可寫(wiki上說的是可寫但結(jié)果不會影響實參,就像C那樣,但在我這里有時可以這樣做,有時卻不行...鬧不明白);out輸出參數(shù),函數(shù)可以向這個參數(shù)輸出值,結(jié)果會被反饋給實參,以此可以實現(xiàn)多輸出函數(shù);inout引用參數(shù),即可寫又可讀,結(jié)果會被反饋給實參.
  9. 對int和相關(guān)的整數(shù)操作與位運算的支持到1.3才出現(xiàn).
  10. 沒有字符串.

共同點也是有不少的:

  1. 幾乎一摸一樣的語法,除了沒有字符串和指針以外.
  2. 同樣要求函數(shù)聲明必須在調(diào)用之前.
  3. 都支持預處理器,格式相同,包括讓編譯器程序員吐血的多行預處理.
  4. 支持結(jié)構(gòu)體.

在OpenGL中,GLSL的著色器shader使用的流程與C語言相似锦积,每個shader類似一個C模塊芒帕,首先需要單獨編譯(compile),然后一組編譯好的shader連接(link)成一個完整程序丰介。一般我們在創(chuàng)建著色器文件時的文件后綴為:.vsh和.fsh(verterx shader/ fragment shader)

在學習著色器代碼之前我們先來看下三種變量修飾符:

  1. uniform修飾符

外部(客戶端)程序傳遞到頂點著色器和片元著色器背蟆,
(1).在客戶端中會提供接口.glUniform 來傳遞相關(guān)的數(shù)據(jù),提供賦值功能哮幢。
(2).類似于const的作用带膀。即被uniform修飾的變量在頂點和片元著色器中就不會被修改,只能使用橙垢。

2.attribute修飾符

attribute是用來修飾屬性變量的修飾符垛叨,且只能在頂點著色器使用

  1. 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>琳要,類似編譯器和鏈接程序。
著色器源代碼被編譯成一個目標形式(類似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>

1.創(chuàng)建著色器

GLuint glCreateShader(GLenum type);

type —創(chuàng)建著色器類型????????????????,GL_VERTEX_SHADER(頂點) ??和GL_FRAGMENT_SHADER ?????? (片元)
返回值 — ???????????????????????? 是指向新的著色器對象的句柄乍惊,可以調(diào)用??????glDeleteShader ????刪除杜秸。

注:我們可以創(chuàng)建多個shader,但是所有的頂點shader只能有一個main函數(shù),片元shader也一樣润绎。

2. 刪除著色器

void glDeleteShader(GLuint shader);

shader -- ????????????????????????????????????????????要刪除的著色器對象的句柄撬碟。

注:如意一個著色器對象調(diào)用了glDeleteShader之后還鏈接在程序中,則說明這個著色器并沒有被真正刪除莉撇,當調(diào)用glDetachShader呢蛤,將著色器從程序中分離之后,才會被真正刪除棍郎,所以在調(diào)用glDeleteShader之前應該先調(diào)用glDetachShader其障。

3. 鏈接源代碼到著色器對象

void glShaderSource(GLuint shader , GLSizei count ,const GLChar * con st *string, const GLint *length);

shader -- 指向著色器對象的句柄。
count -- 著色器源字符串的數(shù)量涂佃,著色器可以由多個源字符串組成励翼,但是每個著色器只有一個main函數(shù)。
string -- 指向保存數(shù)量的count的著色器源字符串的數(shù)組指針辜荠。
length -- 指向保存每個著色器源字符串大小且元素數(shù)量為count的整數(shù)數(shù)組指針抚笔。

4. 編譯著色器對象

void glCompileShader(GLuint shader);

shader -- 需要編譯的著色器對象句柄

5. 獲取編譯結(jié)果

void glGetShaderiv(GLuint shader , GLenum pname , GLint *params );

獲得編譯階段著色器的狀態(tài)
shader -- 需要編譯的著色器對象句柄。
pname -- 獲取的信息參數(shù)?????? 可以為:GL_COMPILE_STATUS
GL_DELETE_STATUS
GL_INFO_LOG_LENGTH GL_SHADER_SOURCE_LENGTH GL_SHADER_TYPE
params -- 指向查詢結(jié)果的整數(shù)存儲位置的指針侨拦。

6. 獲取編譯錯誤日志

void glGetShaderInfolog(GLuint shader , GLSizei maxLength, GLSizei *l ength , GLChar *infoLog);

獲得鏈接階段著色器的狀態(tài)殊橙,如果發(fā)生錯誤,這個日志保存的是最后一次操作的信息狱从。
shader -- 需要獲取信息日志的著色器對象句柄膨蛮。
maxLength -- 保存信息日志的緩存區(qū)大小。
length -- 寫入的信息日志的長度(減去null 終止符)季研;如果不需要知道長度敞葛,這個參數(shù)可以為Null。
infoLog -- 指向保存信息日志的字符緩存區(qū)的指針与涡。

7. 創(chuàng)建程序?qū)ο?/strong>

GLUint glCreateProgram( )

返回值 -- 返回一個執(zhí)行新程序?qū)ο蟮木浔?/p>

注:也可以創(chuàng)建多個程序?qū)ο笕切常阡秩緯r,可以在不同程序切換驼卖。

8. 刪除一個程序?qū)ο?/strong>

void glDeleteProgram( GLuint program )

program -- 指向需要刪除的程序?qū)ο缶浔?/p>

9.鏈接(附著)著色器和程序

void glAttachShader( GLuint program , GLuint shader );

program -- 指向程序?qū)ο蟮木浔?br> shader -- 指向程序鏈接的著色器對象句柄氨肌。

注:如果同時有頂點著色器和片元著色器,需要把它們都鏈接到同一個程序中酌畜。就如同一個C程序有多個功能模塊一樣怎囚,我們可以把很多個相同類型的頂點著色器或者片元著色器鏈接到一個程序中,但是它們只有一個main函數(shù)桥胞。當然恳守,也可以把一個著色器對象鏈接到不同的程序中考婴。

10. 斷開著色器與程序鏈接

void glDetachShader(GLuint program, GLuint shader);

即是 將著色器從程序中分離
program -- 指向程序?qū)ο蟮木浔?br> shader -- 指向程序鏈接的著色器對象句柄。

11.連接程序?qū)ο?/strong>

void glLinkProgram(GLuint program)

program -- 指向程序?qū)ο蟮木浔?/p>

12.檢查鏈接是否成功

void glGetProgramiv (GLuint program,GLenum pname, GLint *params);

檢查鏈接是否成功催烘,即檢查鏈接狀態(tài)沥阱。
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ù)存儲位置的指針。

13. 獲取程序信息日志

void glGetPorgramInfoLog( GLuint program ,GLSizei maxLength, GLSize i *length , GLChar *infoLog )

program -- 指向需要獲取信息日志的程序?qū)ο缶浔?br> maxLength -- 存儲信息日志的緩存區(qū)大小伊群。
length -- 寫入的信息日志的長度(減去null 終止符)喳钟;如果不需要知道長度,這個參數(shù)可以為Null在岂。
infoLog -- 指向保存信息日志的字符緩存區(qū)的指針。

14. 使用程序?qū)ο?/strong>

void glUseProgram(GLuint program)

program -- 在程序?qū)ο箧溄映晒χ舐牛O(shè)置為活動程序的程序?qū)ο缶浔挝纾闯晒χ笫褂贸绦驅(qū)ο蟆?/p>

注:如果一個程序被使用后,如果被再次鏈接酬蹋,則程序被自動替代并使用及老,不需要再次調(diào)用使用函數(shù)。
如果使用的參數(shù)是0范抓,則表示使用固定功能流水線骄恶。

下面我們根據(jù)上述函數(shù)繪制一個著色器和程序的流程圖:
Shader-Program.png

下面我們用一份代碼來看一下這個創(chuàng)建和鏈接的過程(代碼比較簡陋,不足之處望見諒指正):

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);

}

補充內(nèi)容:
OpenGL ES 錯誤處理
如果不正確使用OpenGL ES 命令匕垫,應用程序就會產(chǎn)生一個錯誤編碼僧鲁,這個錯誤編碼將會被記錄,可以調(diào)用glGetError查詢象泵,在應用程序調(diào)用glGetError查詢第一個錯誤代碼之前寞秃,不會記錄其他錯誤代碼,一旦查詢到錯誤代碼偶惠,當前錯誤代碼便會復位為GL_NO_ERROR.

GLenum glGetError(void);
錯誤代碼與描述.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末春寿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子忽孽,更是在濱河造成了極大的恐慌绑改,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兄一,死亡現(xiàn)場離奇詭異厘线,居然都是意外死亡,警方通過查閱死者的電腦和手機出革,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進店門杂曲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人崩侠,你說我怎么就攤上這事∠跞” “怎么了?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵楞抡,是天一觀的道長伟众。 經(jīng)常有香客問我,道長召廷,這世上最難降的妖魔是什么凳厢? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮竞慢,結(jié)果婚禮上先紫,老公的妹妹穿的比我還像新娘。我一直安慰自己筹煮,他們只是感情好遮精,可當我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著败潦,像睡著了一般本冲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上劫扒,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天檬洞,我揣著相機與錄音,去河邊找鬼沟饥。 笑死添怔,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的贤旷。 我是一名探鬼主播澎灸,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遮晚!你這毒婦竟也來了性昭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤县遣,失蹤者是張志新(化名)和其女友劉穎糜颠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萧求,經(jīng)...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡其兴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了夸政。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片元旬。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匀归,到底是詐尸還是另有隱情坑资,我是刑警寧澤,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布穆端,位于F島的核電站袱贮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏体啰。R本人自食惡果不足惜攒巍,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荒勇。 院中可真熱鬧柒莉,春花似錦、人聲如沸沽翔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搀擂。三九已至,卻和暖如春卷玉,著一層夾襖步出監(jiān)牢的瞬間哨颂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工相种, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留威恼,地道東北人。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓寝并,卻偏偏與公主長得像箫措,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衬潦,可洞房花燭夜當晚...
    茶點故事閱讀 45,982評論 2 361

推薦閱讀更多精彩內(nèi)容