LearnGL - 04.2 - 封裝 ShaderProgram 類


LearnGL - 學習筆記目錄

本人才疏學淺,如有什麼錯誤,望不吝指出。

前面幾篇,我們都用到多多少少的 shader。

其中 shader 代碼中的編譯、檢測編譯錯誤、附加着色器、鏈接程序、鏈接錯誤檢測、等,可重用新比較高,所以封裝起來會減少後續的勞動力。


先在之前的文章有介紹的:Dependencies/Include 文件夾下添加:shader.h 文件。
在這裏插入圖片描述

下面是將之前說的,將 shader 的 編譯、檢測編譯錯誤、附加着色器、鏈接程序、鏈接錯誤檢測、等 都需要封裝的處理:

現在着色器使用的API在外部來說就相對更簡單一些了

使用方式

直接加載源碼字符串

...
static const char* vertex_shader_text = R"glsl(
#version 450 compatibility
uniform mat4 transformMat;
attribute vec3 vPos;
attribute vec3 vCol;
varying vec3 fCol;
void main() {
	gl_Position = transformMat * vec4(vPos, 1.0);
	fCol = vCol;
}
)glsl";

static const char* fragment_shader_text = R"glsl(
#version 450 compatibility
varying vec3 fCol;
void main() {
	gl_FragColor = vec4(fCol, 1.0);
}
)glsl";

...

GLint mat_location, vpos_location, vcol_location;
ShaderProgram* shaderProgram = new ShaderProgram;
//shader program init 1 - 直接加載shader源碼方式
if (!shaderProgram->initBySourceCode(vertex_shader_text, fragment_shader_text)) {
	std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 輸出shader program錯誤
	exit(EXIT_FAILURE);
}
mat_location = shaderProgram->getUniformLoc("transformMat");	// 獲取 着色器程序的 uniform 變量的 location
vpos_location = shaderProgram->getAttributeLoc("vPos");			// 獲取 頂點着色器中的頂點 attribute 屬性的 location
vcol_location = shaderProgram->getAttributeLoc("vCol");			// 獲取 頂點着色器中的頂點 attribute 屬性的 location
...

mat4x4 rMat, tMat, tranformMat;											// 聲明定義一個 mat4x4 用的旋轉矩陣

while (!glfwWindowShouldClose(window)) {					// 檢測是否需要關閉窗體

	glfwGetFramebufferSize(window, &width, &height);		// 獲取窗口大小

	glViewport(0, 0, width, height);						// 設置Viewport
	glClearColor(0.1f, 0.2f, 0.1f, 0.f);					// 設置清理顏色緩存時,填充顏色值
	glClear(GL_COLOR_BUFFER_BIT);							// 清理顏色緩存

	//glUseProgram(program);									// 使用此着色器程序,兩個 VAO 的着色都一樣,設置一些 uniform 不一樣
	shaderProgram->use();

	glBindVertexArray(vertex_array_object[0]);				// 先繪製 VAO[0] 的 VBO,EBO,VAF,ENABLED
	mat4x4_identity(tMat);									// 給矩陣單位化,消除之前的所有變換
	mat4x4_translate(tMat, -0.5, 0.0f, 0.0f);				// x軸位移-0.5,注意是NDC下的座標
	//glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tMat); // 設置, 着色器中 uniform mat4 rMat; 的矩陣數據
	shaderProgram->setMatrix4x4(mat_location, (const GLfloat*)tMat); // 設置, 着色器中 uniform mat4 rMat; 的矩陣數據
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 參數1:繪製三角圖元;參數2:取6個索引來繪製三角圖元(每個三角圖元需要3個,所以可以畫兩個三角圖元);參數3:將 GL_ELEMENT_ARRAY_BUFFER 每個元素視爲 uint 類型;參數4:設置索引緩存的字節偏移量。也可以設置爲另一個 緩存數據的指針,即:使用另一個數據。

	glBindVertexArray(vertex_array_object[1]);				// 先繪製 VAO[1] 的 VBO,EBO,VAF,ENABLED
	mat4x4_identity(rMat);									// 給矩陣單位化,消除之前的所有變換
	mat4x4_rotate_Z(rMat, rMat, (float)glfwGetTime());		// 先旋轉,沿着 z 軸旋轉,旋轉量爲當前 glfw 啓用到現在的時間點(秒)
	mat4x4_translate(tMat, +0.5, 0.0f, 0.0f);				// 再位移
	mat4x4_mul(tranformMat, tMat, rMat);					// 將旋轉與位移的變換合併
	//glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tranformMat); // 設置, 着色器中 uniform mat4 rMat; 的矩陣數據
	shaderProgram->setMatrix4x4(mat_location, (const GLfloat*)tranformMat); // 設置, 着色器中 uniform mat4 rMat; 的矩陣數據
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 參數1:繪製三角圖元;參數2:取6個索引來繪製三角圖元(每個三角圖元需要3個,所以可以畫兩個三角圖元);參數3:將 GL_ELEMENT_ARRAY_BUFFER 每個元素視爲 uint 類型;參數4:設置索引緩存的字節偏移量。也可以設置爲另一個 緩存數據的指針,即:使用另一個數據。

	glfwSwapBuffers(window);								// swap buffer, from backbuffer to front buffer
	glfwPollEvents();										// 處理其他的系統消息
}

直接加載源碼文件路徑

將上面 vertex_shader_text,與 fragment_shader_tesxt,分別保存與:shader1.vertshader1.frag 兩個文件。

絕對路徑方式

GLint mat_location, vpos_location, vcol_location;
ShaderProgram* shaderProgram = new ShaderProgram;
// shader program init 2 - 加載shader源碼路徑方式,我真的是服了C++獲取當前運行目錄就這麼難嗎?
char exeFullPath[512];
char vs_path[512], fs_path[512];
GetCurrentDirectoryA(1000, exeFullPath);
sprintf_s(vs_path, "%s\\Debug\\%s", exeFullPath, "shader1.vert");
sprintf_s(fs_path, "%s\\Debug\\%s", exeFullPath, "shader1.frag");
if (!shaderProgram->initByPath(vs_path, fs_path)) {
	std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 輸出shader program錯誤
	exit(EXIT_FAILURE);
}

將 shader 文件放到 Dependencies/Shaders 文件夾下加載

在這裏插入圖片描述

相對路徑方式

// shader program init 3 - 加載shader源碼的相對路徑,方面第二種方法的是絕對路徑
if (!shaderProgram->initByPath("Debug\\shader1.vert", "Debug\\shader1.frag")) {
	std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 輸出shader program錯誤
	exit(EXIT_FAILURE);
}

常量的方式

#define GET_SHADER(name) "..\\..\\Dependencies\\Shaders\\"#name
// shader program init 4 - 根據shader源碼的相對路徑(常量),加載deps下的shader
if (!shaderProgram->initByPath(GET_SHADER(shader1.vert), GET_SHADER(shader1.frag))) {
	std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 輸出shader program錯誤
	exit(EXIT_FAILURE);
}

變量的方式

// callback 函數簽名定義
typedef char* (__stdcall * GetShaderPathCallback)(char*, const char*);
// 先定義一個 callback,後面會封裝到一個頭文件讓外部可以設置 callback
GetShaderPathCallback g_GetShaderPathCallback = NULL;
...
// 用 lambda 設置,獲取 shader 目錄的回調,後面在封裝
g_GetShaderPathCallback = [](char* receiveBuff, const char* file)->char* {
	char buf[MAX_PATH];
	sprintf_s(buf, "..\\..\\Dependencies\\Shaders\\%s", file);
	strcpy_s(receiveBuff, MAX_PATH, buf);
	return receiveBuff;
};

// shader program init 5 - 根據shader源碼的相對路徑(變量),加載deps下的shader
char vs_path[MAX_PATH], fs_path[MAX_PATH];
g_GetShaderPathCallback(vs_path, "shader1.vert");
g_GetShaderPathCallback(fs_path, "shader1.frag");
if (!shaderProgram->initByPath(vs_path, fs_path)) {
	std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 輸出shader program錯誤
	exit(EXIT_FAILURE);
}

其中上面的 MAX_PATH 是 Window SDK 裏的一些頭文件有定義的。
在這裏插入圖片描述


其餘部分都一樣。

另外,有個地方是可以有兩種方式來使用的。

設置 uniform

就是對 uniform 、attribute 設置的方式,這裏以 uniform 爲例:

方式1

先獲取 uniform mat4 transformMat;location

GLint mat_location = shaderProgram->getUniformLoc("transformMat");	// 獲取 着色器程序的 uniform 變量的 location

再使用 mat_location 來設置 uniform 變量:

shaderProgram->setMatrix4x4(mat_location, (const GLfloat*)tranformMat);

方式2

直接使用 uniform 的變量名的字符串來定位、設置:

shaderProgram->setMatrix4x4("transformMat", (const GLfloat*)tranformMat);

這樣獲取 location 與設置 locationuniform 矩陣都是一句就搞定。

區別

方式1 再頻繁設置變量時,會相對高效,因爲不需要每次設置後先獲取 location
方式2 在不太頻繁設置變量時使用會方便很多。

完整源碼

shader.h - ShaderProgram

// jave.lin - 封裝 shader 類,封裝 着色器、着色器程序的編譯、鏈接、錯誤檢查、等,的重複操作

#ifndef _SHADER__H_
#define _SHADER__H_

#include<windows.h> 
#include<iostream>
#include<string>

#define LOC glGetUniformLocation(id(), name)

#define DEF_SET_UNIFORM_VEC(one, vec, type, flag)\
	inline void set##one(GLint loc, const type value) const {\
		glUniform1##flag(loc, value);\
	}\
	inline void set##vec##2(GLint loc, const type value1, const type value2) const {\
		glUniform2##flag(loc, value1, value2);\
	}\
	inline void set##vec##3(GLint loc, const type value1, const type value2, const type value3) const {\
		glUniform3##flag(loc, value1, value2, value3);\
	}\
	inline void set##vec##4(GLint loc, const type value1, const type value2, const type value3, const type value4) const {\
		glUniform4##flag(loc, value1, value2, value3, value4);\
	}\
	inline void set##one##Array(GLint loc, const int count, const type* value) const {\
		glUniform1##flag##v(loc, count, value);\
	}\
	inline void set##vec##2##Array(GLint loc, const int count, const type* value) const {\
		glUniform2##flag##v(loc, count, value);\
	}\
	inline void set##vec##3##Array(GLint loc, const int count, const type* value) const {\
		glUniform3##flag##v(loc, count, value);\
	}\
	inline void set##vec##4##Array(GLint loc, const int count, const type* value) const {\
		glUniform4##flag##v(loc, count, value);\
	}\
	inline void set##one(const char* name, const type value) const {\
		glUniform1##flag(LOC, value);\
	}\
	inline void set##vec##2(const char* name, const type value1, const type value2) const {\
		glUniform2##flag(LOC, value1, value2);\
	}\
	inline void set##vec##3(const char* name, const type value1, const type value2, const type value3) const {\
		glUniform3##flag(LOC, value1, value2, value3);\
	}\
	inline void set##vec##4(const char* name, const type value1, const type value2, const type value3, const type value4) const {\
		glUniform4##flag(LOC, value1, value2, value3, value4);\
	}\
	inline void set##one##Array(const char* name, const int count, const type* value) const {\
		glUniform1##flag##v(LOC, count, value);\
	}\
	inline void set##vec##2##Array(const char* name, const int count, const type* value) const {\
		glUniform2##flag##v(LOC, count, value);\
	}\
	inline void set##vec##3##Array(const char* name, const int count, const type* value) const {\
		glUniform3##flag##v(LOC, count, value);\
	}\
	inline void set##vec##4##Array(const char* name, const int count, const type* value) const {\
		glUniform4##flag##v(LOC, count, value);\
	}

#define DEF_SET_UNIFORM_MAT(type, flag)\
	inline void setMatrix2x2(GLint loc, const type* value) const {\
		glUniformMatrix2##flag##v(loc, 1, GL_FALSE, value);\
	}\
	inline void setMatrix3x3(GLint loc, const type* value) const {\
		glUniformMatrix4##flag##v(loc, 1, GL_FALSE, value);\
	}\
	inline void setMatrix4x4(GLint loc, const type* value) const {\
		glUniformMatrix4##flag##v(loc, 1, GL_FALSE, value);\
	}\
	inline void setMatrix2x3(GLint loc, const type* value) const {\
		glUniformMatrix2x3##flag##v(loc, 1, GL_FALSE, value);\
	}\
	inline void setMatrix3x2(GLint loc, const type* value) const {\
		glUniformMatrix2x3##flag##v(loc, 1, GL_FALSE, value);\
	}\
	inline void setMatrix2x4(GLint loc, const type* value) const {\
		glUniformMatrix2x4##flag##v(loc, 1, GL_FALSE, value);\
	}\
	inline void setMatrix4x2(GLint loc, const type* value) const {\
		glUniformMatrix4x2##flag##v(loc, 1, GL_FALSE, value);\
	}\
	inline void setMatrix3x4(GLint loc, const type* value) const {\
		glUniformMatrix3x4##flag##v(loc, 1, GL_FALSE, value);\
	}\
	inline void setMatrix4x3(GLint loc, const type* value) const {\
		glUniformMatrix4x3##flag##v(loc, 1, GL_FALSE, value);\
	}\
	inline void setMatrix2x2(const char* name, const type* value) const {\
		glUniformMatrix2##flag##v(LOC, 1, GL_FALSE, value);\
	}\
	inline void setMatrix3x3(const char* name, const type* value) const {\
		glUniformMatrix4##flag##v(LOC, 1, GL_FALSE, value);\
	}\
	inline void setMatrix4x4(const char* name, const type* value) const {\
		glUniformMatrix4##flag##v(LOC, 1, GL_FALSE, value);\
	}\
	inline void setMatrix2x3(const char* name, const type* value) const {\
		glUniformMatrix2x3##flag##v(LOC, 1, GL_FALSE, value);\
	}\
	inline void setMatrix3x2(const char* name, const type* value) const {\
		glUniformMatrix2x3##flag##v(LOC, 1, GL_FALSE, value);\
	}\
	inline void setMatrix2x4(const char* name, const type* value) const {\
		glUniformMatrix2x4##flag##v(LOC, 1, GL_FALSE, value);\
	}\
	inline void setMatrix4x2(const char* name, const type* value) const {\
		glUniformMatrix4x2##flag##v(LOC, 1, GL_FALSE, value);\
	}\
	inline void setMatrix3x4(const char* name, const type* value) const {\
		glUniformMatrix3x4##flag##v(LOC, 1, GL_FALSE, value);\
	}\
	inline void setMatrix4x3(const char* name, const type* value) const {\
		glUniformMatrix4x3##flag##v(LOC, 1, GL_FALSE, value);\
	}

#define DEF_SET_UNIFORM_MATS(type, flag)\
	inline void setMatrix2x2Array(GLint loc, const int count, const type* value) const {\
		glUniformMatrix2##flag##v(loc, count, GL_FALSE, value);\
	}\
	inline void setMatrix3x3Array(GLint loc, const int count, const type* value) const {\
		glUniformMatrix4##flag##v(loc, count, GL_FALSE, value);\
	}\
	inline void setMatrix4x4Array(GLint loc, const int count, const type* value) const {\
		glUniformMatrix4##flag##v(loc, count, GL_FALSE, value);\
	}\
	inline void setMatrix2x3Array(GLint loc, const int count, const type* value) const {\
		glUniformMatrix2x3##flag##v(loc, count, GL_FALSE, value);\
	}\
	inline void setMatrix3x2Array(GLint loc, const int count, const type* value) const {\
		glUniformMatrix2x3##flag##v(loc, count, GL_FALSE, value);\
	}\
	inline void setMatrix2x4Array(GLint loc, const int count, const type* value) const {\
		glUniformMatrix2x4##flag##v(loc, count, GL_FALSE, value);\
	}\
	inline void setMatrix4x2Array(GLint loc, const int count, const type* value) const {\
		glUniformMatrix4x2##flag##v(loc, count, GL_FALSE, value);\
	}\
	inline void setMatrix3x4Array(GLint loc, const int count, const type* value) const {\
		glUniformMatrix3x4##flag##v(loc, count, GL_FALSE, value);\
	}\
	inline void setMatrix4x3Array(GLint loc, const int count, const type* value) const {\
		glUniformMatrix4x3##flag##v(loc, count, GL_FALSE, value);\
	}\
	inline void setMatrix2x2Array(const char* name, const int count, const type* value) const {\
		glUniformMatrix2##flag##v(LOC, count, GL_FALSE, value);\
	}\
	inline void setMatrix3x3Array(const char* name, const int count, const type* value) const {\
		glUniformMatrix4##flag##v(LOC, count, GL_FALSE, value);\
	}\
	inline void setMatrix4x4Array(const char* name, const int count, const type* value) const {\
		glUniformMatrix4##flag##v(LOC, count, GL_FALSE, value);\
	}\
	inline void setMatrix2x3Array(const char* name, const int count, const type* value) const {\
		glUniformMatrix2x3##flag##v(LOC, count, GL_FALSE, value);\
	}\
	inline void setMatrix3x2Array(const char* name, const int count, const type* value) const {\
		glUniformMatrix2x3##flag##v(LOC, count, GL_FALSE, value);\
	}\
	inline void setMatrix2x4Array(const char* name, const int count, const type* value) const {\
		glUniformMatrix2x4##flag##v(LOC, count, GL_FALSE, value);\
	}\
	inline void setMatrix4x2Array(const char* name, const int count, const type* value) const {\
		glUniformMatrix4x2##flag##v(LOC, count, GL_FALSE, value);\
	}\
	inline void setMatrix3x4Array(const char* name, const int count, const type* value) const {\
		glUniformMatrix3x4##flag##v(LOC, count, GL_FALSE, value);\
	}\
	inline void setMatrix4x3Array(const char* name, const int count, const type* value) const {\
		glUniformMatrix4x3##flag##v(LOC, count, GL_FALSE, value);\
	}
//
// jave.lin - 封裝 着色器、着色器程序的編譯、鏈接、錯誤檢查、等,的重複操作
//
class ShaderProgram {
private:
	template<typename... Args>
	static void setLog(ShaderProgram* program, const char *format, Args...args) {
		char str[1024];
		int size = sprintf_s(str, format, args...);
		if (program->mErrorLog != NULL) {
			free(program->mErrorLog);
		}
		program->mErrorLog = (GLchar*)malloc(size + 1);
		if (program->mErrorLog == NULL) {
			std::cout << "setLog program->mErrorLog == NULL after malloc." << std::endl;
		}
		strcpy_s(program->mErrorLog, size + 1, str);
	}
public:
	// 銷燬這個着色器程序對象
	~ShaderProgram() {
		glDeleteProgram(id());
		if (mErrorLog != NULL) {
			free(mErrorLog);
			mErrorLog = NULL;
		}
	}
	// 使用shader源碼初始化(目前暫時只支持初始化一次,重複初始化返回 GL_FALSE)
	GLboolean initBySourceCode(
		const char* vs,
		const char* fs
		//, std::string tcs = NULL,
		//, std::string tes = NULL,
		//, std::string ges = NULL
	) {
		if (mProgram != 0) {
			setLog(this, "initBySourceCode Failure! ShaderProgram already inited, shaderProgram : %d", mProgram);
			return GL_FALSE;
		}

		GLuint vertex_shader, fragment_shader, program;
		GLint success, infoLogLen;

		vertex_shader = glCreateShader(GL_VERTEX_SHADER);				// 創建 頂點着色器
		glShaderSource(vertex_shader, 1, &vs, NULL);					// 設置 頂點着色器源碼
		glCompileShader(vertex_shader);									// 編譯 頂點着色器

		glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);		// 獲取着色器編譯狀態
		if (!success) {													// 如果編譯不成功
			glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &infoLogLen);
			GLchar* infoLog = (GLchar*)malloc(infoLogLen);				// 如果編譯失敗,則將編譯日誌儲存到:infoLog 中
			glGetShaderInfoLog(vertex_shader, infoLogLen, NULL, infoLog);
			setLog(this, "Vertex Shader Compiling Error Status: %d, Infomation Log : %s", success, infoLog);
			free(infoLog);
			glDeleteShader(vertex_shader);
			vertex_shader = 0;
			return GL_FALSE;
		}

		fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);				// 創建 片元着色器
		glShaderSource(fragment_shader, 1, &fs, NULL);						// 設置 片元着色器源碼
		glCompileShader(fragment_shader);									// 編譯 片元着色器

		glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);		// 獲取着色器編譯狀態
		if (!success) {														// 如果編譯不成功
			glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &infoLogLen);
			GLchar* infoLog = (GLchar*)malloc(infoLogLen);					// 如果編譯失敗,則將編譯日誌儲存到:infoLog 中
			glGetShaderInfoLog(fragment_shader, infoLogLen, NULL, infoLog);
			setLog(this, "Fragment Shader Compiling Error Status: %d, Infomation Log : %s", success, infoLog);
			free(infoLog);
			glDeleteShader(vertex_shader);
			glDeleteShader(fragment_shader);
			vertex_shader = 0;
			fragment_shader = 0;
			return GL_FALSE;
		}

		program = glCreateProgram();						// 創建着色器程序
		if (program == 0) {
			setLog(this, "CreateProgram Failure : %d", program);
			glDeleteShader(vertex_shader);
			glDeleteShader(fragment_shader);
			vertex_shader = 0;
			fragment_shader = 0;
			return GL_FALSE;
		}
		this->mProgram = program;							// 保存到全局變量
		glAttachShader(program, vertex_shader);				// 附加 頂點着色器
		glAttachShader(program, fragment_shader);			// 附加 片元着色器
		glLinkProgram(program);								// 鏈接着色器程序

		glGetProgramiv(program, GL_LINK_STATUS, &success);	// 如果鏈接不成功
		if (!success) {
			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
			GLchar* infoLog = (GLchar*)malloc(infoLogLen);	// 如果鏈接失敗,則將鏈接日誌儲存到:infoLog 中
			glGetProgramInfoLog(program, infoLogLen, NULL, infoLog);
			setLog(this, "Program Linking Error Status: %d, Infomation Log : %s", success, infoLog);
			free(infoLog);
			glDeleteProgram(program);
			program = 0;
			return GL_FALSE;
		}

		glDeleteShader(vertex_shader);						// 鏈接成功後,就可以刪掉vertex 子程序了
		glDeleteShader(fragment_shader);					// 鏈接成功後,就可以刪掉fragment 子程序了
		return GL_TRUE;
	}
	// 使用shader源碼的路徑來初始化(目前暫時只支持初始化一次,重複初始化返回 GL_FALSE)
	GLboolean initByPath(
		const char* vs_path,
		const char* fs_path
		//, std::string tcs_path = NULL,
		//, std::string tes_path = NULL,
		//, std::string ges_path = NULL
	) {
		if (mProgram != 0) {
			setLog(this, "initByPath Failure! ShaderProgram already inited, shaderProgram : %d", mProgram);
			return GL_FALSE;
		}
		char* vs = readFile(vs_path);
		if (vs == NULL) {
			setLog(this, "initByPath failure! vs == NULL after read vs file, vs_path : %s", vs_path);
			return GL_FALSE;
		}
		char* fs = readFile(fs_path);
		if (fs == NULL) {
			setLog(this, "initByPath failure! fs == NULL after read fs file, fs_path : %s", fs_path);
			return GL_FALSE;
		}
		try {
			GLboolean ret =  initBySourceCode(vs, fs);
			free(vs);
			free(fs);
			return ret;
		}
		catch (...) {
			setLog(this, "initByPath failure!");
			free(vs);
			free(fs);
			return GL_FALSE;
		}
	}
	// 返回着色器程序對象實例指針id,如果 id() == 0,那麼說明創建着色器程序失敗 或者 爲初始化
	inline const GLuint id() const {
		return this->mProgram;
	}
	// 返回着色器程序創建的錯誤日誌,返回 NULL 說明沒有錯誤
	inline const GLchar* errorLog() const {
		return this->mErrorLog;
	}
	// 相當於調用 glUseProgram(id());
	inline void use() const {
		glUseProgram(id());
	}
	// 獲取該 shader program 的 uniform location
	inline GLint getUniformLoc(const char* name) const {
		return LOC;
	}
	// 獲取該 shader program 中的 vertex shader 中的 attribute location
	inline GLint getAttributeLoc(const char* name) const {
		return glGetAttribLocation(id(), name);
	}
	// 定義float的uniforma
	DEF_SET_UNIFORM_VEC(Float, Vec, GLfloat, f);
	// 定義double的uniforma
	DEF_SET_UNIFORM_VEC(Double, DVec, GLdouble, d);
	// 定義int的uniforma
	DEF_SET_UNIFORM_VEC(Int, IVec, GLint, i);
	// 定義uint的uniforma
	DEF_SET_UNIFORM_VEC(UInt, UVec, GLuint, ui);
	// 定義float matrix的uniforma
	DEF_SET_UNIFORM_MAT(GLfloat, f);
	// 定義double matrix的uniforma
	DEF_SET_UNIFORM_MAT(GLdouble, d);
	// 定義float matrix array的uniforma
	DEF_SET_UNIFORM_MATS(GLfloat, f);
	// 定義double matrix array的uniforma
	DEF_SET_UNIFORM_MATS(GLdouble, d);
private:
	// 參考:https://www.khronos.org/opengl/wiki/Tutorial2:_VAOs,_VBOs,_Vertex_and_Fragment_Shaders_%28C_/_SDL%29#tutorial2.c
	/* A simple function that will read a file into an allocated char pointer buffer */
	char* readFile(const char* file) {
		FILE* fptr;
		long length;
		char* buf;

		fopen_s(&fptr, file, "rb"); /* Open file for reading */
		if (!fptr) { /* Return NULL on failure */
			setLog(this, "Error : reading file : %s failure!", file);
			return NULL;
		}
		fseek(fptr, 0, SEEK_END); /* Seek to the end of the file */
		length = ftell(fptr); /* Find out how many bytes into the file we are */
		buf = (char*)malloc(length + 1); /* Allocate a buffer for the entire length of the file and a null terminator */
		if (buf == NULL) {
			setLog(this, "Error : reading file : %s, create buf[%d] failure!", file, length + 1);
			fclose(fptr); /* Close the file */
			return NULL;
		}
		fseek(fptr, 0, SEEK_SET); /* Go back to the beginning of the file */
		fread(buf, length, 1, fptr); /* Read the contents of the file in to the buffer */
		fclose(fptr); /* Close the file */
		buf[length] = 0; /* Null terminator */

		return buf; /* Return the buffer */
	}
	GLuint mProgram = 0; // program 對象指針ID爲0是有問題,所以 0 可以作爲判斷使用
	GLchar* mErrorLog = NULL;
};

#endif // _SHADER__H_

Main.cpp 調用 ShaderProgram

// jave.lin
#include"glad/glad.h"
#include"GLFW/glfw3.h"
//#include"linmath.h"
// 把linmath.h 放在 iostream 之前include會有錯誤,所以放到iostream 後include就好了
// 而這個錯誤正式 xkeycheck.h 文件內 #error 提示的,所以可以使用 #define _XKEYCHECK_H 這個頭文件的引用標記宏
// 就可以避免對 xkeycheck.h 頭文件的 include 了。
#include<iostream>
#include"linmath.h"
#include"shader.h"

#define PRINT_VERSION // 打印版本相關信息

#ifdef PRINT_VERSION

#define PROFILE_NAME_CORE   "core"
#define PROFILE_NAME_COMPAT "compat"

static const char* get_api_name(int api) {
	if (api == GLFW_OPENGL_API)
		return "OpenGL";
	else if (api == GLFW_OPENGL_ES_API)
		return "OpenGL ES";

	return "Unknown API";
}

static const char* get_profile_name_gl(GLint mask) {
	if (mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
		return PROFILE_NAME_COMPAT;
	if (mask & GL_CONTEXT_CORE_PROFILE_BIT)
		return PROFILE_NAME_CORE;

	return "unknown";
}

// 打印各種版本信息
static void print_infos(GLFWwindow* window) {
	//
	// ====== GLFW Library的版本 ======
	//
	int glfw_major, glfw_minor, glfw_revision;
	glfwGetVersion(&glfw_major, &glfw_minor, &glfw_revision);

	// 頭文件聲明版本
	printf("GLFW header version: %u.%u.%u\n",
		GLFW_VERSION_MAJOR,
		GLFW_VERSION_MINOR,
		GLFW_VERSION_REVISION);
	// 庫版本
	printf("GLFW library version: %u.%u.%u\n", glfw_major, glfw_minor, glfw_revision);
	// 庫版本的字符串描述
	printf("GLFW library version string: \"%s\"\n", glfwGetVersionString());

	//
	// ====== client, context, profile 的版本 ======
	//
	int ch, client, major, minor, revision, profile;

	client = glfwGetWindowAttrib(window, GLFW_CLIENT_API);
	major = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR);
	minor = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR);
	revision = glfwGetWindowAttrib(window, GLFW_CONTEXT_REVISION);
	profile = glfwGetWindowAttrib(window, GLFW_OPENGL_PROFILE);

	printf("%s context version string: \"%s\"\n",
		get_api_name(client),
		glGetString(GL_VERSION));

	printf("%s context version parsed by GLFW: %u.%u.%u\n",
		get_api_name(client),
		major, minor, revision);


	GLint mask;
	glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask);

	printf("%s profile mask (0x%08x): %s\n",
		get_api_name(client),
		mask,
		get_profile_name_gl(mask));
	//
	// ====== render, vendor 的信息 ======
	//
	printf("%s context renderer string: \"%s\"\n",
		get_api_name(client),
		glGetString(GL_RENDERER));
	printf("%s context vendor string: \"%s\"\n",
		get_api_name(client),
		glGetString(GL_VENDOR));

	printf("%s context shading language version: \"%s\"\n",
		get_api_name(client),
		glGetString(GL_SHADING_LANGUAGE_VERSION));
}

#endif // PRINT_VERSION

// 這回我們的着色器程序的硬邊碼直接使用的是C++的 R"flag()flag" 的raw-string的方式來定義,書寫更方便
static const char* vertex_shader_text = R"glsl(
#version 450 compatibility
uniform mat4 transformMat;
attribute vec3 vPos;
attribute vec3 vCol;
varying vec3 fCol;
void main() {
	gl_Position = transformMat * vec4(vPos, 1.0);
	fCol = vCol;
}
)glsl";

static const char* fragment_shader_text = R"glsl(
#version 450 compatibility
varying vec3 fCol;
void main() {
	gl_FragColor = vec4(fCol, 1.0);
}
)glsl";

GLfloat vertices[] = {
	// x,	y,	  z
	// 直接放4個頂點
	-0.25f, -0.25f, 0.0f,						// 第0個頂點,左下角
	 0.25f, -0.25f, 0.0f,						// 第1個頂點,右下角
	 0.25f,  0.25f, 0.0f,						// 第2個頂點,右上角
	-0.25f,  0.25f, 0.0f,						// 第3個頂點,左上角
};

GLfloat colors_1[] = {							// 頂點顏色緩存數據1
	1.0f, 0.0f, 0.0f,							// 第0個頂點顏色
	0.0f, 1.0f, 0.0f,							// 第1個頂點顏色
	1.0f, 1.0f, 0.0f,							// 第2個頂點顏色
	0.0f, 0.0f, 1.0f,							// 第3個頂點顏色
};

GLfloat colors_2[] = {							// 頂點顏色緩存數據2
	1.0f, 1.0f, 0.0f,							// 第0個頂點顏色
	0.0f, 1.0f, 1.0f,							// 第1個頂點顏色
	1.0f, 1.0f, 1.0f,							// 第2個頂點顏色
	1.0f, 0.0f, 1.0f,							// 第3個頂點顏色
};

GLuint indices[] = {							// 注意索引從0開始!通過索引緩存來指定 圖元 組成 用的 頂點有哪些
	0, 1, 3,									// 放置頂點的索引,第一個三角形
	1, 2, 3										// 放置頂點的索引,第二個三角形
};

typedef char* (__stdcall * GetShaderPathCallback)(char*, const char*);
GetShaderPathCallback g_GetShaderPathCallback = NULL;

static void error_callback(int error, const char* description) {
	fprintf(stderr, "ErrorCode : %d(0x%08x), Error: %s\n", error, error, description);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { // 當鍵盤按鍵ESCAPE按下時,設置該window爲:需要關閉
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GLFW_TRUE);
}

int main() {
	glfwSetErrorCallback(error_callback); // 安裝glfw內部錯誤時的回調

	if (!glfwInit()) { // 初始化glfw
		std::cout << "glfwInit FAILURE" << std::endl; // 初始化失敗
		exit(EXIT_FAILURE);
	}
	// 設置最低的openGL 版本,major:主版本號,minor:次版本號
	// openGl 太低版本的話是不支持CORE Profile模式的
	// 會報錯:ErrorCode: 65540(0x00010004), Error : Context profiles are only defined for OpenGL version 3.2 and above
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
	// 根據上面的錯誤提示,至少使用3.2纔行,這裏我們使用4.5
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

	// core profile 下運行有問題,不顯示任何內容,但不會報錯。
	// 着色器編譯、着色器程序鏈接都沒有錯誤日誌信息。
	// 很有可能是因爲我參考的學習網站使用的API相對比較老,使用的是3.3的。
	//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	// 所以這裏我們不設置 major, minor的版本,默認使用本計算機能用的最高版本
	// 使用 compatibility profile 就有內容出現了。

	int width = 600;
	int height = 600;

	// 使用glfw創建窗體
	GLFWwindow* window = glfwCreateWindow(width, height, "jave.lin - Learning OpenGL - 04_02_ShaderProgram", NULL, NULL);
	if (window == NULL) {
		std::cout << "Failed to create GLFW window" << std::endl; // 構建窗體失敗
		glfwTerminate();
		exit(EXIT_FAILURE);
	}
	glfwMakeContextCurrent(window);
	glfwSetKeyCallback(window, key_callback); // 安裝glfw內部鍵盤按鍵的回調

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { // 裝載OpenGL的C函數庫
		std::cout << "Failed to initialize OpenGL context" << std::endl; // 裝載報錯
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

#ifdef PRINT_VERSION
	// 打印版本信息
	print_infos(window);
#endif

	int nrAttributes;
	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
	std::cout << "Maximum number of vertex attributes supported : " << nrAttributes << std::endl;

	GLint mat_location, vpos_location, vcol_location;
	GLuint vertex_buffer[3], index_buffer;
	GLuint vertex_array_object[2];
	GLint success, infoLogLen;

	ShaderProgram* shaderProgram = new ShaderProgram;
	//// shader program init 1 - 直接加載shader源碼方式
	//if (!shaderProgram->initBySourceCode(vertex_shader_text, fragment_shader_text)) {

	//// shader program init 2 - 加載shader源碼路徑方式,我真的是服了C++獲取當前運行目錄就這麼難嗎?
	//char exeFullPath[512];
	//char vs_path[512], fs_path[512];
	//GetCurrentDirectoryA(1000, exeFullPath);
	//sprintf_s(vs_path, "%s\\Debug\\%s", exeFullPath, "shader1.vert");
	//sprintf_s(fs_path, "%s\\Debug\\%s", exeFullPath, "shader1.frag");
	//if (!shaderProgram->initByPath(vs_path, fs_path)) {
	
	//// shader program init 3 - 加載shader源碼的相對路徑,方面第二種方法的是絕對路徑
	//if (!shaderProgram->initByPath("Debug\\shader1.vert", "Debug\\shader1.frag")) {
	//	std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 輸出shader program錯誤
	//	exit(EXIT_FAILURE);
	//}

//	// 這種宏定義只能處理常量路徑,所以如果要加載動態變量的路徑只能寫一個方法來處理
//#define GET_SHADER(name) "..\\..\\Dependencies\\Shaders\\"#name
//	// shader program init 4 - 根據shader源碼的相對路徑(常量),加載deps下的shader
//	if (!shaderProgram->initByPath(GET_SHADER(shader1.vert), GET_SHADER(shader1.frag))) {
//		std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 輸出shader program錯誤
//		exit(EXIT_FAILURE);
//	}

	// 用 lambda 設置,獲取 shader 目錄的回調,後面在封裝
	g_GetShaderPathCallback = [](char* receiveBuff, const char* file)->char* {
		char buf[MAX_PATH];
		sprintf_s(buf, "..\\..\\Dependencies\\Shaders\\%s", file);
		strcpy_s(receiveBuff, MAX_PATH, buf);
		return receiveBuff;
	};
	
	// shader program init 5 - 根據shader源碼的相對路徑(變量),加載deps下的shader
	char vs_path[MAX_PATH], fs_path[MAX_PATH];
	g_GetShaderPathCallback(vs_path, "shader1.vert");
	g_GetShaderPathCallback(fs_path, "shader1.frag");
	if (!shaderProgram->initByPath(vs_path, fs_path)) {
		std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 輸出shader program錯誤
		exit(EXIT_FAILURE);
	}

	mat_location = shaderProgram->getUniformLoc("transformMat");	// 獲取 着色器程序的 uniform 變量的 location
	vpos_location = shaderProgram->getAttributeLoc("vPos");			// 獲取 頂點着色器中的頂點 attribute 屬性的 location
	vcol_location = shaderProgram->getAttributeLoc("vCol");			// 獲取 頂點着色器中的頂點 attribute 屬性的 location

	glGenVertexArrays(2, vertex_array_object);						// 生成兩個 VAO

	glGenBuffers(3, vertex_buffer);									// 創建3個 VBO,這裏我們因爲有一個一樣的頂點,兩個不同的頂點顔色
	glGenBuffers(1, &index_buffer);									// 創建1個 EBO,因爲兩個 Quad 的頂點索引順序都是一樣的
	//
	// === VAO[0] ===
	//
	glBindVertexArray(vertex_array_object[0]);						// 綁定 VAO[0],那麼之後的 vbo, ebo,的綁定指針都是指向該 VAO 中的,還有頂點格式(規範)都會保存在該 VAO

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 綁定 VBO[0]
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 設置 VBO 座標數據

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]);				// 綁定 VBO[0]
	glBufferData(GL_ARRAY_BUFFER, sizeof(colors_1), colors_1, GL_STATIC_DRAW); // 設置 VBO 顏色數據

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);			// 綁定 EBO
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 設置 EBO 數據

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 綁定 VBO[0],因爲後面要設置該 VBO 的座標格式
	glVertexAttribPointer(vpos_location, 3, GL_FLOAT, GL_FALSE,		// 設置 頂點屬性 vPos 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vpos_location);						// 啓用 頂點緩存 location 位置的屬性

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]);				// 綁定 VBO[1],因爲後面要設置該 VBO 的顏色格式
	glVertexAttribPointer(vcol_location, 3, GL_FLOAT, GL_FALSE,		// 設置 頂點屬性 vCol 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vcol_location);						// 啓用 頂點緩存 location 位置的屬性

	//
	// === VAO[1] ===
	//
	glBindVertexArray(vertex_array_object[1]);						// 綁定 VAO[1],那麼之後的 vbo, ebo,的綁定指針都是指向該 VAO 中的,還有頂點格式(規範)都會保存在該 VAO

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 綁定 VBO[1]
	//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 設置 VBO 座標數據,這裏不用再設置座標,因爲都一樣

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[2]);				// 綁定 VBO[2]
	glBufferData(GL_ARRAY_BUFFER, sizeof(colors_2), colors_2, GL_STATIC_DRAW); // 設置 VBO 顏色數據,顏色就需要重新設置了,因爲不一樣

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);			// 綁定 EBO
	//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 設置 EBO 數據,這裏不用再設置索引值,因爲都一樣

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 綁定 VBO[0],因爲後面要設置該 VBO 的座標格式
	glVertexAttribPointer(vpos_location, 3, GL_FLOAT, GL_FALSE,		// 設置 頂點屬性 vPos 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vpos_location);						// 啓用 頂點緩存 location 位置的屬性

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[2]);				// 綁定 VBO[2],因爲後面要設置該 VBO 的顏色格式
	glVertexAttribPointer(vcol_location, 3, GL_FLOAT, GL_FALSE,		// 設置 頂點屬性 vCol 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vcol_location);						// 啓用 頂點緩存 location 位置的屬性

	//glEnable(GL_CULL_FACE);										// 開啓面向剔除
	//glCullFace(GL_BACK);										// 設置剔除背面
	GLboolean cf = glIsEnabled(GL_CULL_FACE);					// 查看是否啓用面向剔除
	std::cout << "cull face enabled : " << (cf ? "true" : "false") << std::endl;

	//glFrontFace(GL_CW);											// 順時針
	//glFrontFace(GL_CCW);										// 逆時針(默認的)ClockWise
	GLint facing;
	glGetIntegerv(GL_FRONT_FACE, &facing);						// 獲取正面的順逆時針 : CW(ClockWise - 順時針), CCW(Counter ClockWise - 逆時針)
	std::cout << "facing : " << (facing == GL_CW ? "CW" : "CCW") << std::endl;

	mat4x4 rMat, tMat, tranformMat;											// 聲明定義一個 mat4x4 用的旋轉矩陣

	while (!glfwWindowShouldClose(window)) {					// 檢測是否需要關閉窗體

		glfwGetFramebufferSize(window, &width, &height);		// 獲取窗口大小

		glViewport(0, 0, width, height);						// 設置Viewport
		glClearColor(0.1f, 0.2f, 0.1f, 0.f);					// 設置清理顏色緩存時,填充顏色值
		glClear(GL_COLOR_BUFFER_BIT);							// 清理顏色緩存

		//glUseProgram(program);									// 使用此着色器程序,兩個 VAO 的着色都一樣,設置一些 uniform 不一樣
		shaderProgram->use();

		glBindVertexArray(vertex_array_object[0]);				// 先繪製 VAO[0] 的 VBO,EBO,VAF,ENABLED
		mat4x4_identity(tMat);									// 給矩陣單位化,消除之前的所有變換
		mat4x4_translate(tMat, -0.5, 0.0f, 0.0f);				// x軸位移-0.5,注意是NDC下的座標
		//glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tMat); // 設置, 着色器中 uniform mat4 rMat; 的矩陣數據
		shaderProgram->setMatrix4x4(mat_location, (const GLfloat*)tMat); // 設置, 着色器中 uniform mat4 rMat; 的矩陣數據
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 參數1:繪製三角圖元;參數2:取6個索引來繪製三角圖元(每個三角圖元需要3個,所以可以畫兩個三角圖元);參數3:將 GL_ELEMENT_ARRAY_BUFFER 每個元素視爲 uint 類型;參數4:設置索引緩存的字節偏移量。也可以設置爲另一個 緩存數據的指針,即:使用另一個數據。

		glBindVertexArray(vertex_array_object[1]);				// 先繪製 VAO[1] 的 VBO,EBO,VAF,ENABLED
		mat4x4_identity(rMat);									// 給矩陣單位化,消除之前的所有變換
		mat4x4_rotate_Z(rMat, rMat, (float)glfwGetTime());		// 先旋轉,沿着 z 軸旋轉,旋轉量爲當前 glfw 啓用到現在的時間點(秒)
		mat4x4_translate(tMat, +0.5, 0.0f, 0.0f);				// 再位移
		mat4x4_mul(tranformMat, tMat, rMat);					// 將旋轉與位移的變換合併
		//glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tranformMat); // 設置, 着色器中 uniform mat4 rMat; 的矩陣數據
		shaderProgram->setMatrix4x4(mat_location, (const GLfloat*)tranformMat); // 設置, 着色器中 uniform mat4 rMat; 的矩陣數據
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 參數1:繪製三角圖元;參數2:取6個索引來繪製三角圖元(每個三角圖元需要3個,所以可以畫兩個三角圖元);參數3:將 GL_ELEMENT_ARRAY_BUFFER 每個元素視爲 uint 類型;參數4:設置索引緩存的字節偏移量。也可以設置爲另一個 緩存數據的指針,即:使用另一個數據。

		glfwSwapBuffers(window);								// swap buffer, from backbuffer to front buffer
		glfwPollEvents();										// 處理其他的系統消息
	}

	delete shaderProgram;										// 銷燬 shader program

	glfwDestroyWindow(window);									// 銷燬之前創建的window對象
	glfwTerminate();											// 清理glfw之前申請的資源
	return 0;
} // int main() {

總結

ShaderProgram 封裝後,外部使用流程就會簡單很多。

  • 實例化 ShaderProgram(new)。
  • 初始化 ShaderProgram(init)。
  • 檢測錯誤 (ShaderProgram->errorLog() != NULL)。
  • 使用 ShaderProgram (use)。
  • 設置 uniform 或是 attribute。

完事。

References

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