C++ OpenGL 3D Game Tutorial 2: Making OpenGL 3D Engine学习笔记

视频地址icon-default.png?t=N7T8https://www.youtube.com/watch?v=PH5kH8h82L8&list=PLv8DnRaQOs5-MR-zbP1QUdq5FL0FWqVzg&index=3

一、main类

        接上一篇内容,main.cpp的内容增加了一些代码,显得严谨一些:

#include<OGL3D/Game/OGame.h>
#include<iostream>

int main()
{
	try {
		OGame game;
		game.Run();
	}
	catch (const std::exception& e)
	{
		std::cout << e.what() << std::endl;
		return 1;
	}

	return 0;
}

二、OGame类 

       然后是Game类的添加了很多内容:

        Game.h文件:

#pragma once
#include<memory>

class OGraphicsEngine;
class OWindow;
class OGame
{
public:
	OGame();
	~OGame();

	virtual void onCreate();
	virtual void onUpdate();
	virtual void onQuit();

	void Run();
protected:
	bool m_isRunning = true;
	std::unique_ptr<OGraphicsEngine> m_graphicsEngine;
	std::unique_ptr<OWindow> m_display;
};

        Game.cpp文件:

#include<OGL3D/Graphics/OGraphicsEngine.h>
#include<OGL3D/Window/OWindow.h>
#include<OGL3D/Game/OGame.h>
#include<Windows.h>

OGame::OGame()
{
	m_graphicsEngine = std::make_unique<OGraphicsEngine>();
	m_display = std::make_unique<OWindow>();

	m_display->makeCurrentContext();
}

OGame::~OGame()
{
}

void OGame::onCreate()
{
	m_graphicsEngine->clear(OVec4(1,0,0,1));

	m_display->present(false);
}

void OGame::onUpdate()
{
}

void OGame::onQuit()
{
}

void OGame::Run()
{
	onCreate();

	MSG msg;
	while (m_isRunning)
	{
		msg = {};
		if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
			{
				m_isRunning = false;
				continue;
			}
			else 
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}

		onUpdate();
	}

	onQuit();
}

       和上一篇相比,在构造函数里面创建了OGraphicEngine对象,同时执行了OWindow对象m_display的方法makeCurrentContext,这个方法也是OWindow类在本次视频里面后加的。需要注意的是,这两行代码的顺序不能错:

m_graphicsEngine = std::make_unique<OGraphicsEngine>();
m_display = std::make_unique<OWindow>();

         因为OWindow构造期间需要获取的HDC里面的内容,而这个HDC内容要在OGraphicsEngine构造期间设置,所以必定要先构造OGraphicsEngine对象,顺序颠倒了肯定不行。

        同时OGame类里面还添加onCreate、onUpdate、onQuit方法,注意一下这三个方法的调用时机就好了。

三、OWindow类

Owindow.h文件

#pragma once

#include<Windows.h>
class OWindow
{
public:
		OWindow();
		~OWindow();

		void makeCurrentContext();
		void present(bool vsync);
private:
	HWND m_handle = nullptr;
	HGLRC m_context = nullptr;
};

OWindow.cpp文件

#include<OGL3D/Window/OWindow.h>
#include<glad/glad.h>
#include<glad/glad_wgl.h>
#include <Windows.h>
#include<assert.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_DESTROY:
	{
		OWindow* window = (OWindow*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
		break;
	}
	case WM_CLOSE:
	{
		PostQuitMessage(0);
		break;
	}
	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
	return NULL;
}

OWindow::OWindow()
{
	WNDCLASSEX wc = {};
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.lpszClassName = L"OGL3DWindow";
	wc.lpfnWndProc = WndProc;

	auto classId = RegisterClassEx(&wc);
	assert(classId);

	RECT rc = { 0,0,1024,768 };
	AdjustWindowRect(&rc, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, false);

	m_handle = CreateWindowEx(NULL,
		MAKEINTATOM(classId),
		L"Parcode | OpenGL 3D Game",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		rc.right - rc.left, rc.bottom - rc.top,
		NULL, NULL, NULL, NULL);

	assert(m_handle);

	SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)this);

	ShowWindow(m_handle, SW_SHOW);
	UpdateWindow(m_handle);

	HDC hDC = GetDC(m_handle);

	int pixelFormatAttributes[] = {
		WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
		WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
		WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
		WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
		WGL_PIXEL_TYPE_ARB,WGL_TYPE_RGBA_ARB,
		WGL_COLOR_BITS_ARB,24,
		WGL_DEPTH_BITS_ARB,24,
		WGL_STENCIL_BITS_ARB,8,
		0
	};

	int iPixelFormat = 0;
	UINT numFormats = 0;
	wglChoosePixelFormatARB(hDC, pixelFormatAttributes, nullptr, 1, &iPixelFormat, &numFormats);
	assert(numFormats);

	PIXELFORMATDESCRIPTOR pixelFormatDesc = {};
	DescribePixelFormat(hDC, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pixelFormatDesc);
	SetPixelFormat(hDC, iPixelFormat, &pixelFormatDesc);

	int openAttributes[] = {
	WGL_CONTEXT_MAJOR_VERSION_ARB,4,
	WGL_CONTEXT_MINOR_VERSION_ARB,6,
	WGL_CONTEXT_PROFILE_MASK_ARB,WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
	0
	};

	m_context = wglCreateContextAttribsARB(hDC, 0, openAttributes);
	assert(m_context);
}

OWindow::~OWindow()
{
	wglDeleteContext(m_context);
	DestroyWindow(m_handle);
}

void OWindow::makeCurrentContext()
{
	wglMakeCurrent(GetDC(m_handle), m_context);
}

void OWindow::present(bool vsync)
{
	wglSwapIntervalEXT(vsync);
	wglSwapLayerBuffers(GetDC(m_handle), WGL_SWAP_MAIN_PLANE);
}

创建窗口

        这里需要解释一些创建窗口的问题,其实在上一篇文件里就应该解释,但是当时理解的也不到位,这里算是补充一下。 
        WNDCLASSEX创建一个名称为wc的结构体实例,这个实例相当于我们事先写一份要创建窗口的类型申请表,RegisterClass这个函数就是把wc这个申请表提交(注册)给Window系统,然后当需要创建窗口时,Window系统就会按这个申请表来创建。
        RegisterClass的形参是指向WNDCLASSEX结构体的指针,这个指针会加入到system atom table即SAT中,这样系统就可以通过查找这张表来找到用户自定义的窗口类,window预定义的窗口类指针也在SAT中。

        SAT实际上实现了一种用于查询的映射,ATOM(翻译过来叫“原子”)实际类型是short,即16整数。ATOM表(原子表)是一个系统定义的用于存放字符串和相应的标识符的表。程序把一个字符串放入ATOM表,获得一个相应的16位整数,这个整数就叫原子,可以用来访问该字符串。一个被放进原子表的字符串叫做原子名称。

        只有系统才可直接访问这张表,但在调用某些api函数时,如Registerclass,可以告知系统来存取这张表。当然,还有本地原子表和全局原子表,这些表应用程序是可以直接访问的。

        MAKEINTATOM 宏 (位于winbase.h)将指定的原子转换为对应的字符串地址,以便可以将其传递给接受原子或字符串的函数。调用CreateWindowEx就用到了这个宏。

获取绘制上下文 

        后面补充的代码是从

HDC hDC = GetDC(m_handle); 

这行代码开始,一直到

assert(m_context); 

这行代码结束,其核心的目的是获取绘制上下文,就是 m_context 。而获取上下文的代码时使用的代码是:

m_context = wglCreateContextAttribsARB(hDC, 0, openAttributes);

        总体来说对我是个晕头转向的过程,不过大概就是先要获取形参,第一个是hDC,也是唯一需要说明一下的,这个内容有点儿多,查了老半天也是很糊涂的,先能明白多少算多少。

        HDC——Handle of the Device Context,中文意思是“设备上下文句柄”,这个东西会在后面多次用到。HDC是一种包含有关某个设备(如显示器或打印机)的绘制属性信息的 Windows 数据结构。所有绘制调用都通过设备上下文对象进行,这些对象封装了用于绘制线条、形状和文本的 Windows API。

        虽然说GetDC函数很容易就获取了这个hDC,但是对其像素格式进行设置。就是下面的代码:

SetPixelFormat(hDC, iPixelFormat, &pixelFormatDesc);

        这个过程设计好几个函数,基本没看明白,估计照着写就行。

        就说一个wglChoosePixelFormatARB函数吧,它能够找到合适的像素格式。

        Windows下要通过程序设置多重采样,必须使用wglChoosePixelFormatARB这个函数。正确使用这个函数的关键,就是需要创建一个临时窗体,通过这个窗体,我们可以获取必须的基础像素格式,然后再使用wglChoosePixelFormatARB这个函数,得到可用的多重采样像素格式,最后,对渲染窗口设置这个像素格式即可。

四、OGraphicsEngine类

        新增加了OGraphicsEngine类,先贴出代码:

        OGraphicsEngine.h文件:

#pragma once
#include<OGL3D/Math/OVec4.h>

class OGraphicsEngine
{
public:
	OGraphicsEngine();
	~OGraphicsEngine();
public:
	void clear(const OVec4& color);
};

        OGraphicsEngine.cpp文件:

#include<OGL3D/Graphics/OGraphicsEngine.h>
#include<glad/glad.h>
#include<glad/glad_wgl.h>
#include<assert.h>
#include<stdexcept>

OGraphicsEngine::OGraphicsEngine()
{
	WNDCLASSEX wc = {};
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.lpszClassName = L"OGL3DDummyWindow";
	wc.lpfnWndProc = DefWindowProc;
	wc.style = CS_OWNDC;

	auto classId = RegisterClassEx(&wc);
	assert(classId);

	auto dummyWindow = CreateWindowEx(NULL, MAKEINTATOM(classId), L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);

	assert(dummyWindow);

	HDC dummyDC = GetDC(dummyWindow);

	PIXELFORMATDESCRIPTOR pixelFormatDesc = {};
	pixelFormatDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pixelFormatDesc.nVersion = 1;
	pixelFormatDesc.iPixelType = PFD_TYPE_RGBA;
	pixelFormatDesc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pixelFormatDesc.cColorBits = 32;
	pixelFormatDesc.cAlphaBits = 8;
	pixelFormatDesc.cDepthBits = 24;
	pixelFormatDesc.cStencilBits = 8;
	pixelFormatDesc.iLayerType = PFD_MAIN_PLANE;

	auto iPixelFormat = ChoosePixelFormat(dummyDC, &pixelFormatDesc);
	SetPixelFormat(dummyDC, iPixelFormat, &pixelFormatDesc);

	auto dummyContext = wglCreateContext(dummyDC);
	assert(dummyContext);

	wglMakeCurrent(dummyDC, dummyContext);

	if (!gladLoadWGL(dummyDC))throw std::runtime_error("OGraphic Engine Error:gladLoadWGL failed.");
	if (!gladLoadGL())throw std::runtime_error("OGraphic Engine Error:gladLoadGL failed.");

	wglMakeCurrent(dummyDC, 0);
	wglDeleteContext(dummyContext);
	ReleaseDC(dummyWindow, dummyDC);
	DestroyWindow(dummyWindow);
}

OGraphicsEngine::~OGraphicsEngine()
{
}

void OGraphicsEngine::clear(const OVec4& color)
{
	glClearColor(color.x,color.y,color.z,color.w);
	glClear(GL_COLOR_BUFFER_BIT);
}

        ​choosePixelFormat从HDC中选择最匹配的内容,返回一个索引(iPixelFormat,开头的i应该就是index)。

        SetPixelformat函数将指定设备上下文(HDC)的像素格式设置为索引(iPixelFormat)指定的格式。

        这一通操作意思就是你开始定义的PIXELFORMATDESCRIPTOR只是你自己一厢情愿的想法,设备(显示器或者打印机)有可能支持(这当然最好),也有可能不支持,一旦不支持,就给你找个最接近你要求的参数凑合用用。

        wglCreateContext(为什么是wgl开头呢?应该是来源于Wingdi.h文件的GL函数,wingdi应该就是Window Graphic Deviec Interface)函数创建一个呈现上下文,该上下文适用于在 hdc 引用的设备上绘图。 

        其返回值是HGLRC类型(the Handle of GL Rendering Context,GL渲染上下文句柄)

        wglMakeCurrent就是指示后续在hdc上的绘制工作都是基于dummyContext的。

        绕了一大圈都是为了这两句:

	if (!gladLoadWGL(dummyDC))throw std::runtime_error("OGraphic Engine Error:gladLoadWGL failed.");
	if (!gladLoadGL())throw std::runtime_error("OGraphic Engine Error:gladLoadGL failed.");

        先说一下,通过throw抛出异常会终止线程。

        这两句也都是对hDC状态进行设置。

五、补充

        补充两个文件,一个是OVec4.h文件,另一个是OPerequisites.h文件。

        OVec4.h文件:

#pragma once
#include<OGL3D/OPerequisites.h>

class OVec4
{
public:
	OVec4() {}
	OVec4(f32 x, f32 y, f32 z, f32 w) :x(x), y(y), z(z), w(w) {}
	~OVec4() {}

public:
	f32 x = 0, y = 0, z = 0, w = 0;
};

        OPerequisites.h文件:

#pragma once

typedef float f32;

六、后记

        说实话,Window编程真的很绕,很晦涩(其实本来想说很**,但我要保持优雅的底线),也许这就是垄断的结果吧。其实以前开发苹果App的时候,感觉苹果对开发者就很不友好(对玩家用户倒是友好得很!),禁不住回想在Unity环境下用C#编程,似乎真的很美好(Long Live Unity!)。不过话说回来,开发游戏引擎的通常比使用引擎做游戏的工资更高,算是对被这种代码折磨的补偿吧 :D       

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/303493.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

寒假前端第一次作业

1、用户注册&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>用户注册</title> …

C++学习笔记——string类和new函数

目录 string类 1.功能增强 1.1 子字符串提取 1.2 字符串拼接 1.3 大小写转换 1.4 字符串比较 2.性能优化 3.使用示例 下面是一个简单的使用示例&#xff0c;展示了如何使用改进后的String类&#xff1a; NEW函数 2.1NEW函数的基本用法 2.2NEW函数的注意事项 2.3避…

使用lwip的perf进行测速TCP不稳定的一些相关配置项

在使用lwIP的perf工具进行TCP性能测试时&#xff0c;TCP不稳定可能涉及以下配置问题&#xff1a; 缓冲区大小&#xff08;Buffer Size&#xff09;&#xff1a;lwIP中的TCP性能受到发送和接收缓冲区大小的影响。如果缓冲区过小&#xff0c;可能导致数据包丢失或延迟增加&#x…

《BackTrader量化交易图解》第8章:plot 绘制金融图

文章目录 8. plot 绘制金融图8.1 金融分析曲线8.2 多曲线金融指标8.3 Observers 观测子模块8.4 plot 绘图函数的常用参数8.5 买卖点符号和色彩风格8.6 vol 成交参数8.7 多图拼接模式8.8 绘制 HA 平均 K 线图 8. plot 绘制金融图 8.1 金融分析曲线 BackTrader内置的plot绘图函…

Hibernate实战之操作MySQL数据库(2024-1-8)

Hibernate实战之操作MySQL数据库 2024.1.8 前提环境&#xff08;JavaMySQLNavicatVS Code&#xff09;1、Hibernate简介1.1 了解HQL 2、MySQL数据库建表2.1 编写SQL脚本2.2 MySQL执行脚本 3、Java操作MySQL实例&#xff08;Hibernate&#xff09;3.1 准备依赖的第三方jar包3.2 …

密码学:一文读懂非对称加密算法 DH、RSA

文章目录 前言非对称加密算法的由来非对称加密算法的家谱1.基于因子分解难题2.基于离散对数难题 密钥交换算法-DH密钥交换算法-DH的通信模型初始化DH算法密钥对甲方构建DH算法本地密钥乙方构建DH算法本地密钥DH算法加密消息传递 典型非对称加密算法-RSARSA的通信模型RSA特有的的…

大数据StarRocks(六) :Catalog

StarRocks 自 2.3 版本起支持 Catalog&#xff08;数据目录&#xff09;功能&#xff0c;实现在一套系统内同时维护内、外部数据&#xff0c;方便您轻松访问并查询存储在各类外部源的数据。 1. 基本概念 内部数据&#xff1a;指保存在 StarRocks 中的数据。 外部数据&#xf…

用css给宽高不固定的矩形画对角线

.kong{width: 200rpx;height: 76rpx;background: linear-gradient(to bottom right, #E5E5E5 0%, rgba(0, 0, 0, 0.1) calc(50% - 1px),#175CFF 50%, rgba(0, 0, 0, 0.1) calc(50% 1px),rgba(0, 0, 0, 0.1) 100%);}参考&#xff1a; https://blog.csdn.net/weixin_38779534/a…

1.1map

unordered_map和map的使用几乎是一致的&#xff0c;只是头文件和定义不同 #include<iostream> #include<map>//使用map需要的头文件 #include<unordered_map>//使用unordered_map需要的头文件 #include<set>//使用set需要的头文件 #include<uno…

web前端(html)练习

第一题 1. 用户名为文本框&#xff0c;名称为 UserName&#xff0c;长度为 15&#xff0c;最大字符数为 20。 2. 密码为密码框&#xff0c;名称为 UserPass&#xff0c;长度为 15&#xff0c;最大字符数为 20。 3. 性别为两个单选按钮&#xff0c;名称为 sex&#xff0c;值分…

【linux】tcpdump 使用

tcpdump 是一个强大的网络分析工具&#xff0c;可以在 UNIX 和类 UNIX 系统上使用&#xff0c;用于捕获和分析网络流量。它允许用户截取和显示发送或接收过网络的 TCP/IP 和其他数据包。 一、安装 tcpdump 通常是默认安装在大多数 Linux 发行版中的。如果未安装&#xff0c;可…

使用lwip的perf进行测速TCP会有较多的duplicate ack的原因分析

在使用lwIP的perf工具进行TCP测速时&#xff0c;出现较多的重复确认&#xff08;duplicate ACK&#xff09;可能是由于以下原因导致的&#xff1a; 丢包或乱序&#xff1a;重复确认通常是由于网络中的数据包丢失或乱序到达引起的。当接收方收到一个乱序的数据包时&#xff0c;它…

imazing破解版百度云2.17.3(附激活许可证下载)

iMazing是一款强大的 iOS 设备管理软件&#xff0c;不管是 iPhone、iPad 或 iPod Touch 设备&#xff0c;只要将 iOS 设备连接到计算机&#xff0c;就可以处理不同类型的数据。 iPhone 和 iPad 备份 借助 iMazing 的独有 iOS 备份技术&#xff08;无线、隐私和自动&#xff09…

系列十三、查询数据库中某个库、表、索引等所占空间的大小

一、information_schema数据库 1.1、概述 information_schema数据库是MySQL出厂默认带的一个数据库&#xff0c;不管我们是在Linux中安装MySQL还是在Windows中安装MySQL&#xff0c;安装好后都会有一个数据库information_schema&#xff0c;这个库中存放了其他库的所有信息。 …

【UE Niagara学习笔记】02 - 制作燃烧的火焰

目录 效果 步骤 一、添加资产 二、制作材质 三、制作粒子 3.1 循环播放 3.2 粒子生成的数量 3.3 粒子的生命周期和初始大小 3.4 火焰高度 3.5 火焰范围 3.6 火焰颜色 效果 步骤 一、添加资产 1. 在虚幻商城中搜索“M5 VFX Vol2. Fire and Flames(Niagara)”…

网络协议与攻击模拟_01winshark工具简介

一、TCP/IP协议簇 网络接口层&#xff08;没有特定的协议&#xff09; 物理层&#xff1a;PPPOE宽带拨号&#xff08;应用场景&#xff1a;宽带拨号&#xff0c;运营商切网过来没有固定IP就需要拨号&#xff0c;家庭带宽一般都采用的是拨号方式&#xff09;数据链路层网络层…

kettle的基本介绍和使用

1、 kettle概述 1.1 什么是kettle Kettle是一款开源的ETL工具&#xff0c;纯java编写&#xff0c;可以在Window、Linux、Unix上运行&#xff0c;绿色无需安装&#xff0c;数据抽取高效稳定。 1.2 Kettle核心知识点 1.2.1 Kettle工程存储方式 以XML形式存储以资源库方式存储…

【React源码 - Fiber架构之Reconciler】

前言 React16架构可以分为三层也是最核心的三个功能分别是&#xff1a; Scheduler&#xff08;调度器&#xff09;—调度任务的优先级&#xff0c;高优任务优先进入Reconciler(16新增)Reconciler&#xff08;协调器&#xff09;—负责找出变化的组件Renderer&#xff08;渲染…

Android readelf 工具查找函数符号

ELF&#xff08;Executable and Linkable Format&#xff09;是一种执行文件和可链接文件的格式。它是一种通用的二进制文件格式&#xff0c;用于在各种操作系统中存储可执行程序、共享库和内核模块。 Android 开发当中的 so 库本质上就是一种特殊类型的 ELF 文件&#xff0c;…

crash分析ramdump

我们需要在PC机上运行crash,从源码来编译, 可以从Redhat社区下载源码。 解压后&#xff0c;进入目录后使用一下命令&#xff1a; make targetarm64 make install这样就安装好了crash了。 crash介绍 crash主要是用来离线分析linux内核内存转存文件&#xff0c;它整合了gdb…