改进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"}
);

当珍惜每一片时光~