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

現代OpenGL渲染管線嚴重依賴着色器來處理出入的數據,若是不使用着色器,那麼OpenGL能夠處理的事情可能只有清除窗口了,可見着色器對OpenGL的重要性。在3.0版本(含3.0)之前,若是用到了兼容模式環境,OpenGL還包含一個固定渲染管線,能夠在不使用着色器的狀況下處理幾何與像素數據。自從3.1版本開始,固定渲染管線從核心模式中去除,所以必須使用着色器來完成工做。程序員

GLSL簡介

OpenGL着色語言(OpenGL Shading Language)是用來在OpenGL中着色編程的語言,也即開發人員寫的短小的自定義程序,他們是在圖形卡的GPU (Graphic Processor Unit圖形處理單元)上執行的,代替了固定的渲染管線的一部分,使渲染管線中不一樣層次具備可編程性。好比:視圖轉換、投影轉換等。GLSL(GL Shading Language)的着色器代碼分紅2個部分:Vertex Shader(頂點着色器)和Fragment(片段着色器),有時還會有Geometry Shader(幾何着色器)。負責運行頂點着色的是頂點着色器。它能夠獲得當前OpenGL 中的狀態,GLSL內置變量進行傳遞。GLSL其使用C語言做爲基礎高階着色語言,避免了使用匯編語言或硬件規格語言的複雜性。----GLSL百度百科.編程

GLSL是OpenGL使用的着色器語言,它是一個C-like語言,語法和C極爲類似,區別在於:數組

  1. 渲染用的着色器是由兩部分組成,用於控制頂點變換的頂點着色器和用於控制像素着色的片元着色器.
  2. 使用#version XXX來聲明版本,好比#version 120爲使用1.2版GLSL規範.
  3. 數據類型有限,沒有指針,在早期版本(好比1.1,1.2)中標量只有float,int和bool,後期版本加入了uint,double.
  4. 因爲沒有內存分配和指針,全部數組必須是定長數組.
  5. 有矢量數據類型,好比vec二、vec3和vec4分別是二三四維float向量,mat二、mat3和mat4分別是2x2,3x3,4x4 float矩陣,matNxM爲N*M矩陣,最大爲4x4.對於向量類型,能夠經過.xyzw、.rgba或.stpq來訪問份量,這三種寫法沒有實質差異,好比對一個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輸入參數,也是什麼都不寫時的默認選項,只可讀不可寫(wiki上說的是可寫但結果不會影響實參,就像C那樣,但在我這裏有時能夠這樣作,有時卻不行...鬧不明白);out輸出參數,函數能夠向這個參數輸出值,結果會被反饋給實參,以此能夠實現多輸出函數;inout引用參數,便可寫又可讀,結果會被反饋給實參.
  9. 對int和相關的整數操做與位運算的支持到1.3纔出現.
  10. 沒有字符串.

共同點也是有很多的:緩存

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

在OpenGL中,GLSL的着色器shader使用的流程與C語言類似,每一個shader相似一個C模塊,首先須要單獨編譯(compile),而後一組編譯好的shader鏈接(link)成一個完整程序。通常咱們在建立着色器文件時的文件後綴爲:.vsh和.fsh(verterx shader/ fragment shader)bash

在學習着色器代碼以前咱們先來看下三種變量修飾符:函數

  1. uniform修飾符

外部(客戶端)程序傳遞到頂點着色器和片元着色器, (1).在客戶端中會提供接口.glUniform 來傳遞相關的數據,提供賦值功能。 (2).相似於const的做用。即被uniform修飾的變量在頂點和片元着色器中就不會被修改,只能使用。學習

2.attribute修飾符ui

attribute是用來修飾屬性變量的修飾符,且只能在頂點着色器使用編碼

  1. varying修飾符

中間傳遞功能,在頂點着色器和片元着色器之間傳遞。spa

下面咱們來看一下相關着色器代碼格式: 頂點着色器shaderv.vsh

//頂點座標
attribute vec4 position;    // vec4四維向量
//紋理座標
attribute vec2 textCoordinate;    // vec2 二維向量
//爲了傳遞紋理座標
varying lowp vec2 varyTextCoord;

void main() {
// 經過varying修飾的varyTextCoord,將紋理座標傳遞到片元着色器
varyTextCoord = textCoordinate;

//給內建變量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內建變量 (賦值像素點顏色值)GLSL語言已經提早定義好的變量,有相應的特殊含義。 內建函數 GLSL提早封裝好的函數 texture2D(紋理採樣器,紋理座標),獲取對應座標紋素(讀取紋素,讀取每個像素點的顏色值)。

着色器和程序

關於頂點着色器和片元着色器的內容已經在前面的文章中介紹過,詳情請看OpenGL ES頂點着色器和片元着色器.

咱們須要建立兩個對象才能用着色器進行渲染:着色器對象和程序對象,相似編譯器和連接程序。 着色器源代碼被編譯成一個目標形式(相似obj文件),編譯以後,着色器對象能夠鏈接到一個程序對象,程序對象能夠鏈接多個着色器對象。**在OpenGLES中,每一個程序對象必須連接一個頂點着色器和一個片元着色器。**程序對象被連接爲用於渲染的最後「可執行程序」。 得到鏈接後的着色器對象的過程通常包括6個步驟: 1.建立一個頂點着色器和一個片元着色器: 2.將源代碼鏈接到每一個着色器對象 3.編譯着色器對象 4.建立一個程序對象 5.將編譯後的着色器對象鏈接到程序對象 6.鏈接程序對象

1.建立着色器

GLuint glCreateShader(GLenum type);
複製代碼

type —建立着色器類型􏱀􏱁􏰇􏰈􏰉􏰆􏱨􏲆,GL_VERTEX_SHADER(頂點) 􏲇和GL_FRAGMENT_SHADER 􏱅􏱆􏱇 (片元) 返回值 — 􏱗􏰀􏰁􏱊􏰇􏰈􏰉􏰝􏰞􏰆􏰟􏰠 是指向新的着色器對象的句柄,能夠調用􏰦􏰧􏱱glDeleteShader 􏱋􏱌刪除。

注:咱們能夠建立多個shader,可是全部的頂點shader只能有一個main函數,片元shader也同樣。

2. 刪除着色器

void glDeleteShader(GLuint shader);
複製代碼

shader -- 􏰚􏱋􏱌􏰆􏰇􏰈􏰉􏰝􏰞􏰟􏰠􏰚􏱋􏱌􏰆􏰇􏰈􏰉􏰝􏰞􏰟􏰠要刪除的着色器對象的句柄。

注:如意一個着色器對象調用了glDeleteShader以後還連接在程序中,則說明這個着色器並無被真正刪除,當調用glDetachShader,將着色器從程序中分離以後,纔會被真正刪除,因此在調用glDeleteShader以前應該先調用glDetachShader。

3. 連接源代碼到着色器對象

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

shader -- 指向着色器對象的句柄。 count -- 着色器源字符串的數量,着色器能夠由多個源字符串組成,可是每一個着色器只有一個main函數。 string -- 指向保存數量的count的着色器源字符串的數組指針。 length -- 指向保存每一個着色器源字符串大小且元素數量爲count的整數數組指針。

4. 編譯着色器對象

void glCompileShader(GLuint shader);
複製代碼

shader -- 須要編譯的着色器對象句柄

5. 獲取編譯結果

void glGetShaderiv(GLuint shader , GLenum pname , GLint *params );
複製代碼

得到編譯階段着色器的狀態 shader -- 須要編譯的着色器對象句柄。 pname -- 獲取的信息參數􏰦􏰧􏰗 能夠爲:GL_COMPILE_STATUS GL_DELETE_STATUS GL_INFO_LOG_LENGTH GL_SHADER_SOURCE_LENGTH GL_SHADER_TYPE params -- 指向查詢結果的整數存儲位置的指針。

6. 獲取編譯錯誤日誌

void glGetShaderInfolog(GLuint shader , GLSizei maxLength, GLSizei *l ength , GLChar *infoLog);
複製代碼

得到連接階段着色器的狀態,若是發生錯誤,這個日誌保存的是最後一次操做的信息。 shader -- 須要獲取信息日誌的着色器對象句柄。 maxLength -- 保存信息日誌的緩存區大小。 length -- 寫入的信息日誌的長度(減去null 終止符);若是不須要知道長度,這個參數能夠爲Null。 infoLog -- 指向保存信息日誌的字符緩存區的指針。

7. 建立程序對象

GLUint glCreateProgram( )
複製代碼

返回值 -- 返回一個執行新程序對象的句柄。

注:也能夠建立多個程序對象,在渲染時,能夠在不一樣程序切換。

8. 刪除一個程序對象

void glDeleteProgram( GLuint program )
複製代碼

program -- 指向須要刪除的程序對象句柄。

9.連接(附着)着色器和程序

void glAttachShader( GLuint program , GLuint shader );
複製代碼

program -- 指向程序對象的句柄。 shader -- 指向程序連接的着色器對象句柄。

注:若是同時有頂點着色器和片元着色器,須要把它們都連接到同一個程序中。就如同一個C程序有多個功能模塊同樣,咱們能夠把不少個相同類型的頂點着色器或者片元着色器連接到一個程序中,可是它們只有一個main函數。固然,也能夠把一個着色器對象連接到不一樣的程序中。

10. 斷開着色器與程序連接

void glDetachShader(GLuint program, GLuint shader);
複製代碼

便是 將着色器從程序中分離 program -- 指向程序對象的句柄。 shader -- 指向程序連接的着色器對象句柄。

11.鏈接程序對象

void glLinkProgram(GLuint program)
複製代碼

program -- 指向程序對象的句柄。

12.檢查連接是否成功

void glGetProgramiv (GLuint program,GLenum pname, GLint *params);
複製代碼

檢查連接是否成功,即檢查連接狀態。 program -- 指向須要獲取信息的程序對象的句柄。 pname -- 獲取信息的參數 能夠爲: 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 -- 指向查詢結果整數存儲位置的指針。

13. 獲取程序信息日誌

void glGetPorgramInfoLog( GLuint program ,GLSizei maxLength, GLSize i *length , GLChar *infoLog )
複製代碼

program -- 指向須要獲取信息日誌的程序對象句柄。 maxLength -- 存儲信息日誌的緩存區大小。 length -- 寫入的信息日誌的長度(減去null 終止符);若是不須要知道長度,這個參數能夠爲Null。 infoLog -- 指向保存信息日誌的字符緩存區的指針。

14. 使用程序對象

void glUseProgram(GLuint program)
複製代碼

program -- 在程序對象連接成功以後,設置爲活動程序的程序對象句柄,即成功以後使用程序對象。

注:若是一個程序被使用後,若是被再次連接,則程序被自動替代並使用,不須要再次調用使用函數。 若是使用的參數是0,則表示使用固定功能流水線。

下面咱們根據上述函數繪製一個着色器和程序的流程圖:

Shader-Program.png

下面咱們用一份代碼來看一下這個建立和連接的過程(代碼比較簡陋,不足之處望見諒指正):

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.建立program
    GLint program = glCreateProgram();
  //4.建立最終的程序
    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);
    
    //5.釋放不須要的shader
    glDeleteShader(verShader);
    glDeleteShader(fragShader);

   //6.連接
    glLinkProgram(self.myPrograme);
    GLint linkStatus;
    //獲取連接狀態
    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 命令,應用程序就會產生一個錯誤編碼,這個錯誤編碼將會被記錄,能夠調用glGetError查詢,在應用程序調用glGetError查詢第一個錯誤代碼以前,不會記錄其餘錯誤代碼,一旦查詢到錯誤代碼,當前錯誤代碼便會復位爲GL_NO_ERROR.

GLenum glGetError(void);
複製代碼

錯誤代碼與描述.png
相關文章
相關標籤/搜索