游戏引擎从零开始(17)-VertexArray抽象

前言

本节咱们对VertexArray进行笼统封装,方针是将Application中对OpenGL的直接调用都剥离到Renderer层。

VertexArray笼统

原本Application中分别持有VertexArray、IndexBuffer、VertexBuffer,其中VertexArray经过id间接引用,无法避免使用了OpenGL的API。

本章节将VertexArray的逻辑笼统到VertexArray类中,并将VertexBuffer、IndexBuffer挪到VertexArray类中,如此,Application中就笼统的很干净了。

重构的示意图如下:

游戏引擎从零开始(17)-VertexArray抽象

接口类VertexArray

和VertexBuffer相似,接口在Renderer层声明,完成在Platform/OpenGL中,经过多态的方式完成不同平台的切换。

VertexArray能够绑定多个VertexBuffer(极点、颜色、法线等),用vector<std::shared_ptr>存储,IndexBuffer一般只有一个。从命名上看也很考究,VertexBuffer增加用AddVertexBuffer,IndexBuffer增加用SetIndexBuffer。

Sandbox/Hazel/src/Hazel/Renderer/VertexArray.h

#pragma once
#include <memory>
#include "Renderer/Buffer.h"
namespace Hazel {
    class VertexArray {
    public:
        virtual ~VertexArray(){}
        virtual void Bind() const = 0;
        virtual void Unbind() const = 0;
        virtual void AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer) = 0;
        virtual void SetIndexBuffer(std::shared_ptr<IndexBuffer>& indexBuffer) = 0;
        virtual const std::vector<std::shared_ptr<VertexBuffer>>& GetVertexBuffers() const = 0;
        virtual const std::shared_ptr<IndexBuffer>& GetIndexBuffer() const = 0;
        static VertexArray* Create();
    };
}

Create()放到.cpp中完成: Sandbox/Hazel/src/Hazel/Renderer/VertexArray.cpp


#include "VertexArray.h"
#include "Renderer.h"
#include "Platform/OpenGL/OpenGLVertexArray.h"
namespace Hazel{
    VertexArray* VertexArray::Create() {
        switch (Renderer::GetAPI()) {
            case RendererAPI::None: HZ_CORE_ASSERT(false, "RendererAPI::None is currently not suported!"); return nullptr;
            case RendererAPI::OpenGL: return new OpenGLVertexArray();
        }
        HZ_CORE_ASSERT(false, "Unknow RendererAPI!");
        return nullptr;
    }
}

完成类OpenGLVertexArray

VertexBuffer、IndexBuffer放到OpenGLVertexArray中。简略的Get办法放到头文件中,复杂的办法放到.cpp中完成

Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLVertexArray.h

#pragma once
#include <cstdint>
#include "Renderer/VertexArray.h"
namespace Hazel {
    class OpenGLVertexArray : public VertexArray{
    public:
        OpenGLVertexArray();
        virtual ~OpenGLVertexArray();
        virtual void Bind() const override;
        virtual void Unbind() const override;
        virtual void AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer) override;
        virtual void SetIndexBuffer(std::shared_ptr<IndexBuffer>& indexBuffer) override;
        virtual const std::vector<std::shared_ptr<VertexBuffer>>& GetVertexBuffers() const {
            return m_VertexBuffers;
        };
        virtual const std::shared_ptr<IndexBuffer>& GetIndexBuffer() const {
            return m_IndexBuffer;
        };
    private:
        uint32_t m_RendererID;
        std::vector<std::shared_ptr<VertexBuffer>> m_VertexBuffers;
        std::shared_ptr<IndexBuffer> m_IndexBuffer;
    };
}

将Appplication中和vertexArray相关的逻辑都下沉到OpenGLVertexArray.cpp中: Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLVertexArray.cpp

 #include "OpenGLVertexArray.h"
#include <glad/glad.h>
namespace Hazel{
    static GLenum ShaderDataTypeToOpenGLBaseType(ShaderDataType type) {
        switch (type) {
            case ShaderDataType::Float:         return GL_FLOAT;
            case ShaderDataType::Float2:        return GL_FLOAT;
            case ShaderDataType::Float3:        return GL_FLOAT;
            case ShaderDataType::Float4:        return GL_FLOAT;
            case ShaderDataType::Mat3:          return GL_FLOAT;
            case ShaderDataType::Mat4:          return GL_FLOAT;
            case ShaderDataType::Int:           return GL_INT;
            case ShaderDataType::Int2:          return GL_INT;
            case ShaderDataType::Int3:          return GL_INT;
            case ShaderDataType::Int4:          return GL_INT;
            case ShaderDataType::Bool:          return GL_BOOL;
        }
        HZ_CORE_ASSERT(false, "Unknow ShaderDataType!");
        return 0;
    }
    OpenGLVertexArray::OpenGLVertexArray() {
        glGenVertexArrays(1, &m_RendererID);
    }
    OpenGLVertexArray::~OpenGLVertexArray() {
        glDeleteVertexArrays(1, &m_RendererID);
    }
    void OpenGLVertexArray::Bind() const {
        glBindVertexArray(m_RendererID);
    }
    void OpenGLVertexArray::Unbind() const {
        glBindVertexArray(0);
    }
    void OpenGLVertexArray::AddVertexBuffer(const std::shared_ptr<VertexBuffer> &vertexBuffer) {
        HZ_CORE_ASSERT(vertexBuffer->GetLayout().GetElements().size(), "Vertex Buffer has no layout");
        glBindVertexArray(m_RendererID);
        vertexBuffer->Bind();
        uint32_t index = 0;
        const auto& layout = vertexBuffer->GetLayout();
        for (const auto& element : layout) {
            glEnableVertexAttribArray(index);
            glVertexAttribPointer(index,
                                  element.GetComponentCount(),
                                  ShaderDataTypeToOpenGLBaseType(element.Type),
                                  element.Normallized ? GL_TRUE : GL_FALSE,
                                  layout.GetStride(),
                                  (const void*)element.Offset);
            index++;
        }
        m_VertexBuffers.push_back(vertexBuffer);
    }
    void OpenGLVertexArray::SetIndexBuffer(std::shared_ptr<IndexBuffer> &indexBuffer) {
        glBindVertexArray(m_RendererID);
        indexBuffer->Bind();
        m_IndexBuffer = indexBuffer;
    }
} 

调整Application中的逻辑

m_Shader、m_VertexArray、m_BlueShader、m_BlueShader、m_SquareVA等字段都声明成std::shared_ptr,因为程序中有多处会使用到,声明为unique_ptr不合适: Sandbox/Hazel/src/Hazel/Application.h

 class Application {
    ...
    ImGuiLayer* m_ImGuiLayer;
    bool m_Running = true;
    LayerStack m_LayerStack;
    std::shared_ptr<Shader> m_Shader;
    std::shared_ptr<VertexArray> m_VertexArray;
    std::shared_ptr<Shader> m_BlueShader;
    std::shared_ptr<VertexArray> m_SquareVA;
private:
    static Application* s_Instance;
}; 

Application.cpp中替换了VertexArray的逻辑,全体清爽了许多,别的增加了一个矩阵的制作: Sandbox/Hazel/src/Hazel/Application.cpp

#include "ImGui/ImGuiLayer.h"
#include <glad/glad.h>
#include <iostream>
#include <memory>
namespace Hazel{
  Application::Application() {
        ...
        PushOverlay(m_ImGuiLayer);
        //-----------------三角形----------------------
        m_VertexArray.reset(VertexArray::Create());
        float vertices[3 * 7] = {
                -0.5f, -0.5, 0.0, 0.8f, 0.2f, 0.8f, 1.0f,
                0.5f, -0.5f, 0.0f, 0.2f, 0.3f, 0.8f, 1.0f,
                0.0f, 0.5f, 0.0f, 0.8f, 0.8f, 0.2f, 1.0f
        };
        std::shared_ptr<VertexBuffer> vertexBuffer(VertexBuffer::Create(vertices, sizeof(vertices)));
        BufferLayout layout = {
                {ShaderDataType::Float3, "a_Position"},
                {ShaderDataType::Float4, "a_Color"},
        };
        vertexBuffer->SetLayout(layout);
        m_VertexArray->AddVertexBuffer(vertexBuffer);
        uint32_t indices[3] = {0, 1, 2};
        std::shared_ptr<IndexBuffer> indexBuffer(IndexBuffer::Create(indices, sizeof(indices)/sizeof(uint32_t)));
        m_VertexArray->SetIndexBuffer(indexBuffer);
        // -----------------矩形--------------------
        m_SquareVA.reset(VertexArray::Create());
        // 注意矩形,极点按照逆时针排列
        float squareVertices[3*4] = {
                -0.75f, -0.75f, 0.0f,
                0.75f, -0.75f, 0.0f,
                0.75f, 0.75f, 0.0f,
                -0.75f, 0.75f, 0.0f
        };
        std::shared_ptr<VertexBuffer> squareVB(VertexBuffer::Create(squareVertices, sizeof(squareVertices)));
        squareVB->SetLayout({
                                    {ShaderDataType::Float3, "a_Position"}
                            });
        m_SquareVA->AddVertexBuffer(squareVB);
        uint32_t squareIndices[6] = {0,1,2,2,3,0};
        std::shared_ptr<IndexBuffer> squareIB;
        squareIB.reset(IndexBuffer::Create(squareIndices, sizeof(squareIndices)/sizeof(uint32_t)));
        m_SquareVA->SetIndexBuffer(squareIB);
        ...
        m_Shader = std::make_shared<Shader>(vertexSrc, fragmentSrc);
        std::string blueShaderVertexSrc = 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 blueShaderFragmentSrc = R"(
            #version 330 core
            layout(location=0) out vec4 color;
            in vec3 v_Position;
            void main() {
                color = vec4(0.2, 0.3, 0.8, 1.0);
            }
        )";
        m_BlueShader = std::make_shared<Shader>(blueShaderVertexSrc, blueShaderFragmentSrc);
    }
    void Application::PushLayer(Layer *layer) {
            ...
            m_BlueShader->Bind();
            m_SquareVA->Bind();
            glDrawElements(GL_TRIANGLES, m_SquareVA->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
            m_Shader->Bind();
            m_VertexArray->Bind();
            glDrawElements(GL_TRIANGLES, m_VertexArray->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
            for (Layer* layer : m_LayerStack) {
                layer->OnUpdate();
            }

运转成果

假如你的代码运转没有问题,能看到如下效果,在原来的三角形背后,多了一个蓝色的矩形:

游戏引擎从零开始(17)-VertexArray抽象

完整代码 & 总结

到此,Renderer层渲染的API基本封装完了,全体流程逐步接近商业引擎的使用习气。

代码中除了上面讲的修正,还增加了一些调试日志及代码优化。

本次代码修正参阅:vertex abstract