1 OpenGL着色器Shader创建编译使用

1.1 使用Shader的大致过程

在OpenGL中,如果在程序中需要使用Shader为基础图元进行着色,首先需要创建一个Shader对象,然后创建一个着色器程序关联当前Shader,然后再进行使用。

对于每一个Shader对象,都需要:

  • 创建一个Shader对象
  • 编译Shader代码
  • 验证Shader代码是否编译成功

在创建完Shader对象之后,如果我们需要使用这个Shader对象对图元进行操作,都需要:

  • 创建一个着色器程序
  • 将之前创建的Shader对象关联到当前的着色器程序
  • 链接着色器程序
  • 判断着色器程序链接过程是否完成
  • 使用着色器程序处理顶点和片段

1.2 相关的OpenGL API函数

1.2.1 glCreateShader

函数作用

创建和分配一个着色器对象。

函数形式

GLuint glCreateShader(GLenum type)

函数参数

  • type:我们可以通过glCreateShader函数创建多种类型的Shader对象,比如顶点着色器、片段着色器、细分着色器、几何着色器等等。所以type可以是以下枚举GL_VERTEX_SHADERGL_FRAGMENT_SHADERGL_TESS_CONTROL_SHADERGL_TESS_EVALUATION_SHADERGL_GEOMETRY_SHADER

函数返回值

如果成功返回一个非零的整数值,这个整数值就是所创建的Shader 对象的ID;如果失败返回0。

1.2.2 glShaderSource

函数作用

将着色器源代码关联到一个着色器对象上。

函数形式

void glShaderSource(GLuint shader,GLsizei count,const GLchar** string,const GLint* length)

函数参数

  • shader:所需关联的着色器对象ID
  • count:源代码行数
  • string:由count行GLchar类型字符串组成的数组,用于表示着色器的源代码数据,string可以是NULL结尾的也可以不是NULL结尾的
  • length:length的取值可以有以下几种。第一种,如果length是NULL,则假设string给出的每行字符串都是NULL结尾的,否则length中必须有count个元素,它们分别表示string中对应行的长度。第二种,如果length数组中的某一个值是一个整数,那么它表示对应的字符串中的字符数。如果某个值是负数,那么string中对应行假设为NULL结尾。

函数返回值

1.2.3 glCompileShader

函数作用

编译指定shader对象的源代码

函数形式

void glCompileShader(GLuint shader)

函数参数

  • shader:所需编译的着色器ID

函数返回值

1.2.4 glCreateProgram

函数作用

创建一个空的着色器程序

函数形式

GLuint glCreateProgram(void)

函数参数

函数返回值

如果成功返回一个非零的整数值,这个整数值就是所创建的着色器程序的ID;如果失败返回0。

1.2.5 glAttachShader

函数作用

将Shader对象关联到着色器程序program上

函数形式

void glAttachShader(Gluint program,GLuint shader)

函数参数

  • program:着色器程序ID
  • shader:shader对象ID

函数返回值

1.2.6 glDetachShader

函数作用

移除Shader对象与着色器程序program的关联

函数形式

void glDetachShader(Gluint program,GLuint shader)

函数参数

  • program:着色器程序ID
  • shader:shader对象ID

函数返回值

1.2.7 glLinkProgram

函数作用

在所有需要的Shader关联到着色器程序program之后,链接所有的Shader对象生成一个完整的着色器程序

函数形式

void glLinkProgram(GLuint program)

函数参数

  • program:着色器程序ID

函数返回值

1.2.8 glUseProgram

函数作用

使用链接好的着色器程序program

函数形式

void glUseProgram(GLuint program)

函数参数

  • program:着色器程序ID,如果program为零,则当前所有的使用的着色器程序都会被清除。如果没有绑定任何着色器,那么OpenGL的操作结果是未定义的,虽然操作结果是未定义的,但是不会产生错误。

如果已经启用了一个着色器程序,而它需要关联新的着色器对象,或者解除之前关联的对象,那么我们需要重新对它进行链接。如果链接过程成功,那么新的着色器程序会直接替代之前启用的着色器程序。如果链接失败,那么当前绑定的着色器程序依然是可用的,不会被替代,直到我们重新链接或者使用glUseProgram指定了新的着色器程序为止。

函数返回值

1.2.9 glDeleteShader

函数作用

删除着色器Shader对象

函数形式

void glDeleteShader(GLuint shader)

函数参数

  • shader:需要删除的shader对象的ID

函数返回值

1.2.10 glDeleteProgram

函数作用

立即删除一个当前没有在任何环境中使用的着色器程序

函数形式

void glDeleteProgram(GLuint program)

函数参数

  • program:需要删除的着色器程序的ID

函数返回值

1.2.11 glIsShader

函数作用

判断某个Shader对象是否存在

函数形式

GLboolean glIsShader(GLuint shader)

函数参数

  • shader:需要判断的shader对象的ID

函数返回值

如果shader是一个通过glCreateShader()生成的Shader对象,并且没有被删除,则返回GL_TRUE;如果shader为零或者不是Shader对象ID,则返回GL_FALSE

1.2.12 glIsProgram

函数作用

判断某个着色器程序是否存在

函数形式

GLboolean glIsProgram(GLuint program)

函数参数

  • program:需要判断的着色器对象的ID

函数返回值

如果program是一个通过glCreateProgram()生成的着色器对象,并且没有被删除,则返回GL_TRUE;如果program为零或者不是着色器对象ID,则返回GL_FALSE

1.3 封装的Shader类

在上一节中总结了opengl中几乎所有与shader操作相关的api函数,为了更好的使用上述的api,我们将上述api使用面向对象的思想封装为Shader类。

Shader.h

#ifndef ENGINE_GRAPHICS_BASE_SHADER_H
#define ENGINE_GRAPHICS_BASE_SHADER_H

#include <string>
#include <vector>

#include "EngineUtils/PancakeEngineProjectHeader.h"

namespace PancakeEngine
{
    class Shader
    {
    public:
        Shader();
        Shader(
            const std::string& shader_name, 
            const std::string& vertex_shader_path, 
            const std::string& fragment_shader_path
        );
        Shader(
            const std::string& shader_name,
            const std::string& vertex_shader_path,
            const std::string& fragment_shader_path,
            const std::string& geometry_shader_path
        );
        virtual~Shader();

    public:
        void Use();
        static void UnUse();
        unsigned int GetShaderIndex();
        std::string GetShaderName();

        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;
        void SetVec2(const std::string& name, const glm::vec2& value) const;
        void SetVec2(const std::string& name, float x, float y) const;
        void SetVec3(const std::string& name, const glm::vec3& value) const;
        void SetVec3(const std::string& name, float x, float y, float z) const;
        void SetVec4(const std::string& name, const glm::vec4& value) const;
        void SetVec4(const std::string& name, float x, float y, float z, float w);
        void SetMat2(const std::string& name, const glm::mat2& mat) const;
        void SetMat3(const std::string& name, const glm::mat3& mat) const;
        void SetMat4(const std::string& name, const glm::mat4& mat) const;
        void SetMat4Vector(const std::string& name, const std::vector<glm::mat4>& mat4Vector);
        void SetFloatVector(const std::string& name, const std::vector<float>& floatArray, unsigned int length)const;

    private:
        std::string ReadShaderFromFile(const std::string& path);
        void CheckCompileErrors(unsigned int shader, std::string type);

    private:
        unsigned int m_ShaderId;
        std::string m_ShaderName;
    };
}

#endif // !ENGINE_GRAPHICS_BASE_SHADER_H

Shader.cpp

#include "Shader.h"

#include <fstream>
#include <sstream>

#include "EngineUtils/PancakeEngineProjectHeader.h"

namespace PancakeEngine
{
    Shader::Shader()
        :m_ShaderId(-1)
    {

    }
    Shader::Shader(
        const std::string& shader_name, 
        const std::string& vertex_shader_path, 
        const std::string& fragment_shader_path)
        :m_ShaderId(-1)
    {
        // 1 从文件中读取顶点着色器和片段着色器源码
        std::string vertex_shader_code;
        std::string fragment_shader_code;
        vertex_shader_code = ReadShaderFromFile(vertex_shader_path);
        fragment_shader_code = ReadShaderFromFile(fragment_shader_path);
        const char* vertext_shader_code_char = vertex_shader_code.c_str();
        const char* fragment_shader_code_char = fragment_shader_code.c_str();

        // 2 编译shader
        unsigned int vertex_shader_id;
        unsigned int fragment_shader_id;

        vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex_shader_id, 1, &vertext_shader_code_char, nullptr);
        glCompileShader(vertex_shader_id);
        CheckCompileErrors(vertex_shader_id, "VERTEX");

        fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment_shader_id, 1, &fragment_shader_code_char, nullptr);
        glCompileShader(fragment_shader_id);
        CheckCompileErrors(fragment_shader_id, "FRAGMENT");

        // 3 创建着色器程序
        m_ShaderId = glCreateProgram();
        glAttachShader(m_ShaderId, vertex_shader_id);
        glAttachShader(m_ShaderId, fragment_shader_id);
        glLinkProgram(m_ShaderId);
        CheckCompileErrors(m_ShaderId, "PROGRAM");

        // 4 删除着色器
        glDeleteShader(vertex_shader_id);
        glDeleteShader(fragment_shader_id);
    }
    Shader::Shader(
        const std::string& shader_name, 
        const std::string& vertex_shader_path, 
        const std::string& fragment_shader_path, 
        const std::string& geometry_shader_path)
    {
        // 1 从文件中读取顶点着色器和片段着色器源码
        std::string vertex_shader_code;
        vertex_shader_code = ReadShaderFromFile(vertex_shader_path);
        const char* vertext_shader_code_char = vertex_shader_code.c_str();

        std::string fragment_shader_code;
        fragment_shader_code = ReadShaderFromFile(fragment_shader_path);
        const char* fragment_shader_code_char = fragment_shader_code.c_str();

        std::string geometry_shader_code;
        geometry_shader_code = ReadShaderFromFile(geometry_shader_path);
        const char* geometry_shader_code_char = geometry_shader_code.c_str();

        // 2 编译shader
        unsigned int vertex_shader_id;
        unsigned int fragment_shader_id;
        unsigned int geometry_shader_id;

        vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex_shader_id, 1, &vertext_shader_code_char, nullptr);
        glCompileShader(vertex_shader_id);
        CheckCompileErrors(vertex_shader_id, "VERTEX");

        fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment_shader_id, 1, &fragment_shader_code_char, nullptr);
        glCompileShader(fragment_shader_id);
        CheckCompileErrors(fragment_shader_id, "FRAGMENT");

        geometry_shader_id = glCreateShader(GL_GEOMETRY_SHADER);
        glShaderSource(geometry_shader_id, 1, &geometry_shader_code_char, nullptr);
        glCompileShader(geometry_shader_id);
        CheckCompileErrors(geometry_shader_id, "GEOMETRY");

        // 3 创建着色器程序
        m_ShaderId = glCreateProgram();
        glAttachShader(m_ShaderId, vertex_shader_id);
        glAttachShader(m_ShaderId, fragment_shader_id);
        glAttachShader(m_ShaderId, geometry_shader_id);
        glLinkProgram(m_ShaderId);
        CheckCompileErrors(m_ShaderId, "PROGRAM");

        // 4 删除着色器
        glDeleteShader(vertex_shader_id);
        glDeleteShader(fragment_shader_id);
        glDeleteShader(geometry_shader_id);
    }
    Shader::~Shader()
    {
    }

    void Shader::Use()
    {
        glUseProgram(m_ShaderId);
    }

    void Shader::UnUse()
    {
        glUseProgram(0);
    }

    unsigned int Shader::GetShaderIndex()
    {
        return m_ShaderId;
    }

    std::string Shader::GetShaderName()
    {
        return m_ShaderName;
    }

    void Shader::SetBool(const std::string& name, bool value) const
    {
        glUniform1i(glGetUniformLocation(m_ShaderId, name.c_str()), static_cast<int>(value));
    }

    void Shader::SetInt(const std::string& name, int value) const
    {
        glUniform1i(glGetUniformLocation(m_ShaderId, name.c_str()), value);
    }

    void Shader::SetFloat(const std::string& name, float value) const
    {
        glUniform1f(glGetUniformLocation(m_ShaderId, name.c_str()), value);
    }

    void Shader::SetVec2(const std::string& name, const glm::vec2& value) const
    {
        glUniform2fv(glGetUniformLocation(m_ShaderId, name.c_str()), 1, &value[0]);
    }

    void Shader::SetVec2(const std::string& name, float x, float y) const
    {
        glUniform2f(glGetUniformLocation(m_ShaderId, name.c_str()), x, y);
    }

    void Shader::SetVec3(const std::string& name, const glm::vec3& value) const
    {
        glUniform3fv(glGetUniformLocation(m_ShaderId, name.c_str()), 1, &value[0]);
    }

    void Shader::SetVec3(const std::string& name, float x, float y, float z) const
    {
        glUniform3f(glGetUniformLocation(m_ShaderId, name.c_str()), x, y, z);
    }

    void Shader::SetVec4(const std::string& name, const glm::vec4& value) const
    {
        glUniform4fv(glGetUniformLocation(m_ShaderId, name.c_str()), 1, &value[0]);
    }

    void Shader::SetVec4(const std::string& name, float x, float y, float z, float w)
    {
        glUniform4f(glGetUniformLocation(m_ShaderId, name.c_str()), x, y, z, w);
    }

    void Shader::SetMat2(const std::string& name, const glm::mat2& mat) const
    {
        glUniformMatrix2fv(glGetUniformLocation(m_ShaderId, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void Shader::SetMat3(const std::string& name, const glm::mat3& mat) const
    {
        glUniformMatrix3fv(glGetUniformLocation(m_ShaderId, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void Shader::SetMat4(const std::string& name, const glm::mat4& mat) const
    {
        glUniformMatrix4fv(glGetUniformLocation(m_ShaderId, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void Shader::SetMat4Vector(const std::string& name, const std::vector<glm::mat4>& mat4Vector)
    {
        glUniformMatrix4fv(glGetUniformLocation(m_ShaderId, name.c_str()), mat4Vector.size(), GL_FALSE, glm::value_ptr(mat4Vector[0]));
    }

    void Shader::SetFloatVector(const std::string& name, const std::vector<float>& floatArray, unsigned int length) const
    {
        glUniform4fv(glGetUniformLocation(m_ShaderId, name.c_str()), length, floatArray.data());
    }

    std::string Shader::ReadShaderFromFile(const std::string& path)
    {
        std::string code;
        std::ifstream file;

        // 读取着色器文件
        file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try
        {
            file.open(path);
            if (!file.is_open())
            {
                LOG(ERROR) << "Shader: Read shader file" << path << "is failed" << std::endl;

                return code;
            }
            std::stringstream stream;
            stream << file.rdbuf();
            file.close();
            code = stream.str();
        }
        catch (std::ifstream::failure e)
        {
            LOG(ERROR) << "Shader: Read shader file" << path << "is failed" << std::endl;
        }

        return code;
    }
    void Shader::CheckCompileErrors(unsigned int shader, std::string type)
    {
        GLint success;
        GLchar infoLog[1024];
        if (type != "PROGRAM")
        {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                LOG(ERROR) << "ERROR::SHADER_COMPILATION_ERROR of type: "
                    << type << "\n" << infoLog
                    << "\n -- --------------------------------------------------- -- "
                    << std::endl;
            }
        }
        else
        {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success)
            {
                glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                LOG(ERROR) << "ERROR::PROGRAM_LINKING_ERROR of type: "
                    << type << "\n" << infoLog
                    << "\n -- --------------------------------------------------- -- "
                    << std::endl;
            }
        }
    }
}