OpenGL ES 2.0 知識串講 (7) ——OpenGL ES 詳解I(綁定 Shader)

出處:電子設備中的畫家|王爍 於 2017 年 7 月 12 日發表,原文鏈接(http://geekfaner.com/shineengine/blog8_OpenGLESv2_7.html)

 

上節回顧

在之前的六節中,講解了 EGL、GLSL 與 OpenGL ES 三個專業術語的概念以及它們的關係,串講了整個繪製流程;之後分別講解了 EGL 主要 API 的用處,以及 GLSL 的主要語法。現在,對 EGL 和 GLSL 有了比較全面的瞭解,那從這一節開始, 會根據 OpenGL ES 與 EGL 和 GLSL 的關係,按照繪製圖片的整個流程,對 OpenGL ES 進行詳細講解。

之前提過 OpenGL ES 其實就是一個圖形學庫,由 109 個 API 組成,只要明白了這 109 個 API 的意義和用途,就掌握了 OpenGL ES。那麼從這一節開始,將主要對 OpenGL ES API 進行詳細介紹。


OpenGL ES 中的 Shader

通過 EGL、GLSL、OpenGL ES 的關係,可以看出 EGL 就是爲繪製做準備工作的,比如關聯一塊顯示屏,初始化 GPU 信息,準備一塊繪製 buffer 等。EGL 做的事情相對獨立,只有當 EGL 的 API 被執行完畢,準備工作做好之後,才輪到 OpenGL ES 和 GLSL 出場。所以 EGL 與 GLSL 和 OpenGL ES 沒有特別多的交互。而 GLSL 主要就是書寫一個個的 Shader,這些 Shader 將在 OpenGL ES 的 pipeline 中被調用使 用。所以,GLSL 與 OpenGL ES 存在着大量的交互,那麼就先來說明,如何通過 OpenGL ES 的 API 指定 OpenGL ES 將要使用的 Shader。

Shader,在 OpenGL ES 中,就好比一塊內存,這塊內存由 OpenGL ES 創建, 使用 GLSL 語言書寫的內容將其填充,然後由 OpenGL ES 觸發 GPU 的 Shader 編譯器對其進行編譯,然後編譯好的 Shader 就可以由 OpenGL ES 控制其輸入,然後經過 Shader 生成的結果,也就是上一節說到的 Shader 中內置變量算出的那些結果,會被 OpenGL ES pipeline 的後半段操作所使用,最終結果會被保存在繪製 buffer 中。OpenGL ES 中 Shader 的操作,大概流程就是這樣的,下面來詳細介紹 一下 OpenGL ES 中對應的這些 API。


OpenGL ES API 詳解

GLenum glGetError(void);

先說明一下,只有當 EGL 給 GL 創建好環境,也就是創建好一套適合 GL API 運行的 surface 和 context,並將它們 makecurrent 之後,GL 的 API 纔可以被正常 調用。

當調用 GL 的 API 的時候,大部分 API 可以直接通過返回值,判斷這個 API 執行的成功還是失敗。如果是成功,那麼情況只有一種,但是如果失敗,則失敗的可能性則有千萬種,所以不可能只是簡單的通過返回值獲取失敗的原因。所以, 還需要更詳細的信息來判斷爲什麼失敗:是傳入參數有誤,還是發生了別的衝突之類的情況。glGetError 這個函數就提供了這個功能,每種錯誤的可能,都會被分配一個代碼號。而這個 API 就是用於返回當前 thread 如果 GL 的 API 出錯的話, 最近一個錯誤所對應的錯誤代碼。

這個 API 功能很強大,而且經常被廣泛使用,但是過多的錯誤檢測會負面的影響到一個無錯誤程序的性能,所以儘量只在一些關鍵的地方使用這個 API。

這個函數的輸入爲空。因爲這個 API 是針對目前結果進行判斷的,所以不需要任何輸入。在 GPU driver 中,當執行 GL API 的時候,如果出錯了,會將錯誤代碼號對應的標誌位進行設置,然後當調用 glGetError 的時候,通過獲取標誌位的信息,可以獲取到對應的代碼號,所以不需要任何輸入。然後該標誌位就會被清零,如果以後再出現錯誤,標誌位再被重設。

這個函數的輸出是可以用來判斷詳細錯誤信息的錯誤代碼。比如當返回 GL_NO_ERROR 的時候,說明自上次 glGetError 的執行以來,或者 GL 被初始化到目前爲止,所有的 GL API 都運行正常,沒有出錯。如果返回其它標記位,就說明,在兩次 glGetError 之間,或者 GL 被初始化之後, 調用的 GL API 出現了錯誤。

有一種特殊情況,假如在調用這個 API 之前,出現過不止一次錯誤,那麼調用這個 API 獲取的將是最近一次錯誤的錯誤代碼,並將該錯誤代碼的標記重置。 然後再調用一次,獲取的將是倒數第二次錯誤的錯誤代碼,以此類推,直至所有被標記的錯誤代碼全部被重置後,再調用這個 API,則返回 GL_NO_ERROR。 除了 GL_NO_ERROR 之外還有 5 個錯誤代碼,標誌着 5 種錯誤情況,這 5 種情況等說到對應 API 的時候再進行具體的解釋說明。

那麼當 GL API 出錯了之後,下面執行的 GL 命令是否會被繼續正常執行呢?

某種意義上可以認爲,只有當出現了內存不夠的情況的時候,也就是 glGetError 返回 GL_OUT_OF_MEMORY 的時候,剩下的 GL 操作的結果會被認爲是 undefine。 而其他情況下,剩餘的 API 應該會忽略這些錯誤,繼續正常執行,但這個也需要 case by case 的看待,比如在創建 buffer 的 API 出錯,buffer 沒有被創建成功,那麼下面當對這個 buffer 進行賦值的 API 的時候,肯定也是會出錯的。

GLuint glCreateShader(GLenum shaderType);

OpenGL ES 中需要使用到 Shader,比如 OpenGL ES 會通過 API 給 Shader 傳遞傳入參數,也會觸發 GPU 去編譯 Shader,那麼首先,需要通過 API 創建一個 Shader 的 handle,然後把 GLSL 書寫的 shader source 傳給這個 shader handle,然後才能觸發 GPU 去編譯 shader 等一系列的操作,而 glCreateShader 這個 API,就是用於創建一個 shader 的 handle。

這個函數的輸入爲 shader type,指定了哪種 shader object 被創建,比如創建了一個 Vertex shader,或者是創建了一個 fragment shader。這裏的 shader type 輸入,只支持 GL_VERTEX_SHADER 和 GL_FRAGMENT_SHADER,如果使用其他 token,那麼就會報告 GL_INVALID_ENUM 的錯誤。在這裏解釋一下 GL_INVALID_ENUM,出現這個錯誤代碼,說明原本需要輸入某些特定的 enum 參數,但是卻輸入了其他的不支持的參數,這樣就會報這個錯誤。而這種錯誤可以被忽略,除了這個 API 要執行的操作沒有成功執行,以及 GPU driver 中被打上了標記之外,其他方面不會產生任何影響。

這個函數的輸出是一個非負數字,用於指定返回的 shader object,當創建失敗的話,返回 0。這個 shader object 就是我們剛纔說的 shader 的 handle,用於存放構建 shader 所使用的 shader source。之後使用 shader 的時候,就可以通過這個非 0 的名字來訪問對應的 shader。剛被創建的 shader object 爲空。

shader 使用的 namespace 和之後我們要介紹的 texture、program 一致,可以被多個共享的 context 所共享。當這些東西被 share 的時候,它們的內容也是會被共享的。也就會出現了一個 context 創建 shader,然後被另外一個 share_context 去使用的情況。可以想象到,當 share 這些東西的時候,需要開發者自己注意數據讀寫順序的問題。

void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length);

當一個 Shader Object 被創建之後,初始爲空的,裏面沒有內容,那麼需要先把 GLSL 書寫的內容輸入到這個 Shader Object 中,那麼這個 glShaderSource 的 API,就是往一個 shader 的 handle 中傳遞 shader source。

這個函數輸入的第一個參數是 Shader Object,也就是剛纔通過 glCreateShader 創建,所得到的那個非 0 值。比如剛纔通過 glCreateShader 創建了一個 Shader,名字爲 1,那麼在這裏就要傳入 1。假如傳入的並非是一個合法的 Shader Object,比如傳入一個 2,或者傳入一個別的值,就會出現 INVALID_VALUE 的 error。在這裏解釋一下 GL_INVALID_VALUE,出現這個錯誤代碼,說明原本需要輸入某些特定的數值參數,但是卻輸入了其他的不支持的參數,這樣就會報這個錯誤。而這種錯誤可以被忽略,除了這個 API 要執行的操作沒有成功執行,以及 GPU driver 中被打上了標記之外,其他方面不會產生任何影響。

第二個參數和第三個參數的意思是:string 是一個由 count 個指針元素組成的數組,每個數組元素都是一個無終結的字符串。那麼也就是說,可以把一個完整的 GLSL 書寫的 shader source 寫在一個字符串中,那麼 count 就爲 1,string 就是一個只有一個指針元素的數組。也可以把一個完整的 GLSL 書寫的 shader source 寫在多個字符串中,那麼 count 就不是 1,string 就是由多個指針元素組成的數組。其實由於 GLSL 書寫的 shader source 有很多格式化的內容,比如之前在說 GLSL 語法的時候就說到,fragment shader 中需要指明 float 的默認 precision,比如: precision lowp float; 。像這種內容基本每個 fragment shader 都需要寫一遍,那麼就可以把它拿出來,當在一個程序中需要創建多個 shader 的時候,就可以在傳 shader source 的時候,將每個 shader 的 shader source 都分成 2 個或者 2 個以上 的字符串,其中一個字符串就是用於保存這些格式化的內容,這樣就不需要在代碼中多次創建和書寫冗餘的字符串了。這種編程技巧基本一直在使用。GPU 會在執行這個 API 的時候把 shader source copy 過去,也就是說,當執行完這個 API之後,string 這個用於保存 shader source 的字符串數組就可以被 free 了。 第四個參數也是一個數組,它對應於 string,用於限制 string 數組中對應的數組元素的字符串的長度。如果 count 爲 2,那麼 string 中也就有 2 個數組元素, 也就對應於 2 個字符串,那麼 length 這個數組也應該有 2 個元素,每個元素都是一個數值,用於指定對應的 string 中的兩個字符串的長度。假如數值爲負,那麼對應的字符串的長度爲無限長,如果爲 NULL,那麼 string 中所有的字符串的長度都是無限長。

現在已經知道了這個命令是將 shader object 中的內容設置爲 string 中的內容,但是假如這個 shader object 本身就有內容,那麼原來的內容會被覆蓋掉。在這裏,string 裏的內容是會直接被 copy 到 shader object 中的,不會經過 scan 或 者 parse 等其他檢查操作。所以默認會認爲 string 中的內容組合成的 shader source 是一段由 GLSL 書寫的正確的 shader source。而假如其中有錯誤,那麼在這裏不會被發現,而在之後編譯 shader 或者其他操作中才會被發現。

需要注意的是,有可能某些 GPU 並不支持 shader compiler,那麼可以通過 glGet 這個 API,輸入 GL_SHADER_COMPILER 這個參數來查詢,假如 GPU 不支持 shadercompiler 的 話 , 那 麼 glshadersource 、 glCompilerShader 、 glGetShaderPrecisionFormat 、 glReleaseShaderCompiler 會報 GL_INVALID_OPERATION 的錯誤。在這種情況下,雖然 GPU 不支持 shadercompiler, 而 shader 又是必不可少的一步,那麼這種 GPU 一般都會支持 glShaderBinary,可以支持 load 一個已經編譯好的 shader binary,這個 API 我們一會再說。這裏再解釋一下 GL_INVALID_OPERATION,出現這個錯誤代碼,說明在當前狀態下該操作不被允許,刻意的非要執行的話,就會報這個錯誤。而這種錯誤可以被忽略,除了這個 API 要執行的操作沒有成功執行,以及 GPU driver 中被打上了標記之外, 其他方面不會產生任何影響。

這個函數沒有輸出,但是有以下幾種情況會出錯,除了剛纔說的傳入的 shader object 不是一個合法的 shader 會報 INVALID_VALUE 的錯,以及如果 GPU 不支持 shadercompiler,執行指定的那幾個 API 會報 GL_INVALID_OPERATION 之外, 還有如果函數的第二個參數 count 小於 0,則出現 INVALID_VALUE 的 error。 INVALID_VALUE 我們剛纔已經解釋過了,以上那些已經解釋過的 error,在之後就不做重複解釋了。

void glCompileShader(GLuint shader);

一個程序必須編譯之後才能運行,shader 也是。區別在於,一般的程序是在 CPU 進行編譯,而 Shader 則是在 GPU 中進行編譯的。那麼 glCompileShader 這個 API,就是把一個已經包含 shader source 內容的 shader 發給 GPU 進行編譯。

這個函數的輸入參數也是 Shader Object,和 glShaderSource 的第一個參數一樣,也就是剛纔通過 glCreateShader 創建所得到的那個非 0 值。同樣的,如果我們傳入一個不合法的 shader object 的數值,就會出現 INVALID_VALUE 的 error。

這個函數沒有輸出參數。但是每個 shader object 都有一個布爾值 Compile_status,這個值會根據編譯的結果進行修改,比如 shader 編譯成功沒有問題且可以使用,那麼這個狀態將會被設爲 TRUE,否則則爲 FALSE。這個 status 值可以通過 GetShaderiv 這個 API 查詢。根據學過的 GLSL 語法,編譯失敗的原因有很多,在這裏就不詳細進行一一說明了。如果編譯失敗,則之前編譯的所有信息都將丟失。也就是說編譯失敗之後,該 shader 的狀態不會回滾到編譯之前的舊的狀態。通過 glshadersource 改變 shader object 的內容,並不會改變其編譯狀態或編譯出來的 shader code。只有當再執行一次 glCompileShader,且編譯成功, 纔會改變該 shader 的編譯狀態。

每個 shader object 也都有一個 information log,是一個 string,每次編譯都會被重寫,這個 log 包含很多編譯信息,且可以通過 glGetShaderInfoLog 查看。

void glShaderBinary(GLsizei n, const GLuint *shaders, GLenum binaryformat, const void *binary, GLsizei length);

預編譯的 shader binary 可以通過 glShaderBinary 這個 API 傳給 Shader object, 也就是剛纔說的如果 GPU 不支持 compileShader 的話,可以通過這個 API 把 shader binary 傳給 shader object。這樣會比較省時,節約了 GPU 編譯 shader 的時間。 這個函數輸入的第一個參數和第二個參數的意思是:shaders 是包含了 n 個 shader object 的數組,每個 shader object 都有自己的類型,比如是 vertex shader 或者是 fragment shader。但是如果 shader 中存放了兩個相同類型的 shader,比如存放了兩個 vertex shader 的 object,那麼就會出現 INVALID_OPERATION 的錯誤。 第三個參數 binaryformat 指出了預編譯 shader 的格式,到時候 shader 會根據 binaryformat 來進行解碼,解碼後才類似於一個編譯好之後的 shader,然後交給 GPU 使用。第四個參數 binary 是一個指向預編譯的 shader binary 的指針,第五個參數 length 限定了這個 shader binary 的長度。GPU 支持的 shaderbinary 格式可以由參數 NUM_SHADER_BINARY_FORMATS 和 SHADER_BINARY_FORMATS 查詢到, 第一個是用於查詢支持的 shader binaryformat 的數量,第二個是查詢到具體哪些 shader binaryformat 所被支持。根據 shader 的類型,shaderbinary 會讀取 binary vertex 或者 fragment shader。又或者是一個可執行的二進制文件,其中包含了一個優化好的 vertex shader 和 fragment shader 的組合。

這個函數沒有輸出參數。但是有以下幾種情況會出錯,除了剛纔說到的 shaders 中不能保存兩個或者兩個以上相同類型的 shader,否則會出現 INVALID_OPERATION 之外,還有比如第一個參數 n 或者最後一個參數 length 爲負, 則報INVALID_VALUE,如果第二個參數shaders中保存的shader object不是一個合法的 shader 會報 INVALID_VALUE 的錯。第三個參數 binaryformat 如果不被支持, 也就是說不存在於 SHADER_BINARY_FORMATS 之中,那麼會出現 INVALID_ENUM 錯誤。比如第四個參數 binary 指向的數據不符合第三個參數的 binaryformat,那 麼會出現 INVALID_VALUE 的錯誤。並且,可能根據不同的 binaryformat 的定義, 還會出現不同的錯誤。

如果 glShaderBinary 失敗,那麼該 shader 的狀態不會回滾到編譯之前的舊的狀態。

需要注意的是,如果 GPU 支持 ShaderBinary 這種機制,那麼要求如果 shaderbinary是一組被優化了的vertexshader和fragment shaderbinary,它們必須被用在一起。這種限制一般會寫在 binaryformat 的定義文檔中。

需要注意的是,有可能某些 GPU 並不支持 glShaderBinary 這種機制,據我所知,這種 GPU 不在少數,那麼可以通過 glGet 這個 API,輸入 NUM_SHADER_BINARY_FORMATS 和 SHADER_BINARY_FORMATS 這兩個參數來查詢,假如 GPU 不支持 glShaderBinary 的話,那麼執行 glShaderBinary 這個 API 會報 GL_INVALID_OPERATION 的錯誤。在這種情況下,雖然 GPU 不支持 glShaderBinary,而 shader 又是必不可少的一步,那麼這種 GPU 一般都會支持 glShaderSource 和 glCompileShader 那種機制。

GPU 會在我們執行這個 API 的時候就把 shader binary copy 過去了,也就是說, 當執行完這個 API 之後,binary 這個用於保存 shader binary 的指針就可以被 free 了。

GLuint glCreateProgram(void);

在一個完整的 OpenGL ES 的 pipeline 中,需要使用兩個不同類型的 shader, 一個是 vertex shader,用於處理頂點座標,一個是 fragment shader,用於處理頂點顏色。可以在代碼中創建無數個 shader,但是最終交給 GPU 真正使用的,一次只能是一組兩個不同類型的 shader。這兩個 shader 將被放在一個叫做 program 的 object 中,然後把 program object 交給 GPU。而 glCreateProgram 就是用於創 建一個 program object 的。

這個函數沒有輸入參數。

這個函數的輸出是一個非負整數,用於指定返回的 program object,當創建失敗的話,返回 0。之後使用 program 的時候,就可以通過這個非 0 的名字來訪問對應的 program。剛被創建的 program object 爲空。

剛纔也說過了,program object 和 shader object 使用的是同一個命名空間, 也就是可以被多個共享的 context 所共享。當被 share 的時候,它們的內容也是會被共享的。

void glAttachShader(GLuint program,GLuint shader);

剛纔說需要把兩個不同類型的 shader 放在一個 program 中之後,program 才能被交給 GPU,那麼 glAttachShader 這個 API,就是把一個 shader 關聯到該 program object 上。

這個函數的第一個輸入參數爲 program object,也就是剛纔通過 glCreateProgram 創建,所得到的那個非 0 值。第二個輸入參數爲 shader object, 也就是通過 glCreateShader 創建,所得到的那個非 0 值。如果傳入一個不合法的 program object 數值或者 shader object 數值,就會出現 INVALID_VALUE 的 error。 需要注意的是,在這裏被傳入的 shader object,在執行這個 API 的時候,可以還沒有被賦予其 shadersource,也可以在該 shader 沒有被編譯之前。一個 shader object 可以被 attach 到多個 program 上。

這個函數沒有輸出參數,但是有以下幾種情況會出錯,除了剛纔說的傳入的shader object 不是一個合法的 shader,或者 program object 不是一個合法的 program 會報 INVALID_VALUE 的錯之外,如果一個 shader object 已經被關聯到某個 program 上之後,再將其重複關聯到那個 program 上,就會出現 INVALID_OPERATION 的錯誤。或者如果一個 program 上已經關聯了某種類型的 shader,那麼再在該 program 上關聯一個相同類型的 shader 的話也會出現 INVALID_OPERATION 的錯誤。

void glDetachShader(GLuint program, GLuint shader);

剛纔說了如果一個 program 上 attach 了一個某種類型的 shader,再 attach 上一個相同類型的 shader 的時候就會報錯,那麼爲了能進行同種類型 shader 的 替換,所以有了 glDetachShader 這個操作,這個 API 就是把該 shader 從該 program 上進行解綁,此後該 shader 和該 program 就不再有關係了,而這個時候這個 program 就可以 attach 其他該類型的 shader 了。

這個函數的輸入參數和 glAttachShader 一樣,第一個輸入參數爲 program object,第二個輸入參數爲 shader object,如果我們傳入一個不合法的 program object 數值或者 shader object 數值,就會出現 INVALID_VALUE 的 error。

這個函數也沒有輸出參數,但是有以下幾種情況會出錯,除了剛纔說的傳入 的 shader object 不是一個合法的 shader,或者 program object 不是一個合法的 program 會報 INVALID_VALUE 的錯之外,如果該 shader object 原本就沒有被 attach 到該 program 上的話,強行執行這個 API,就會報 INVALID_OPERATION 的錯誤。

另外需要注意的是:

如果一個 shader 已經被 attach 到 program 上了,然後刪除該 shader,那麼不會立即刪除該 shader,而是對該 shader 做一個標記,當這個 shader 從 program 上 detach,且確保該 shader 沒有 attach 到其他 program 上之後,刪除操作纔會被執行。

void glLinkProgram(GLuint program);

program 交給 GPU 使用的時候,需要上面 attach 了一個 vertex shader 和一個 fragment shader,然而可以通過 attachShader 和 detachShader 不停的往 program 中放入和取出 shader,那麼 glLinkProgram 這個 API,就好比把 program 封住,使得其中的 vertex shader 和 fragment shader 組成一對,形成一個新的完整的個體。

這個函數的輸入參數爲 program object,如果傳入一個不合法的 program object 數值,就會出現 INVALID_VALUE 的 error。

這個函數沒有輸出參數。但是每個 program object 都有一個布爾值 Link_status,這個值會根據 link 的結果進行修改,比如 program 鏈接成功,且一個有效的可執行文件被創建,那麼這個狀態將會被設爲 TRUE,否則則爲 FALSE。 這個 status 值可以通過 glGetProgramiv 這個 API 查詢。根據我們學過的 GLSL 語法, 鏈接失敗的原因有很多,比如 program 中的 shader object 沒有被成功編譯,比如 program 中沒有 vertex shader 或者 fragment shader,比如 shader 中使用了超出限制的 uniform 或 sample 變量,比如 shader object 是通過預編譯的 shader binary 讀取生成的等等,在這裏就不詳細進行一一說明了。如果 link 失敗,則之前 link 的所有信息都將丟失。也就是說鏈接失敗之後,該 program 的狀態不會回滾到鏈接之前的舊的狀態。而有一些信息還是能被找回來的,這些信息是 attribute 和 uniform 相關的信息,這個下個課時我們再詳細說明。

每個 program object 也都有一個 information log,每次 link 都會被重寫,這個 log 包含很多鏈接信息,且可以通過 glGetProgramInfoLog 查看。

link 成功之後,所有 shader 中開發者自定義的 active 的 uniform 都會被初始化爲 0,然後會被分配一個地址,該地址可以通過 glGetUniformLocation 這個 API 來獲取。同樣的,shader 中所有開發者自定義的 active 的 attribute,如果沒有被於一個指定的 index 綁定,在這個時候就會給它分配一個 index。這個 index 可以 通過 glGetAttribLocation 這個 API 來獲取,該兩個 API 我們會在下個課時進行詳細說明。

當 program 被 link 之後,該 program 對應的 shader 可以被修改、重新編譯、 detach、attach 其他 shader 等操作,而這些操作不會影響 link 的 log 以及 program 的可執行文件。

void glUseProgram(GLuint program);

當一個 program 被成功鏈接之後,也就說明了該 program 已經準備就緒,可以被使用了,一個程序中可以有很多鏈接好的 program,但是同一時間只能有一個 program 被使用,那麼 glUseProgram 這個 API 就是指定了哪個 program 會被使用,也就是把該 program 的可執行文件當作當前 rendering state 的一部分。

這個函數的輸入參數爲 program object,一個合法的 program object 爲非 0, 那麼假如傳入 0,也不會出錯,但是當前的 rendering state 則使用的是一個不合法的 program,如果執行繪製命令,則會出現 undefine 的結果。而如果傳入了不是一個合法的 program,也不是 0 的話,則出現 INVALID_VALUE 的錯誤。如果傳入的 program 還沒有被成功 link,則出現 INVALID_OPERATION 的錯誤。而當前的 rendering state 不會做任何更改。

這個函數沒有輸出參數。當一個合法且 link 成功的 program 被 use 之後,該 program 對應的 shader 可以被修改、重新編譯、detach、attach 其他 shader 等操 作,而這些操作不會影響 link 的 log 以及 program 的可執行文件。如果被使用的 program 被 relink 成功,則該 program 新生成的可執行文件會替換掉當前 rendering state 中使用的可執行文件。如果被使用的 program 被 relink 失敗,那麼該 program 的 linkstatus 會被設置爲 false,而 program 依然可以被使用而且當前 rendering state 中的可執行文件依然是之前可用的那個可執行文件,直到另外一個 program 被 use。當它被替換掉之後,除非是再 relink 成功,否則將不再能被 use。

void glDeleteProgram(GLuint program);

當 program 不再被需要的時候,則可以通過 glDeleteProgram 這個 API 把 program 刪除。

這個函數的輸入參數爲 program object,可以傳入 0,傳入 0 的時候,這個命令會被忽略掉,如果我們傳入的不是一個合法的 program object 數值或者 0 的時候,就會出現 INVALID_VALUE 的 error。

這個函數沒有輸出參數。如果 program 不是任何 GL Context 的當前 program, 也就是沒有被任何 GL Context use,則它會被立即刪除。否則的話,該 program 會被做一個標記,當這個 program 不再被任何 GL Context use 的時候,該 program 的刪除操作纔會被執行。可以通過 glGetProgramiv 這個 API,傳入參數 GL_DELETE_STATUS 這個參數,查詢該 program 是否被打上 delete 的標記。當 program 被刪除之後,所有的 shader object 都會被 detach。

void glDeleteShader(GLuint shader);

當 shader 不再被需要的時候,則可以通過 glDeleteShader 這個 API 把 shader 刪除。

這個函數的輸入參數爲 shader object,可以傳入 0,傳入 0 的時候,這個命令會被忽略掉,如果我們傳入的不是一個合法的 shader object 數值的時候,就會出現 INVALID_VALUE 的 error。

這個函數沒有輸出參數。剛纔在說 detach shader 的時候已經說了,如果一個 shader 已經被 attach 到 program 上了,然後刪除該 shader,那麼不會立即刪除該 shader,而是對該 shader 做一個標記,當這個 shader 從 program 上 detach, 且確保該 shader 沒有 attach 到其他 program 上之後,刪除操作纔會被執行。可以通過 glGetShaderiv 這個 API,傳入參數 GL_DELETE_STATUS 這個參數,查詢該 shader 是否被打上 delete 的標記。

void glReleaseShaderCompiler(void);

釋放 ShaderCompiler 相關的資源。當預計之後都不會在編譯 shader,或者最起碼一段時間之內不會再編譯 shader 了,那麼就可以把 shader 編譯器相關的資源釋放掉,然後把這些資源拿去更有效的利用起來。

這個函數沒有輸入參數。

這個函數也沒有輸出參數。但是假如在執行了這個 API 之後,我們又需要使用 glCompileShader 這個 API 去編譯 shader,那麼 shader 編譯器就會被重新組裝起來去編譯 shader,就彷彿沒有被釋放掉一樣。剛纔也說了,當 GPU 不支持 shadercompile 的時候,執行這個 API 會出現 INVALID_OPERATION 的錯誤。

以上這些 API 就是 OpenGL ES 綁定 shader 的主要 API,除此之外,還有很多 API 是用於查詢服務的。比如 glValidateProgram 這個 API 是用於查詢 program 是否包含一個可執行文件,且可以被用於當前 renderingstate 的。如果我們 linkprogram 成功的話,這個查詢結果就會是正確的,查詢結果會放在 program object 的布爾值 GL_VALIDATE_Status 中。再比如 API glGetAttachedShaders 用於獲取 program 當前 attach 的 shader。比如 API glGetShaderPrecisionFormat,用於獲取指定 shader 類型中某種格式的精度和範圍,比如查詢 VS 中 high float 的精度和範圍。再比如 API glGetShaderSource 用於獲取 shader object 中包含的 shader source。比如 API glIsProgram 和 glIsShader 用於查詢傳入的參數,是否是一個合法的 program 或者 shader。最後,再比如這一節提到過的 glGetProgramInfoLog,glGetShaderInfoLog,glGetProgramiv,glGetShaderiv 用於獲取 program 和 shader 的 log,以及其他參數信息,以及 glGet 這個用於獲取 GPU 常規信息的 API,在這一節用這個 API 查詢了 GPU 是否支持 shadercompiler 和 shaderbinary,在之後的課時裏還會使用這個 API 查詢更多其他的信息。

本節教程就到此結束,下一節將學習如何通過 OpenGL ES 傳入繪製信息,希望大家繼續閱讀我之後的教程。

謝謝大家,再見!


寫在末尾的話

要站在巨人的肩膀上做創新,每一次你寫一行新的代碼,第一要做的,先想一想你這行代碼值得不值得寫,是不是有人已經做了同樣的工作,可能做得比你還好一點。有沒有其他人已經解決這個問題,然後你可以把你的時間放在更好的創新上。

遊戲引擎中涉及到太多模塊,文件壓縮、圖片解析、網絡支持、物理引擎、音頻等,這些都有非常優秀的第三方庫,所以多數遊戲引擎都會直接使用第三方庫,使得其更專注於遊戲引擎最核心的渲染模塊。作爲遊戲開發者或者遊戲引擎開發者,在遊戲引擎中最需要了解的就是渲染引擎部分,而渲染引擎的目的就是將遊戲邏輯層上的元素繪製到屏幕上,那麼會調用到用於跟手機芯片GPU溝通的底層代碼,這塊底層代碼在端遊遊戲引擎中爲 DirectX(for windows),OpenGL(For Unix),而在手遊的遊戲引擎中絕大多數爲 OpenGL ES,所以學習遊戲引擎,一定要掌握 OpenGL ES 。

學習OpenGL ES,可以讓我們清楚的直到每個元素是怎樣被繪製,知道如何使用它們才能達到最高性能,知道如何使用着色器來增強畫面表現力,也更能看清楚所使用引擎的不足之處以及判斷其發展趨勢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章