前言

这章做一点小小的代码改进,将Shader类笼统成接口,完成类OpenGLShader放到Platform/OpenGL下,以确保Renderer层是跨渠道的。

Shader笼统与完成

Shader笼统

Shader中的函数规划成纯虚函数,变成一个纯接口的笼统类。
Sandbox/Hazel/src/Hazel/Renderer/Shader.h

class Shader {
public:
    ~Shader() = default;
    virtual void Bind() const = 0;
    virtual void Unbind() const = 0;
    static Shader* Create(const std::string& vertexSrc, const std::string& fragmentSrc);
};

Sandbox/Hazel/src/Hazel/Renderer/Shader.cpp

Shader *Shader::Create(const std::string &vertexSrc, const std::string &fragmentSrc) {
    switch (Renderer::GetAPI()) {
        case RendererAPI::API::None:
            HZ_CORE_ASSERT(false, "RendererAPI::None is currently not supported!");
            return nullptr;
        case RendererAPI::API::OpenGL:
            return new OpenGLShader(vertexSrc, fragmentSrc);

Shader完成-OpenGLShader

之前Shader中的逻辑挪到OpenGLShader中,除了Bind()、Unbind()方法,再额定补充了一组更新uniform变量的接口:
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.h

#pragma once
#include "Renderer/Shader.h"
namespace Hazel {
    class OpenGLShader : public Shader {
    public:
        OpenGLShader(const std::string& vertexSrc, const std::string& fragmentSrc);
        ~OpenGLShader();
        void Bind() const override;
        void Unbind() const override;
        void UploadUniformInt(const std::string& name, int value);
        void UploadUniformFloat(const std::string& name, float value);
        void UploadUniformFloat2(const std::string& name, const glm::vec2& value);
        void UploadUniformFloat3(const std::string& name, const glm::vec3& value);
        void UploadUniformFloat4(const std::string& name, const glm::vec4& value);
        void UploadUniformMat3(const std::string& name, const glm::mat3 value);
        void UploadUniformMat4(const std::string& name, const glm::mat4 value);
    private:
        uint32_t m_RendererID;
    };
}

Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.cpp

//
// Created by xiatian05 on 2023/10/10.
//
#include "OpenGLShader.h"
#include "glad/glad.h"
#include "Log.h"
#include "Base.h"
#include "glm/gtc/type_ptr.hpp"
namespace Hazel{
    OpenGLShader::OpenGLShader(const std::string &vertexSrc, const std::string &fragmentSrc) {
        // Create an empty vertex shader handle
        GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
        // Send the vertex shader source code to GL
        // Note that std::string's .c_str is NULL character terminated.
        //reference to https://www.khronos.org/opengl/wiki/Shader_Compilation#Example
        const GLchar* source = vertexSrc.c_str();
        glShaderSource(vertexShader, 1, &source, 0);
        // Compile the vertex shader
        glCompileShader(vertexShader);
        GLint isCompiled = 0;
        glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &isCompiled);
        if (isCompiled == GL_FALSE)
        {
            GLint maxLength = 0;
            glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength);
            // The maxLength includes the NULL character
            std::vector<GLchar> infoLog(maxLength);
            glGetShaderInfoLog(vertexShader, maxLength, &maxLength, &infoLog[0]);
            // We don't need the shader anymore.
            glDeleteShader(vertexShader);
            HZ_CORE_ERROR("{0}", infoLog.data());
            HZ_CORE_ASSERT(false, std::string("Vertex shader compilation failure! ") + "\n" + "vertex :\n" + vertexSrc)
            return;
        }
        // Create an empty fragment shader handle
        GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        // Send the fragment shader source code to GL
        // Note that std::string's .c_str is NULL character terminated.
        source = fragmentSrc.c_str();
        glShaderSource(fragmentShader, 1, &source, 0);
        // Compile the fragment shader
        glCompileShader(fragmentShader);
        glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &isCompiled);
        if (isCompiled == GL_FALSE)
        {
            GLint maxLength = 0;
            glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength);
            // The maxLength includes the NULL character
            std::vector<GLchar> infoLog(maxLength);
            glGetShaderInfoLog(fragmentShader, maxLength, &maxLength, &infoLog[0]);
            // We don't need the shader anymore.
            glDeleteShader(fragmentShader);
            // Either of them. Don't leak shaders.
            glDeleteShader(vertexShader);
            HZ_CORE_ERROR("{0}", infoLog.data());
            HZ_CORE_ASSERT(false, std::string("Fragment shader compilation failure!") +  "\n" + "fragment :\n" + fragmentSrc);
            return;
        }
        // Vertex and fragment shaders are successfully compiled.
        // Now time to link them together into a program.
        // Get a program object.
        m_RendererID = glCreateProgram();
        GLuint program = m_RendererID;
        // Attach our shaders to our program
        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        // Link our program
        glLinkProgram(program);
        // Note the different functions here: glGetProgram* instead of glGetShader*.
        GLint isLinked = 0;
        glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);
        if (isLinked == GL_FALSE)
        {
            GLint maxLength = 0;
            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
            // The maxLength includes the NULL character
            std::vector<GLchar> infoLog(maxLength);
            glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);
            // We don't need the program anymore.
            glDeleteProgram(program);
            // Don't leak shaders either.
            glDeleteShader(vertexShader);
            glDeleteShader(fragmentShader);
            HZ_CORE_ERROR("{0}", infoLog.data());
            HZ_CORE_ASSERT(false, "Shader link failure!");
            return;
        }
        // Always detach shaders after a successful link.
        glDetachShader(program, vertexShader);
        glDetachShader(program, fragmentShader);
    }
    OpenGLShader::~OpenGLShader() {
        glDeleteProgram(m_RendererID);
    }
    void OpenGLShader::Bind() const {
        glUseProgram(m_RendererID);
    }
    void OpenGLShader::Unbind() const {
        glUseProgram(0);
    }
    void OpenGLShader::UploadUniformInt(const std::string &name, int value) {
        GLint location = glGetUniformLocation(m_RendererID, name.c_str());
        glUniform1i(location, value);
    }
    void OpenGLShader::UploadUniformFloat(const std::string &name, float value) {
        GLint location = glGetUniformLocation(m_RendererID, name.c_str());
        glUniform1f(location, value);
    }
    void OpenGLShader::UploadUniformFloat2(const std::string &name, const glm::vec2 &value) {
        GLint location = glGetUniformLocation(m_RendererID, name.c_str());
        glUniform2f(location, value.x, value.y);
    }
    void OpenGLShader::UploadUniformFloat3(const std::string &name, const glm::vec3 &value) {
        GLint location = glGetUniformLocation(m_RendererID, name.c_str());
        glUniform3f(location, value.x, value.y, value.z);
    }
    void OpenGLShader::UploadUniformFloat4(const std::string &name, const glm::vec4 &value) {
        GLint location = glGetUniformLocation(m_RendererID, name.c_str());
        glUniform4f(location, value.x, value.y, value.z, value.w);
    }
    void OpenGLShader::UploadUniformMat3(const std::string &name, const glm::mat3 value) {
        GLint location = glGetUniformLocation(m_RendererID, name.c_str());
        glUniformMatrix3fv(location, 1, GL_FALSE, glm::value_ptr(value));
    }
    void OpenGLShader::UploadUniformMat4(const std::string &name, const glm::mat4 value) {
        GLint location = glGetUniformLocation(m_RendererID, name.c_str());
        glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(value));
    }
}

更新SandBoxApp中调用Shader的逻辑
Sandbox/src/SandBoxApp.cpp

...
m_Shader.reset(Hazel::Shader::Create(vertexSrc, fragmentSrc));
...
m_BlueShader.reset(Hazel::Shader::Create(vertexSrc, fragmentSrc));
...

到这步,能运转起来了,和之前的作用相同。

imGui设置色彩到Shader

添加一个额定的功能,加一个imGui控件,设置矩形棋盘的色彩。
blueShader命名不精确,改成flatColorShader(单色shader)。对应的shader、vertexSrc、fragmentSrc都改成成flatColor最初的命名。

flatColorShaderFragmentSrc中添加uniform变量 vec3 v_Color,用来控制棋盘矩阵色彩

Sandbox/src/SandBoxApp.cpp

std::string flatColorShaderVertexSrc = R"(
    #version 330 core
    layout(location = 0) in vec3 a_Position;
    ...
  }
)";
std::string flatColorShaderFragmentSrc = R"(
    #version 330 core
    layout(location=0) out vec4 color;
    in vec3 v_Position;
    uniform vec3 u_Color;
    void main() {
        color = vec4(u_Color, 1.0);
    }
)";
private:
  std::shared_ptr<Hazel::Shader> m_Shader;
  std::shared_ptr<Hazel::VertexArray> m_VertexArray;
  std::shared_ptr<Hazel::Shader> m_FlatColorShader;
  std::shared_ptr<Hazel::VertexArray> m_SquareVA;
  ...

运转起来,棋盘是黑色的,此时u_Color为默认值(0, 0, 0)

游戏引擎从零开始(22)-Shader抽象

绑定u_Color

声明m_SquareColor,和shader中的u_Color对应

...
float m_CameraRotationSpeed = 180.0f;
glm::vec3 m_SquareColor = {0.2f, 0.3f, 0.8f};

OnUpdate中,每帧更新m_CameraRotationSpeed到Shader中

Hazel::Renderer::BeginScene(m_Camera);
static glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(0.1f));
std::dynamic_pointer_cast<Hazel::OpenGLShader>(m_FlatColorShader)->Bind();
std::dynamic_pointer_cast<Hazel::OpenGLShader>(m_FlatColorShader)->UploadUniformFloat3("u_Color", m_SquareColor);

ExampleLayer的OnImGuiRenderer()派上用场了,添加imGui色彩挑选器的控件

virtual void OnImGuiRender() override {
    ImGui::Begin("Settings");
    ImGui::ColorEdit3("Square Color", glm::value_ptr(m_SquareColor));
    ImGui::End();
}

运转没问题的话,呈现一个色彩挑选的面板,随意挑选色彩,会同步更新棋盘矩阵的色彩。

游戏引擎从零开始(22)-Shader抽象

完好代码&总结

本次代码修正参考:
github.com/summer-go/H…

文章会同步更新到掘进渠道,有问题欢迎留言交流:
/column/7156…