游戏引擎从零开始(14)-shader封装

1,052 阅读2分钟

前言

在Application中写大段的shader逻辑显然不合理,而且每个shader都会走一遍编译、链接的逻辑,很直观的想到,将重复的逻辑抽象出来,写成shader类

Shader封装

提出vertex和fragmetn着色器,再加上Bind()、UnBind()方法,就组成了Shader的基本内容

Shader类

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

#pragma once
#include <string>
namespace Hazel {
    class Shader {
    public:
        Shader(const std::string& vertexSrc, const std::string& fragmentSrc);
        ~Shader();

        void Bind() const;
        void Unbind() const;
    private:
        uint32_t m_RendererID;
    };
}

Shader实现,即将之前裸露在Application中的shader逻辑抠出来 Sandbox/Hazel/src/Hazel/Renderer/Shader.cpp


#include "Shader.h"
#include "glad/glad.h"
#include "Log.h"
#include "Base.h"

#include <vector>

namespace Hazel {
    Shader::Shader(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, "Vertex shader compilation failure!");
            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, "Fragment shader compilation failure!");
            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);
    }

    Shader::~Shader() {
        glDeleteProgram(m_RendererID);
    }

    void Shader::Bind() const {
        glUseProgram(m_RendererID);
    }

    void Shader::Unbind() const {
        glUseProgram(0);
    }
}

这段shader逻辑可以参考OpenGL官网opengl/wiki/Shader_Compilation

Application中调用shader

声明Shader Sandbox/Hazel/src/Hazel/Application.h

...
#include "Renderer/Shader.h"

namespace Hazel{
class Application {
public:
    ...
    std::unique_ptr<Shader> m_Shader;
    ...

Sandbox/Hazel/src/Hazel/Application.cpp

Application::Application() {
...
std::string vertexSrc = R"(
    #version 330 core
    layout(location = 0) in vec3 a_Position;
    out vec3 v_Position;
    void main()
    {
        v_Position = a_Position;
        gl_Position = vec4(a_Position, 1.0);
    }
)";

std::string fragmentSrc = R"(
    #version 330 core
    layout(location = 0) out vec4 color;
    in vec3 v_Position;
    void main()
    {
        color = vec4(v_Position * 0.5 + 0.5, 1.0);
    }
)";

m_Shader.reset(new Shader(vertexSrc, fragmentSrc));
}

void Application::PushLayer(Layer *layer) {
    ...
    glClear(GL_COLOR_BUFFER_BIT);

    m_Shader->Bind();
    glBindVertexArray(m_VertexArray);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);
}

注意,声明字符串vertexSrc和fragmentSrc用R""的方式,保留原始格式,避免每行加\n。

完整代码参考:shader Encapsulation