游戏引擎从零开始(5)-窗口&显示

前言

终于,到了可以看到界面的阶段了。

基于glad和GLFW来构建渲染上屏的功能。

我们熟悉的OpenGL只是一套图形标准,GL版本众多,函数的地址无法在编译时确定下来,需要运行时查询,寻址的方法和平台相关。

比如在Windows平台上申请一段buffer的操作如下,代码显的很冗余。每个GL的API调用都如此,会有大量的重复的逻辑。

c++ 复制代码
// 定义函数原型
typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*);
// 找到正确的函数并赋值给函数指针
GL_GENBUFFERS glGenBuffers  = (GL_GENBUFFERS)wglGetProcAddress("glGenBuffers");
// 现在函数可以被正常调用了
GLuint buffer;
glGenBuffers(1, &buffer);

这里引入glad库解决这个问题

引入glad

glad是一个开源库,解决OpenGL API查询的繁琐问题。

进入glad官网: glad.dav1d.de/

选择需要的版本,配置好

选择Generate a loader,点击generate,下载glad.zip,里面有头文件和cpp源码

将头文件目录copy到工程Platform下,glad.h放到Windows下

更新引擎CMake文件,Hazel/CMakeLists.txt:

bash 复制代码
# 添加glad的头文件和源码
set(SRC_LIST
        ...
        src/Hazel/glad.c
        )

# 添加源码
target_include_directories(${PROJECT_NAME} PUBLIC
        ...
        src/Hazel/Platform/include
        )

注意!target_include_directories方法里要设置成PUBLIC,如果设置成PRIVATE,则引擎中include的头文件无法传递给业务方,即Sanbox访问不到这里include的头文件。

OK!glad配置好了,我们接着配置GLFW

GLFW配置

OpenGL可以看成是一套基于GPU API的数学计算工具,但是并不能和屏幕设备打交道。

需要引入专门的处理窗口的工具-GLFW,类似的工具层还有SDL、Android平台的EGL、苹果平台的EAGL。

GLFW是开源的、多平台库,支持OpenGL、GLes、Vulkan开发,用简单的API就能创建windows、contexts和surfaces,还支持输入事件的处理。

有多种GLFW的配置方法,这里讲一种我使用的,相对简单、通用的。

  1. 下载GLFW

进入根目录执行命令,下载GLFW到vendor目录,你也可以进入glfw官网手动下载。这里采用submodule的方式组织第三方库,将三方库从引擎工程总剥离,工程更简洁清爽。

bash 复制代码
git submodule add https://github.com/TheCherno/glfw Sandbox/Hazel/vendor/GLFW

.gitmodules自动增加了一项GLFW配置

ini 复制代码
[submodule "Sandbox/Hazel/vendor/GLFW"]
	path = Sandbox/Hazel/vendor/GLFW
	url = https://github.com/TheCherno/glfw
  1. CMake中更新GLFW配置

我们下载的是源码,需要参与编译

bash 复制代码
# 编译子项目 GLFW
add_subdirectory(vendor/GLFW)

# 链接glfw库
target_link_libraries(${PROJECT_NAME}  glfw)

# 增加glfw include地址
target_include_directories(${PROJECT_NAME} PUBLIC
        ...
        vendor/GLFW/include
        )

OK!窗口相关的环境配置好了,进入正式的编码环节了

Window抽象与实现

设计一个基类Window,抽象出窗口的基本属性和接口,再派生出Windows平台的窗口类WindowsWindow,当然还可能有其他的平台,我们暂不实现。

  • WindowProps:描述窗口宽高、title等属性
  • OnUpdate():每次tick的时候更新
  • GetWidth()、GetHeight():获取宽高
  • SetEventCallback():设置事件处理的回调,需要设置进GLFW
  • SetVsync():设置强制同步
  • IsVSync():查询是否强制同步

声明一个静态的创建Window的函数:

static Window* Create();

由各个具体的平台去实现,在Application层调用。

完整代码如下: Haze./src/Hazel/Core/Window.h

c++ 复制代码
#ifndef SANBOX_WINDOW_H
#define SANBOX_WINDOW_H

#include "../Core.h"
#include "Event.h"

namespace Hazel {
    struct WindowProps {
        std::string Title;
        unsigned int Width;
        unsigned int Height;
        
        // 默认参数需要手动转成(std::string&)类型
        WindowProps(const std::string& title = (std::string&)("Hazel Engine"),
                    unsigned int width = 1280,
                    unsigned int height = 720)
                    : Title(title), Width(width), Height(height) {
        }

    };

    class Window {
    public:
        using EventCallbackFn = std::function<void(Event&)>;
        virtual ~Window() = default;
        virtual void OnUpdate() = 0;
        virtual unsigned int GetWidth() const = 0;
        virtual unsigned int GetHeight() const = 0;

        // Window attributes
        virtual void SetEventCallback(const EventCallbackFn& callback) = 0;
        virtual void SetVSync(bool enabled) = 0;
        virtual bool IsVSync() const = 0;

        static Window* Create(const WindowProps& props = WindowProps());
    };

}

#endif //SANBOX_WINDOW_H

Windows下的Window实现

这里基于OpenGL实现的,其实也是跨平台的,如果后面切换成directX,则就真的是Windows平台独有的。

在Hazel/src/Hazel/Platform/Windows目录下新建WindowsWindow.h WindowsWindow.cpp文件

这两个文件可以参考我提交的代码WindowsWindow.h、WindowsWindow.cpp

里面实现了Windows中定义的虚函数。不做过多解释,需要注意的几点:

  1. WindowsWindow类封装了GLFWwindow,这是真实的窗口
  2. glad会和GLFW冲突,需要将glad放到GLFW前面include
c++ 复制代码
#include "Window.h"
#include <glad/glad.h>
#include <GLFW/glfw3.h>
  1. glfw的使用不同的平台上有点区别,mac上需要额外加一行代码兼容
c++ 复制代码
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
  1. glad初始化要放到glfwMakeContextCurrent调用之后
c++ 复制代码
glfwMakeContextCurrent(m_Window);
glfwSetWindowUserPointer(m_Window, &m_Data);
  1. 垂直同步 SetVSync(true)调用了glfwSwapInterval(1),表示只有窗口缓冲中数据更新了才swap数据上屏,否则每次都会进行swap上屏操作。
c++ 复制代码
void WindowsWindow::SetVSync(bool enabled) {
    if (enabled) {
        glfwSwapInterval(1);
    } else {
        glfwSwapBuffers(0);
    }
    m_Data.VSync = enabled;
}

Application中增加窗口

在Application构造函数中创建窗口,在Run函数中更新窗口

别的地方不需要持有Window,我们用unique_ptr智能指针包装Window.

在Run的While循环中,调用m_Window->OnUpdate(),这里我们绘制一个空白的窗口,背景颜色设置为绿色

c++ 复制代码
#include "Application.h"
#include "ApplicationEvent.h"
#include "Log.h"
#include <glad/glad.h>

namespace Hazel{
    Application::Application() {
        m_Window = std::unique_ptr<Window>(Window::Create());
    }


    void Application::Run() {
        ...
        while(m_Running) {
            glClearColor(0, 1, 0, 1);
            glClear(GL_COLOR_BUFFER_BIT);
            m_Window->OnUpdate();
        }
    }

}

没问题的话,你应该能看到一个纯绿色的窗口

OK!总算迈进了一大步~~

这章节完整代码参考: 窗口抽象与实现

相关推荐
碧海蓝天202217 分钟前
_Array类,类似于Vector,其实就是_string
数据结构·c++·算法
Zach_yuan25 分钟前
C++重生之我是001
开发语言·c++
Antonio91533 分钟前
【高级数据结构】并查集
数据结构·c++·算法
Che3rry1 小时前
C/C++|关于多线程冲突
java·c语言·c++
郭源潮13 小时前
【C++二叉树】二叉树的前序遍历、中序遍历、后序遍历递归与非递归实现
开发语言·c++·算法
咩咩大主教3 小时前
LinuxC++的UDP服务器和客户端通信
linux·服务器·c语言·开发语言·c++·udp
zyx没烦恼3 小时前
C\C++内存管理详解
c语言·c++
Wangx_wang4 小时前
C++——多态
开发语言·c++
bcbobo21cn4 小时前
MFC获取网页的html文本
c++·mfc·chttpfile
一丝晨光4 小时前
语言的循环语句
java·c++·python·c#·c·fortran·algol