OpenGL ES 入門之旅 -- GLSL初識(shí)著色器語(yǔ)言

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

  1. 渲染用的著色器是由兩部分組成,用于控制頂點(diǎn)變換的頂點(diǎn)著色器和用于控制像素著色的片元著色器.
  2. 使用#version XXX來(lái)聲明版本,比如#version 120為使用1.2版GLSL規(guī)范.
  3. 數(shù)據(jù)類型有限,沒有指針,在早期版本(比如1.1,1.2)中標(biāo)量只有float,int和bool,后期版本加入了uint,double.
  4. 由于沒有內(nèi)存分配和指針,所有數(shù)組必須是定長(zhǎng)數(shù)組.
  5. 有矢量數(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就是不友善的寫法.
  6. 有采樣源類型,比如sampler2D就是一個(gè)2D紋理.
  7. 有特殊的變量修飾符,比如attribute定點(diǎn)變量,在頂點(diǎn)著色器中可用,每個(gè)頂點(diǎn)均有它獨(dú)特的值;uniform一致變量,任何著色器均可用,用于存儲(chǔ)常量,在一次渲染(即一次DrawCall)中uniform可以視為常量;varying可插值傳遞變量,由頂點(diǎn)著色器傳遞給片元著色器的變量,當(dāng)一個(gè)三角形上不同頂點(diǎn)間的輸出值不同時(shí),會(huì)被插值然后傳遞給其上的像素.
  8. 特殊的形參修飾符,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í)參.
  9. 對(duì)int和相關(guān)的整數(shù)操作與位運(yùn)算的支持到1.3才出現(xiàn).
  10. 沒有字符串.

共同點(diǎn)也是有不少的:

  1. 幾乎一摸一樣的語(yǔ)法,除了沒有字符串和指針以外.
  2. 同樣要求函數(shù)聲明必須在調(diào)用之前.
  3. 都支持預(yù)處理器,格式相同,包括讓編譯器程序員吐血的多行預(yù)處理.
  4. 支持結(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)看下三種變量修飾符:

  1. 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)著色器使用

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

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

GLuint glCreateShader(GLenum type);

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

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

2. 刪除著色器

void glDeleteShader(GLuint shader);

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ì)象

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

shader -- 指向著色器對(duì)象的句柄。
count -- 著色器源字符串的數(shù)量,著色器可以由多個(gè)源字符串組成,但是每個(gè)著色器只有一個(gè)main函數(shù)。
string -- 指向保存數(shù)量的count的著色器源字符串的數(shù)組指針。
length -- 指向保存每個(gè)著色器源字符串大小且元素?cái)?shù)量為count的整數(shù)數(shù)組指針。

4. 編譯著色器對(duì)象

void glCompileShader(GLuint shader);

shader -- 需要編譯的著色器對(duì)象句柄

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

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

獲得編譯階段著色器的狀態(tài)
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ǔ)位置的指針。

6. 獲取編譯錯(cuò)誤日志

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

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

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

GLUint glCreateProgram( )

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

注:也可以創(chuàng)建多個(gè)程序?qū)ο?,在渲染時(shí),可以在不同程序切換。

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

void glDeleteProgram( GLuint program )

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

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

void glAttachShader( GLuint program , GLuint shader );

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

注:如果同時(shí)有頂點(diǎn)著色器和片元著色器,需要把它們都鏈接到同一個(gè)程序中。就如同一個(gè)C程序有多個(gè)功能模塊一樣,我們可以把很多個(gè)相同類型的頂點(diǎn)著色器或者片元著色器鏈接到一個(gè)程序中,但是它們只有一個(gè)main函數(shù)。當(dāng)然,也可以把一個(gè)著色器對(duì)象鏈接到不同的程序中。

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

void glDetachShader(GLuint program, GLuint shader);

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

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ù)存儲(chǔ)位置的指針。

13. 獲取程序信息日志

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

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

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

void glUseProgram(GLuint program)

program -- 在程序?qū)ο箧溄映晒χ?,設(shè)置為活動(dòng)程序的程序?qū)ο缶浔闯晒χ笫褂贸绦驅(qū)ο蟆?/p>

注:如果一個(gè)程序被使用后,如果被再次鏈接,則程序被自動(dòng)替代并使用,不需要再次調(diào)用使用函數(shù)。
如果使用的參數(shù)是0,則表示使用固定功能流水線。

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

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

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

}

補(bǔ)充內(nèi)容:
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);
錯(cuò)誤代碼與描述.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容