著色器的處理
從編寫著色器源碼,到編譯著色器,再到創(chuàng)建著色器程序,這是一個非常繁瑣的過程。因此我們需要對整個過程進行簡化和封裝。
創(chuàng)建著色器
我們首先可以把“創(chuàng)建著色器”這個過程封裝為函數(shù)CreateShader()。兩個參數(shù)分別是要創(chuàng)建的著色器的類型和相關(guān)的著色器源碼。

由于著色器源碼很容易出現(xiàn)錯誤,且一旦出錯編譯器也無法給出明確的提示,因此我們可以在這里加上錯誤處理的代碼。這需要用到函數(shù)glGetShaderiv()和glGetShaderInfoLog()。
glGetShaderiv用于獲取著色器對象的信息。

shader表示要查詢的著色器對象的標識符。
pname表示要獲取的信息類型:GL_SHADER_TYPE(獲取著色器類型),GL_DELETE_STATUS(獲取著色器刪除狀態(tài),返回GL_TRUE表示已刪除,GL_FALSE表示未刪除),GL_COMPILE_STATUS(獲取著色器編譯狀態(tài),返回GL_TRUE表示編譯成功,GL_FALSE表示編譯失?。?strong>GL_INFO_LOG_LENGTH(獲取著色器信息日志的長度),GL_SHADER_SOURCE_LENGTH(獲取著色器代碼的長度)。
params用于接收查詢結(jié)果。
glGetShaderInfoLog用于獲取著色器對象的編譯或鏈接信息日志。

maxLength指定infoLog緩沖區(qū)的最大長度(以字節(jié)為單位)。
length用于接收實際寫入infoLog緩沖區(qū)的字符數(shù)。如果不需要返回的字符串長度,可以設(shè)置為NULL。
infoLog用于接收信息日志的緩沖區(qū),通常是一個字符數(shù)組。
下面是錯誤處理的代碼部分:

使用alloca動態(tài)分配字符數(shù)組message相對簡單,也可以使用動態(tài)數(shù)組vector來代替,這樣可以更好地管理內(nèi)存,避免內(nèi)存泄漏:

創(chuàng)建著色器程序
創(chuàng)建完著色器后,下一步就是對創(chuàng)建著色器程序進行封裝。通過傳入相應(yīng)的著色器源碼,來獲取最終的著色器程序。

這里比之前創(chuàng)建著色器程序多了一步——使用函數(shù)glValidateProgram()。它用于驗證一個著色器程序?qū)ο蟮挠行?,驗證的結(jié)果可以通過函數(shù)glGetProgramiv()獲取。
這只是最基本的實現(xiàn)。嚴謹一點,就需要在glLinkProgram和glValidateProgram后分別進行一次錯誤處理。當然,這里和上面創(chuàng)建著色器部分的錯誤處理的方法和步驟以及函數(shù)的用法幾乎一致。


獲取著色器源碼
為了更好地編寫和管理著色器源碼,可以把著色器源碼全部放在另外一個文件中,避免在代碼中出現(xiàn)冗長的字符串。
讓我們在項目中添加一個“Shader.glsl”文件,當然文件的命名和后綴都不重要,也可以讓它是一個.txt文件,怎樣都行。我們把所有的著色器源碼都寫在這個文件里。為了方便后面讀取兩個著色器的各自部分的代碼,我們?yōu)槊總€著色器添加一個前綴標識符,如下所示:

接下來要做的就是把這個文件讀取為兩個字符串。我們將定義一個函數(shù),它接收著色器源碼文件路徑,返回兩個字符串。因此我們首先要定義一個存儲兩個字符串的結(jié)構(gòu)體:

接下來在函數(shù)中,我們遍歷文件中的每一行。首先檢查我們設(shè)置的著色器類型標識符。第一個條件語句檢查當前行是否包含“#shader”。如果包含,表示這是指定著色器類型的行。第二個條件語句用于確定著色器類型。如果當前行不是指定著色器類型的行,則將該行添加到字符串流數(shù)組對象source中。注意,這里讓source的下標和枚舉的著色器類型相互對應(yīng),簡化了代碼。

當然,這里也只是最基本的實現(xiàn)。我們依然可以添加許多錯誤處理的代碼。譬如可以檢查文件是否被正確打開:

另外,使用std::stringstream存儲解析結(jié)果,可能導(dǎo)致較大的內(nèi)存消耗。我們也可以換一種方式,使用字符串的加法來解決結(jié)果的存儲問題:

到此為止,我們已經(jīng)初步完成了著色器相關(guān)步驟的封裝?,F(xiàn)在我們要在主函數(shù)中創(chuàng)建一個著色器程序只需要兩行代碼:
