改进LearnOpenGL中的Shader类
简介
LearnOpenGL教程中使用一个Shader
类从文件中读取着色器,然后编译并链接他们。随着学习的深入,我学到了更多的着色器类型,这个Shader
类已经不能满足学习的编码需求了。因此,本文对Shader
类进行改进,以支持编译和链接更多的着色器类型。
改进说明
对LearnOpenGL中的Shader
类的改进思路如下:
1.类名
在OpenGL中,Shader(着色器)和Program(程序)是两个不同的概念。Shader是用于在图形渲染管线中执行特定任务的程序。它们是在GPU上执行的小型程序,用于处理图形数据的不同方面,如顶点位置、颜色、光照等。在OpenGL中,常见的着色器类型有顶点着色器(Vertex Shader)和片段着色器(Fragment Shader),它们分别处理顶点和像素级别的计算。Program是由多个Shader组合而成的程序对象。它是OpenGL中用于管理和执行着色器的组合的实体。Program对象可以由一个或多个Shader对象连接而成,以便在渲染过程中执行特定的图形任务。Program对象可以包含顶点着色器、片段着色器,以及其他类型的着色器,如几何着色器、计算着色器等。
为了能够更清楚地区分Shader和Program的概念,这个类应该命名为Program。同时,考虑到这个类的使用场景,最终将其更名为ShaderProgram
。
2.构造
原始的Shader
类构建时传递两个着色器源码文件的路径作为参数,分别读取两个文件创建着色器,然后编译并链接生成program。然而,可用的着色器不止有两种,而且有的着色器是可选项,这意味着构造的参数是可变的。
因此,使用可变参数实现program的构造过程,以支持灵活创建启用不同数量着色器的program。
3.枚举
不同类型shader的从文件读取,创建并编译的过程是一致的,可以将其统一起来,使用枚举类型以区分不同类型的shader。
新增枚举类型ShaderType
用于区分不同的shader类型。
4.错误提示
对于编译shader和链接生成program的过程进行错误检查的时,丰富输出的信息,方便定位到具体的着色器类型。
ShaderProgram
以下为改进过后的ShaderProgram
类:
#ifndef SHADER_PROGRAM_H
#define SHADER_PROGRAM_H
#include "glad/glad.h"
#include "glm.hpp"
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <unordered_map>
class ShaderProgram{
public:
// 用于构建program的shader类型
enum class ShaderType{
VERTEX = GL_VERTEX_SHADER,
TESSCONTROL = GL_TESS_CONTROL_SHADER,
TESSEVALUATION = GL_TESS_EVALUATION_SHADER,
GEOMETRY = GL_GEOMETRY_SHADER,
FRAGMENT = GL_FRAGMENT_SHADER,
};
using ShaderSourcePair = std::pair<ShaderType, std::string>;
template<typename... Args>
ShaderProgram(Args... args){
ID = glCreateProgram();
compileAndAttachShader(args...);
glLinkProgram(ID);
checkProgramLinkErrors();
deleteShaders();
}
~ShaderProgram()
{
glDeleteProgram(ID);
}
// activate the shader
// ------------------------------------------------------------------------
void use() const
{
glUseProgram(ID);
}
// utility uniform functions
// ------------------------------------------------------------------------
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);
}
// ------------------------------------------------------------------------
void setVec2(const std::string &name, const glm::vec2 &value) const
{
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec2(const std::string &name, float x, float y) const
{
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}
// ------------------------------------------------------------------------
void setVec3(const std::string &name, const glm::vec3 &value) const
{
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec3(const std::string &name, float x, float y, float z) const
{
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}
// ------------------------------------------------------------------------
void setVec4(const std::string &name, const glm::vec4 &value) const
{
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec4(const std::string &name, float x, float y, float z, float w) const
{
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}
// ------------------------------------------------------------------------
void setMat2(const std::string &name, const glm::mat2 &mat) const
{
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat3(const std::string &name, const glm::mat3 &mat) const
{
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat4(const std::string &name, const glm::mat4 &mat) const
{
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
private:
void compileAndAttachShader(void) {}
void compileAndAttachShader(const ShaderSourcePair& shaderSourcePair){
std::string shaderCode;
std::ifstream shaderFile;
// ensure ifstream objects can throw exceptions:
shaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
try
{
// open files
shaderFile.open(shaderSourcePair.second);
std::stringstream shaderStream;
// read file's buffer contents into streams
shaderStream << shaderFile.rdbuf();
// close file handlers
shaderFile.close();
// convert stream into string
shaderCode = shaderStream.str();
}
catch (std::ifstream::failure& e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
std::cout << "FILE_NAME: " << shaderSourcePair.second << std::endl;
}
const char* shaderCodeCStr = shaderCode.c_str();
unsigned int shader = glCreateShader(static_cast<GLenum>(shaderSourcePair.first));
glShaderSource(shader, 1, &shaderCodeCStr, NULL);
glCompileShader(shader);
checkShaderCompileErrors(shader, shaderSourcePair.first);
glAttachShader(ID, shader);
shaderIDs.push_back(shader);
}
template<typename... Args>
void compileAndAttachShader(const ShaderSourcePair& shaderSourcePair, Args... args){
compileAndAttachShader(shaderSourcePair);
compileAndAttachShader(args...);
}
void deleteShaders(void){
for(unsigned int shader : shaderIDs){
glDeleteShader(shader);
}
shaderIDs.clear();
}
void checkShaderCompileErrors(GLuint shader, ShaderType type)
{
GLint success;
GLchar infoLog[1024];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << shaderTypeMap[type] << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
void checkProgramLinkErrors(void){
GLint success;
GLchar infoLog[1024];
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(ID, 1024, NULL, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR: \n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
private:
unsigned int ID;
std::vector<unsigned int> shaderIDs;
static std::unordered_map<ShaderType, const char*> shaderTypeMap;
};
std::unordered_map<ShaderProgram::ShaderType, const char*> ShaderProgram::shaderTypeMap = {
{ShaderProgram::ShaderType::VERTEX, "VERTEX"},
{ShaderProgram::ShaderType::TESSCONTROL, "TESS_CONTROL"},
{ShaderProgram::ShaderType::TESSEVALUATION, "TESS_EVALUATION"},
{ShaderProgram::ShaderType::GEOMETRY, "GEOMETRY"},
{ShaderProgram::ShaderType::FRAGMENT, "FRAGMENT"}
};
#endif
值得一提的是,创建shader和program作为两种不同的职责,应当对它们进行分离。如果需要增加新的着色器,那么只需要扩展ShaderType
枚举类和对应的ShaderTypeMap
即可。
使用方法
除了构造时有所不同,ShaderProgram
类的使用方法和原有的类基本一致。以下为使用的示例(请将着色器源码文件路径修改为自己的路径):
using ShaderSourcePair = ShaderProgram::ShaderSourcePair;
using ShaderType = ShaderProgram::ShaderType;
ShaderProgram shaderProgram = ShaderProgram(
ShaderSourcePair{ShaderType::VERTEX, "path/to/your/vertesShader"},
ShaderSourcePair{ShaderType::TESSCONTROL, "path/to/your/tescShader"},
ShaderSourcePair{ShaderType::TESSEVALUATION, "path/to/your/teseShader"},
ShaderSourcePair{ShaderType::FRAGMENT, "path/to/your/fragmentShader"}
);
Comments | NOTHING