C++ OpenGL 3D Game Tutorial 2: Making OpenGL 3D Engine学习笔记
视频地址https://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 oncetypedef float f32;
六、后记
说实话,Window编程真的很绕,很晦涩(其实本来想说很**,但我要保持优雅的底线),也许这就是垄断的结果吧。其实以前开发苹果App的时候,感觉苹果对开发者就很不友好(对玩家用户倒是友好得很!),禁不住回想在Unity环境下用C#编程,似乎真的很美好(Long Live Unity!)。不过话说回来,开发游戏引擎的通常比使用引擎做游戏的工资更高,算是对被这种代码折磨的补偿吧 :D
相关文章:

C++ OpenGL 3D Game Tutorial 2: Making OpenGL 3D Engine学习笔记
视频地址https://www.youtube.com/watch?vPH5kH8h82L8&listPLv8DnRaQOs5-MR-zbP1QUdq5FL0FWqVzg&index3 一、main类 接上一篇内容,main.cpp的内容增加了一些代码,显得严谨一些: #include<OGL3D/Game/OGame.h> #include<i…...

Redis小计(4)
目录 1.Set和Get操作 2.mset和mget 3.mset,mget,set后加参数的优点 4.incr,incrby,incrbyfloat 1.Set和Get操作 flushall:清除所有k-v键值对。(删库跑路小技巧) set k v[ex | px]:设置超时…...
【React】常用Hook函数的梳理和总结(第二篇)
1. 前言 本篇梳理和总结React中常用的Hook函数。 欢迎大家来到Hook的世界,真的贼好用~ 2. 常用Hook函数 Hook说明示例useState(initialState) 功能:为函数组件添加状态变量,输入可是基本数据类型或引用数据类型,也可以是不带参数…...

【JaveWeb教程】(15) SpringBootWeb之 响应 详细代码示例讲解
目录 SpringBootWeb请求响应2. 响应2.1 ResponseBody2.2 统一响应结果2.3 案例2.3.1 需求说明2.3.2 准备工作2.3.3 实现步骤2.3.4 代码实现2.3.5 测试2.3.6 问题分析 SpringBootWeb请求响应 2. 响应 前面我们学习过HTTL协议的交互方式:请求响应模式(有…...
「 PyMuPDF专栏 」PyMuPDF为PDF文件添加注释
文章目录 一、PyMuPDF的安装与基本使用1. 安装PyMuPDF库的方法2. 导入PyMuPDF库二、新建PDF文档1. 创建一个空白的PDF文档三、添加注释1. 导入库并打开PDF文件2. 选择要添加注释的页面3. 创建并添加注释3.1. 文本注释3.1.1. 完整代码3.1.2. 注释效果图3.2. 高亮注释3.2.1. 完整…...

5 - 视图|存储过程
视图|存储过程 视图视图基本使用使用视图视图进阶 存储过程创建存储过程存储过程进阶存储过程参数循环结构 视图 视图是虚拟存在的表 表头下的数据在真表里 表头下的数据存储在创建视图时 在select命令访问的真表里 优点: 安全数据独立简单 用户无需关…...
系统学习Python——警告信息的控制模块warnings:警告过滤器-[基础知识]
分类目录:《系统学习Python》总目录 警告过滤器控制着警告是否被忽略、显示或转为错误(触发异常)。 从概念上讲,警告过滤器维护着一个经过排序的过滤器类别列表;任何具体的警告都会依次与列表中的每种过滤器进行匹配&…...
vue中高德地图使用
1、安装 npm i amap/amap-jsapi-loader --save2、封装地图组件 <template><div id"map" ref"mapcontainer"></div> </template><script> import AMapLoader from "amap/amap-jsapi-loader"; export default {befo…...
算法训练营Day33
#Java #贪心 开源学习资料 Feeling and experiences: 单调递增的数字:力扣题目链接 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时,我们称这个整数是单调递增的。 给定一个整数 n ,返回 小于或等于 n 的最大数字&am…...
.net6解除文件上传限制。Multipart body length limit 16384 exceeded
在C#中上传文件时如果不修改默认文件的上传大小会提示Multipart body length limit 16384 exceeded这个错误提示表明你的请求中的Multipart body长度超过了16384字节的限制。这通常意味着你正在尝试发送一个太大的请求体,可能是因为包含了太多数据或者太大的文件。要…...

电子电器架构网络演化 —— 车载以太网TSN
电子电器架构网络演化 —— 车载以太网TSN 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消…...

智能门锁触控工作原理中应用的电容式触摸芯片
智能门锁的识别技术中,密码几乎成为标配功能。相比机械按键的触控方式,电容式触控方式可以在加上一层玻璃甚至金属一体成型之后与用户进行交互,由于进行了物理性隔离,使得外壳更具完整性,物理上安全性更佳。电容式触控…...
Spark 中 BroadCast 导致的内存溢出(SparkFatalException)
背景 本文基于 Spark 3.1.1 open-jdk-1.8.0.352目前在排查 Spark 任务的时候,遇到了一个很奇怪的问题,在此记录一下。 现象描述 一个 Spark Application, Driver端的内存为 5GB,一直以来都是能正常调度运行,突然有一天,报…...

深度学习经典算法详细模型图
很早绘制的一些模型图,当时放在CSDN的草稿里,今天发现了,把它分享出来吧,还能更清晰的帮助理解! 1.AlexNet(2012) 2. VGGNet(2014) 3. SqueezeNet(2016) 4. GoogleNet(2014)...

03、Kafka ------ CMAK(Kafka 图形界面管理工具) 下载、安装、启动
目录 CMAK(Kafka 图形界面管理工具)下载安装启动打开 cmak 图形界面 CMAK(Kafka 图形界面管理工具) Kafka本身并没有提供Web管理工具,而是推荐使用bin目录下各种工具命令来管理Kafka, 这些工具命令其实用起…...
复习python从入门到实践——函数function
复习python从入门到实践——函数function 函数是特别难的,大家一定要好好学、好好复习、反复巩固。函数没学好,会为后面造成很大困扰。 教科书中函数举例会稍微有点复杂。在此章复习中,我将整理出容易疏漏和混淆的知识点,并用最简…...

【Internal Server Error】pycharm解决关闭flask端口依然占用问题
Internal Server Error The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application. 起因: 我们在运行flask后,断开服务依然保持运行࿰…...
torch.nn.functional.interpolate与torchvision.transforms.Resize方法对张量图像Resize应用
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、非张量数据使用torch方法resize(transforms.Resize)二、张量数据使用torch方法resize(torch.nn.functional.interpolate) 前言 要使用 PyTorch 对张量进行…...

【Spring】Spring的事务管理
前言: package com.aqiuo.service.impl;import com.aqiuo.dao.AccountMapper; import com.aqiuo.pojo.Account; import com.aqiuo.service.AccountService; import org.springframework.jdbc.core.JdbcTemplate;import java.sql.Connection; import java.sql.SQLEx…...

配置cendos 安装docker 配置阿里云国内加速
由于我安装的cendos是镜像版。已经被配置好了。所以只需要更新相关配置信息即可。 输入 yum update自动更新所有配置 更新完成后输入 yum list docker-ce --showduplicates | sort -r 自动查询所有可用的docker版本 输入 yum install docker-ce docker-ce-cli container…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...