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

前言

这章做一点小小的代码改进,将Shader类抽象成接口,实现类OpenGLShader放到Platform/OpenGL下,以保证Renderer层是跨平台的。

Shader抽象与实现

Shader抽象

Shader中的函数设计成纯虚函数,变成一个纯接口的抽象类。

Sandbox/Hazel/src/Hazel/Renderer/Shader.h

c++ 复制代码
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

c++ 复制代码
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

c++ 复制代码
#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

c++ 复制代码
//
// 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

c++ 复制代码
...
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

c++ 复制代码
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)

绑定u_Color

声明m_SquareColor,和shader中的u_Color对应

c++ 复制代码
...
float m_CameraRotationSpeed = 180.0f;
glm::vec3 m_SquareColor = {0.2f, 0.3f, 0.8f};

OnUpdate中,每帧更新m_CameraRotationSpeed到Shader中

c++ 复制代码
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颜色选择器的控件

c++ 复制代码
virtual void OnImGuiRender() override {
    ImGui::Begin("Settings");
    ImGui::ColorEdit3("Square Color", glm::value_ptr(m_SquareColor));
    ImGui::End();
}

运行没问题的话,出现一个颜色选择的面板,随意选择颜色,会同步更新棋盘矩阵的颜色。

完整代码&总结

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

文章会同步更新到掘进平台,有问题欢迎留言交流:
juejin.cn/column/7156...

相关推荐
whoispo15 小时前
Imgui处理glfw的鼠标键盘的方法
opengl·imgui·gflw·鼠标键盘事件
XZen4 天前
Shadertoy转传统技术实现程序化星球纹理
图形学
Thomas游戏开发4 天前
Unity3D状态管理器实现指南
前端框架·unity3d·游戏开发
阿杰在学习6 天前
基于OpenGL ES实现的Android人体热力图可视化库
android·前端·opengl
彼方卷不动了6 天前
【技术学习】在 Android 上用 Kotlin 实现支持多图层的 OpenGL 渲染管线
android·kotlin·opengl
米芝鱼6 天前
LearnOpenGL(九)自定义转换类
开发语言·c++·算法·游戏·图形渲染·shader·opengl
byxdaz6 天前
OpenGL绘制文本
opengl
非衣居士7 天前
Lua程序设计笔记
lua·游戏开发
龙湾7 天前
OpenGLshader开发实战学习笔记:第一章 初识游戏图形
opengl
stevenzqzq7 天前
openGl片段着色器的含义
opengl·着色器