游戏引擎从零开始(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...

相关推荐
优梦创客1 天前
《黑神话悟空》开发框架与战斗系统解析
unity·游戏开发·黑神话悟空·战斗系统·解包
战术摸鱼大师3 天前
OpenGL(四) 纹理贴图
贴图·opengl
ansondroider5 天前
Android MediaPlayer + GLSurfaceView 播放视频
android·opengl·mediaplayer·glsurfaceview
charon87785 天前
虚幻引擎 | 实时语音转口型 Multilingual lipsync
人工智能·游戏·语音识别·游戏开发
玫瑰花店7 天前
OpengGL教程(三)---使用VAO和VBO方式绘制三角形
c++·ubuntu·计算机视觉·cmake·opengl
Thomas_YXQ8 天前
Unity3D 实现水体交互详解
开发语言·unity·编辑器·交互·unity3d·游戏开发
北冥没有鱼啊8 天前
ue5 伤害插件
游戏·ue5·ue4·游戏开发·虚幻
刘好念9 天前
[OpenGL]使用OpenGL绘制三角形
c++·opengl
爱看书的小沐12 天前
【小沐学OpenGL】Ubuntu环境下glew的安装和使用
linux·c++·ubuntu·centos·opengl·glfw·glut
如果可以00316 天前
Android OpenGLES开发:EGL环境搭建
android·opengl·egl