之前我們將着色器的代碼用glsl寫好之后,保存為字符串指針,然后用一個函數去編譯它,這是一種手段,對於簡單的着色器代碼可以這樣。但當我們針對復雜的着色器,我們發現編寫、編譯、管理着色器是一件麻煩事。我們用一個類將着色器的所有編譯,鏈接,管理都放在一個文件里。再將着色器源碼單獨設置成.glsl文件用來,從文件流讀取,不再放到c++編譯器里了。這樣主函數就比較簡潔了。 我們建立一個類shader,將一切着色器的步驟都在這個類里封裝了,這樣我們在主函數實例化它,我們就直接可以使用着色器不用在意內部的具體情況(從文件流讀取,編譯,鏈接)。
因為我們是在.h文件里面實現的這些步驟,包括函數的具體實現我們都放到.h文件里了,所以我們還需要一些特殊處理
#ifndef SHADER_H //先測試x是否被宏定義過
#define SHADER_H //如果沒有宏定義下面就宏定義x並編譯下面的語句
#include <glad/glad.h>; // 包含glad來獲取所有的必須OpenGL頭文件
#include <string>
#include <fstream> //file stream ,fstream是C++ STL中對文件操作的合集,包含了常用的所有文件操作。
#include <sstream> //字符串流,可以支持C風格的串流的輸入輸出操作。
#include <iostream> #endif //如果已經定義了則編譯#endif后面的語句
然后我們可以聲明這個類的結構了:
class Shader {
public:
// 程序ID
unsigned int ID;
// 構造器讀取並構建着色器
Shader(const GLchar* vertexPath, const GLchar* fragmentPath);
// 使用/激活程序
void use();
// uniform工具函數
void setBool(const std::string &name, bool value) const;
void setInt(const std::string &name, int value) const;
void setFloat(const std::string &name, float value) const;
};
看着比較簡單,接下來我們對函數的具體實現,分析,首先是最重要的構造函數,這一個函數里面,將一切編譯鏈接都做完了。構造函數需要傳入兩個參數的文件地址(針對vs的.sln文件目錄來說,我們把文件放到那里,就只需要一個名字就可以了),不用管第三個,那是一個幾何地址,設為NULL。
Shader(const char* vertexPath, const char* fragmentPath, const char* geometryPath = nullptr) { // 1. 從文件路徑中獲取頂點/片段着色器 //聲明一些對象,ifstream 的意思是 從硬盤到內存的文件流對象,這個用來存儲我們的着色器源碼,並且對其進行處理 std::string vertexCode; std::string fragmentCode; std::string geometryCode; std::ifstream vShaderFile; std::ifstream fShaderFile; std::ifstream gShaderFile; //保證ifstream對象可以拋出異常,防止錯誤讀取,出現故障,即使停止。 vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); gShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); try { // 打開文件,這里輸入文件地址。 vShaderFile.open(vertexPath); fShaderFile.open(fragmentPath); std::stringstream vShaderStream, fShaderStream;//聲明兩個讀取流對象,用來臨時存放數據 // 讀取文件的緩沖內容到數據流中 vShaderStream << vShaderFile.rdbuf(); fShaderStream << fShaderFile.rdbuf(); // 關閉文件處理器 vShaderFile.close(); fShaderFile.close(); // 轉換數據流到string stringvertexCode = vShaderStream.str(); fragmentCode = fShaderStream.str(); // if geometry shader path is present, also load a geometry shader//這一段不用管。 if (geometryPath != nullptr) { gShaderFile.open(geometryPath); std::stringstream gShaderStream; gShaderStream << gShaderFile.rdbuf(); gShaderFile.close(); geometryCode = gShaderStream.str(); } } //如果捕捉到異常,輸出一些文字告訴程序員 catch (std::ifstream::failure e) { std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl; } //聲明兩個字符串數組指針,用來存放從上面讀下來的源碼,用於接下來的編譯 const char* vShaderCode = vertexCode.c_str(); const char * fShaderCode = fragmentCode.c_str(); //下面的就是編譯,鏈接,刪除,這都是類似的了。 // 2. compile shaders unsigned int vertex, fragment; // vertex shader vertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex, 1, &vShaderCode, NULL); glCompileShader(vertex); checkCompileErrors(vertex, "VERTEX"); // fragment Shader fragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment, 1, &fShaderCode, NULL); glCompileShader(fragment); checkCompileErrors(fragment, "FRAGMENT"); // if geometry shader is given, compile geometry shader unsigned int geometry; if (geometryPath != nullptr) { const char * gShaderCode = geometryCode.c_str(); geometry = glCreateShader(GL_GEOMETRY_SHADER); glShaderSource(geometry, 1, &gShaderCode, NULL); glCompileShader(geometry); checkCompileErrors(geometry, "GEOMETRY"); } // shader Program ID = glCreateProgram(); glAttachShader(ID, vertex); glAttachShader(ID, fragment); if (geometryPath != nullptr) glAttachShader(ID, geometry); glLinkProgram(ID); checkCompileErrors(ID, "PROGRAM"); // delete the shaders as they're linked into our program now and no longer necessery glDeleteShader(vertex); glDeleteShader(fragment); //不管這個 if (geometryPath != nullptr) glDeleteShader(geometry); }
好了,現在我們已經成功創造出來了這個類的構造函數,接下來我們設計一下幾個公有函數就好了,它們都很簡單。 激活程序:
void use() {
glUseProgram(ID);
}
// uniform工具函數:
void setBool(const std::string &name, bool value) const { glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value); }
void setInt(const std::string &name, int value) const {
glUniform1i(glGetUniformLocation(ID, name.c_str()), value); }
void setFloat(const std::string &name, float value) const { glUniform1f(glGetUniformLocation(ID, name.c_str()), value); } 好了,這樣我們就解決了所有的問題,它讓我們的使用變得非常簡單易。
Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
... while(...) {
ourShader.use();
ourShader.setFloat("someUniform", 1.0f); DrawStuff();
}