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

前言

本节我们对VertexArray进行抽象封装,目标是将Application中对OpenGL的直接调用都剥离到Renderer层。

VertexArray抽象

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

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

重构的示意图如下:

接口类VertexArray

和VertexBuffer类似,接口在Renderer层声明,实现在Platform/OpenGL中,通过多态的方式实现不同平台的切换。

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

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

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

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

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

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

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

c++ 复制代码
#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();
            }

运行结果

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

完整代码 & 总结

到此,Renderer层渲染的API基本封装完了,整体流程逐渐靠近商业引擎的使用习惯。

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

本次代码修改参考:vertex abstract

相关推荐
Ysjt | 深1 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word2 小时前
c++基础语法
开发语言·c++·算法
一只小小汤圆2 小时前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz3 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE3 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy4 小时前
c++ 笔记
开发语言·c++
fengbizhe4 小时前
笔试-笔记2
c++·笔记
徐霞客3204 小时前
Qt入门1——认识Qt的几个常用头文件和常用函数
开发语言·c++·笔记·qt