Dragon Engine:OpenGL渲染架构

2020-08-17  本文已影响0人  Dragon_boy

这一节具体介绍OpenGL的渲染架构实现:

渲染环境类:

namespace Dragon
{
    class OpenGLContext : public GraphicsContext
    {
    public:
        OpenGLContext(GLFWwindow* windowHandle);

        virtual void Init() override;
        virtual void SwapBuffers() override;
    private:
        GLFWwindow* m_WindowHandle;
    };
}

实现:

namespace Dragon
{
    OpenGLContext::OpenGLContext(GLFWwindow* windowHandle)
        :m_WindowHandle(windowHandle)
    {
        DG_CORE_ASSERT(windowHandle, "Window handle is null");
    }
    void OpenGLContext::Init()
    {
        glfwMakeContextCurrent(m_WindowHandle);
        int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
        DG_CORE_ASSERT(status, "Failed to initailize GLAD!");

        DG_CORE_INFO("OpenGL info:");
        DG_CORE_INFO("  Vendor: {0}", glGetString(GL_VENDOR));
        DG_CORE_INFO("  Renderer: {0}", glGetString(GL_RENDERER));
        DG_CORE_INFO("  Version: {0}", glGetString(GL_VERSION));

    }
    void OpenGLContext::SwapBuffers()
    {
        glfwSwapBuffers(m_WindowHandle);
    }
}

构造方法会检测窗口是否为空。Init函数调用GLFW的方法创建当前窗口的环境,接着加载glad,并打印OpenGL的版本相关信息。SwapBuffers是一个交换缓冲的方法。

OpenGL的RendererAPI类:

namespace Dragon
{
    class OpenGLRendererAPI : public RendererAPI
    {
    public:
        virtual void Init() override;

        virtual void SetClearColor(const glm::vec4& color) override;
        virtual void Clear() override;

        virtual void DrawIndexed(const std::shared_ptr<VertexArray>& vertexArray) override;
    };
}

方法的实现:

namespace Dragon
{
    void OpenGLRendererAPI::Init()
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    }

    void OpenGLRendererAPI::SetClearColor(const glm::vec4& color)
    {
        glClearColor(color.r, color.g, color.b, color.a);
    }
    void OpenGLRendererAPI::Clear()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }
    void OpenGLRendererAPI::DrawIndexed(const std::shared_ptr<VertexArray>& vertexArray)
    {
        glDrawElements(GL_TRIANGLES, vertexArray->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);
    }
}

各种方法是OpenGL的一些方法的封装。

OpenGL的缓冲类:

namespace Dragon
{
    class OpenGLVertexBuffer : public VertexBuffer
    {
    public:
        OpenGLVertexBuffer(const void* vertices, uint32_t size);
        virtual ~OpenGLVertexBuffer();

        virtual void Bind() const override;
        virtual void Unbind() const override;

        virtual const BufferLayout& GetLayout() const override { return m_Layout; }
        virtual void SetLayout(const BufferLayout& layout) override { m_Layout = layout; }
    private:
        uint32_t m_RendererID;
        BufferLayout m_Layout;
    };

    class OpenGLIndexBuffer : public IndexBuffer
    {
    public:
        OpenGLIndexBuffer(const void* indices, uint32_t count);
        virtual ~OpenGLIndexBuffer();

        virtual void Bind() const;
        virtual void Unbind() const;

        virtual uint32_t GetCount() const;
    private:
        uint32_t m_RendererID;
        uint32_t m_Count;
    };
}

这里定义了顶点缓冲和索引缓冲。

相关方法实现:

namespace Dragon
{
    ////////////////////////////////////////////////////////////////////////
    //VertexBuffer///////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////

    OpenGLVertexBuffer::OpenGLVertexBuffer(const void* vertices, uint32_t size)
    {
        glCreateBuffers(1, &m_RendererID);
        glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
        glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW);
    }

    OpenGLVertexBuffer::~OpenGLVertexBuffer()
    {
        glDeleteBuffers(1, &m_RendererID);
    }

    void OpenGLVertexBuffer::Bind() const
    {
        glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
    }

    void OpenGLVertexBuffer::Unbind() const
    {
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    ////////////////////////////////////////////////////////////////////////
    //IndexBuffer///////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////

    OpenGLIndexBuffer::OpenGLIndexBuffer(const void* indices, uint32_t count)
        :m_Count(count)
    {
        glCreateBuffers(1, &m_RendererID);
        glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
        glBufferData(GL_ARRAY_BUFFER, count * sizeof(uint32_t), indices, GL_STATIC_DRAW);
    }

    OpenGLIndexBuffer::~OpenGLIndexBuffer()
    {
        glDeleteBuffers(1, &m_RendererID);
    }
    void OpenGLIndexBuffer::Bind() const
    {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID);
    }
    void OpenGLIndexBuffer::Unbind() const
    {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }
    uint32_t OpenGLIndexBuffer::GetCount() const
    {
        return m_Count;
    }
}

两个重点都在于构造方法,这里使用较新OpenGL API的的glCreateBuffers函数创建缓冲对象,接着绑定缓冲对象到特定目标,接着传递缓冲数据(这里可以使用更新的glNamedBufferStorage方法)。析构函数删除缓冲对象,Bind和Unbind绑定和解绑。

接着是顶点数组对象类:

namespace Dragon
{
    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(const 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;
        //uint32_t m_VertexBufferIndex = 0;
        std::vector<std::shared_ptr<VertexBuffer>> m_VertexBuffers;
        std::shared_ptr<IndexBuffer> m_IndexBuffer;
    };
}

成员函数基本继承自顶点数组类。

相关方法实现:

namespace Dragon
{
    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;
        }
        DG_CORE_ASSERT(false, "Unknown Type");
        return 0;
    }

    OpenGLVertexArray::OpenGLVertexArray()
    {
        glCreateVertexArrays(1, &m_RendererID);
    }
    OpenGLVertexArray::~OpenGLVertexArray()
    {
        glDeleteVertexArrays(1, &m_RendererID);
    }
    void OpenGLVertexArray::Bind() const
    {
        glBindVertexArray(m_RendererID);
    }
    void OpenGLVertexArray::Unbind() const
    {
        glBindVertexArray(0);
    }

这里定义了一个静态方法,它的功能是将我们之前定义的ShaderDataType转换为OpenGL的数据类型。

void OpenGLVertexArray::AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer)
    {
        DG_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.Normalized ? GL_TRUE : GL_FALSE,
                layout.GetStride(),
                (const void*)(intptr_t)element.Offset);
            index++;
        }

        m_VertexBuffers.push_back(vertexBuffer);
    }
    void OpenGLVertexArray::SetIndexBuffer(const std::shared_ptr<IndexBuffer>& indexBuffer)
    {
        glBindVertexArray(m_RendererID);
        indexBuffer->Bind();

        m_IndexBuffer = indexBuffer;
    }

添加顶点缓冲的方法,逻辑是:先判断要添加的顶点缓冲对象是否有布局,接着绑定顶点数组对象,绑定顶点缓冲,接着遍历布局中的元素,设置所有的顶点属性,所有属性方法之前都介绍过,应该不用完整介绍相关逻辑。

设置索引缓冲的方法很简单,绑定顶点数组对象后,绑定索引缓冲后设置成员属性即可。

到此为止,一个基本的渲染架构就以完成,大家可以绘制一个简单的三角形来实验一下(不用考虑着色器,大部分GPU会设置一个默认的顶点着色器和片元着色器(白色))

在客户端,我们就可以这么使用。

比如,创建一个层对象,然后,在其构造函数中设置顶点的坐标,然后设置顶点缓冲和顶点数组:

float vertices = {...};  //三角形坐标

// 已在层类的中定义了顶点数组对象
m_VertexArray.reset(VertexArray::Create());

// 创建一个顶点缓冲对象
std::shared_ptr<VertexBuffer> vertexBuffer;

// 根据顶点数据创建布局
BufferLayout layout = {
    {ShaderDataType::Float3, "a_Position"}
    };

// 使用相应数据创建OpenGL顶点缓冲对象
vertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));
//设置布局
vertexBuffer->SetLayout(layout);
//添加顶点缓冲
m_VertexArray->AddVertexBuffer(vertexBuffer);

然后在层类的update()成员函数中:

//设置背景颜色
Dragon::RenderCommand::SetColor({...});
//清除缓冲
Dragon::RenderCommand::Clear();
//开始场景
Dragon::Renderer::BeginScene();
//提交渲染数据
Dragon::Renderer::Submit(...);
//结束场景
Dragon::Renderer::EndScene();

可以看到抽象极大的简化了命令的调用,这也是我们的目的。不出意外地话,可以看到一个白色三角形。

下一节介绍摄像机的实现。

项目github地址:https://github.com/Dragon-Baby/Dragon

上一篇下一篇

猜你喜欢

热点阅读