文章目录
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.vert
和 shader1.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
与设置 location
的 uniform
矩阵都是一句就搞定。
区别
方式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。
完事。