游戏引擎从零开始(13)-渲染Context抽象&第一个三角形

从这篇起,开始进入渲染模块(Renderer),Renderer是游戏引擎中最重要的一块,需要具备良好的数学、图形学、OpenGL基础

Context抽象

要设计更通用的游戏引擎,需要将图形API抽象出来,设计成上下文-Context。不同的图形驱动对应不同的Context子类。

如下图中,windows平台支持directx和OpenGl,这里以OpenGLContext为例,讲解Context抽象是如何实现的。

基类GraphicsContext

创建基类,将驱动Init和SwapBuffers两个方法抽象到基类GraphicsContext中。 Sandbox/Hazel/src/Hazel/Renderer/GraphicsContext.h

c++ 复制代码
#pragma once

namespace Hazel {
    class GraphicsContext {
    public:
        virtual void Init() = 0;
        virtual void SwapBuffers() = 0;
    };
}

OpenGLContext实现类

继承自GraphicsContext。 Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLContext.h

c++ 复制代码
#pragma once

#include "Renderer/GraphicsContext.h"

struct GLFWwindow;

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

        virtual void Init() override;
        virtual void SwapBuffers() override;

    private:
        GLFWwindow* m_WindowHandle;
    };
}

Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLContext.cpp

c++ 复制代码
#include "OpenGLContext.h"
#include "Core/Base.h"
#include <glad/glad.h>
#include <GLFW/glfw3.h>

namespace Hazel {
    OpenGLContext::OpenGLContext(GLFWwindow *windowHandle):m_WindowHandle(windowHandle) {
        HZ_CORE_ASSERT(windowHandle, "Window handle is null");
    }

    void OpenGLContext::Init() {
        glfwMakeContextCurrent(m_WindowHandle);
        int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
        HZ_CORE_ASSERT(status, "Failed to initialize Glad!");
    }

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

修改WindowsWindow

WindowsWindow中增加GraphicsContext,替换掉OpenGL的操作,这里只替换了gladLoadLoader、glfwSwapBuffers操作,后续会逐步将所有图形API抽象到基类中。

Sandbox/Hazel/src/Hazel/Platform/Windows/WindowsWindow.h

Sandbox/Hazel/src/Hazel/Platform/Windows/WindowsWindow.cpp

更新CMakeList.txt

css 复制代码
set(SRC_LIST
        ...
        src/Hazel/Renderer/GraphicsContext.h
        src/Hazel/Platform/OpenGL/OpenGLContext.cpp
        src/Hazel/Platform/OpenGL/OpenGLContext.h
)

OK,到此,Context抽象的逻辑暂告一段落。下面进入到绘制的阶段,是时展示时真正的技术了。

绘制第一个三角形

OpenGL API提供了一组查询GPU硬件、图形驱动等信息的方法。glGetString(...)

实例如下: Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLContext.cpp

c++ 复制代码
    void OpenGLContext::Init() {
        glfwMakeContextCurrent(m_WindowHandle);
        int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
        HZ_CORE_ASSERT(status, "Failed to initialize Glad!");

        HZ_CORE_INFO("OpenGL Info:");
        HZ_CORE_INFO(" Vendor: {0}", (char*)glGetString(GL_VENDOR));
        HZ_CORE_INFO(" Renderer: {0}", (char*)glGetString(GL_RENDERER));
        HZ_CORE_INFO(" Version: {0}", (char*)glGetString(GL_VERSION));
    }

命令行能看到日志:

csharp 复制代码
[00:38:19] HAZEL : OpenGL Info:
[00:38:19] HAZEL :  Vendor: ATI Technologies Inc.
[00:38:19] HAZEL :  Renderer: AMD Radeon Pro 5300M OpenGL Engine
[00:38:19] HAZEL :  Version: 4.1 ATI-4.14.1

绘制三角形

绘制一个三角行虽然很基础,但也囊括了OpenGL操作的大部分流程。这里不讲OpenGL和图形学的基础知识,建议零基础的朋友参考LearnOpenGL教程学习。

三角形处于窗口的正中间,顶点的坐标为:

  • 声明顶点数组、顶点缓冲、索引缓冲. Sandbox/Hazel/src/Hazel/Application.h
c++ 复制代码
private:
    ...
    unsigned int m_VertexArray, m_VertexBuffer, m_IndexBuffer;
    unsigned int shaderProgram;
  • 定义顶点数组、顶点缓冲、索引缓冲.

一个顶点数组可以关连多个缓冲,包括顶点缓冲、索引缓冲。

注意!顶点数组本身并没有数据,通过绑定的缓冲间接的关连数据缓冲,使用顶点数组方便在多组缓冲之间切换状态,更方便了,不用挨个缓冲切换。当然了,不使用顶点数组也可以使用缓冲。

Sandbox/Hazel/src/Hazel/Application.cpp

c++ 复制代码
Application::Application() {
    ...
    // 生成顶点数组
    glGenVertexArrays(1, &m_VertexArray);
    // 绑定顶点数组
    glBindVertexArray(m_VertexArray);
    
    // 生成顶点缓冲
    glGenBuffers(1, &m_VertexBuffer);
    // 绑定数组缓冲
    glBindBuffer(GL_ARRAY_BUFFER, m_VertexBuffer);

    // 定义三角形的三个点
    float vertices[3 * 3] = {
            -0.5f, -0.5, 0.0,
            0.5f, -0.5f, 0.0f,
            0.0f, 0.5f, 0.0f
    };
    
    // 填充数组数据。三角形数据绑定到GPU上
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    // enable第一个顶点数组,对应shader中的顶点数据
    glEnableVertexAttribArray(0);
    // 定义顶点属性为:从零开始,三组数据,浮点型,不进行归一化,每组数据为3*float(即步幅)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), nullptr);
    
    // 生成索引缓冲,本质都是缓冲和顶点缓冲没区别,存不同的数据而已
    glGenBuffers(1, &m_IndexBuffer);
    // 绑定到索引缓冲
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexBuffer);
    // 定义索引
    unsigned int indices[3] = {0, 1, 2};
    //将indices填充索引缓冲数据(发送到GPU)
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
  • 调GL的draw方法进行绘制 Sandbox/Hazel/src/Hazel/Application.cpp
c++ 复制代码
while(m_Running) {
    glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
    glClear(GL_COLOR_BUFFER_BIT);

    glBindVertexArray(m_VertexArray);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);

    for (Layer* layer : m_LayerStack) {
        layer->OnUpdate();
    }

OpenGL绘制三角形有两类方法:glDrawArrays、glDrawArrays

c++ 复制代码
glUseProgram(shaderProgram);
glBindVertexArray(VAO);

// drawArrays
glDrawArrays(GL_TRIANGLES, 0, 3);
// drawElements
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

到此Windows上应该就能出现三角形了,但是Mac上不行!

Mac上的OpenGL,还需要定义shader(可能Windows上的OpenGL有一个默认的shader)。

增加以下代码为了在Mac上显示三角形,其实完整的流程也应该有shader的。

Sandbox/Hazel/src/Hazel/Application.h

c++ 复制代码
private:
    // 声明shader id
    unsigned int shaderProgram;
};

增加一个简单的shader实现,如果你有OpenGL基础,很容易看懂这段代码. Sandbox/Hazel/src/Hazel/Application.cpp

c++ 复制代码
 Application::Application() {
 ...

    // build and compile our shader program
    // ------------------------------------
    // vertex shader

    const char *vertexShaderSource = "#version 330 core\n"
                                     "layout (location = 0) in vec3 aPos;\n"
                                     "void main()\n"
                                     "{\n"
                                     "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
                                     "}\0";
    const char *fragmentShaderSource = "#version 330 core\n"
                                       "out vec4 FragColor;\n"
                                       "void main()\n"
                                       "{\n"
                                       "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
                                       "}\n\0";

    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // fragment shader
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // link shaders
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}

绘制三角形之前,设置shader

c++ 复制代码
while(m_Running) {
      ...
      glUseProgram(shaderProgram);
      glBindVertexArray(m_VertexArray);
      glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);
}

实现效果&代码

如果顺利的话,能看到一个橙色的三角形

本节两段实现,完整代码参考:

  1. add GraphicContext

  2. draw a triangle

相关推荐
程序员老舅11 分钟前
C++ Qt项目教程:WebServer网络测试工具
c++·qt·测试工具·webserver·qt项目·qt项目实战
靡不有初11130 分钟前
CCF-CSP第18次认证第一题——报数【两个与string相关的函数的使用】
c++·学习·ccfcsp
cookies_s_s2 小时前
Linux--进程(进程虚拟地址空间、页表、进程控制、实现简易shell)
linux·运维·服务器·数据结构·c++·算法·哈希算法
不想编程小谭3 小时前
力扣LeetCode: 2506 统计相似字符串对的数目
c++·算法·leetcode
曼巴UE54 小时前
UE5.3 C++ TArray系列(一)
开发语言·c++·ue5
阿巴~阿巴~4 小时前
多源 BFS 算法详解:从原理到实现,高效解决多源最短路问题
开发语言·数据结构·c++·算法·宽度优先
CoderCodingNo5 小时前
【GESP】C++二级真题 luogu-b3924, [GESP202312 二级] 小杨的H字矩阵
java·c++·矩阵
刃神太酷啦6 小时前
堆和priority_queue
数据结构·c++·蓝桥杯c++组
Heris996 小时前
2.22 c++练习【operator运算符重载、封装消息队列、封装信号灯集】
开发语言·c++
----云烟----6 小时前
C/C++ 中 volatile 关键字详解
c语言·开发语言·c++