基于C++实现的3D野外赛车驾驶游戏源码+项目文档+汇报PPT

项目介绍:本项目实现了一个户外场景下的赛车游戏,可以通过键盘控制赛车的移动,视角为第二人称视角。场景中有汽车,建筑,道路,天空等物体,拥有光照和阴影的效果。通过粒子系统模拟尾气效果,以及在场景边界加入水波效果。在汽车运动过程中,通过文本在屏幕上显示汽车的速度等所需信息。

完整代码下载地址:基于C++实现的3D野外赛车驾驶游戏

具体的效果请看doc目录下的演示视频

开发环境以及使用到的第三方库

开发环境

操作系统:windows 10

IDE:       visual studio 2017

编译器:  msvc++

使用到的第三方库

imgui

glad

glfw

assimp

glm

实现功能列表(Basic与Bonus)

Basic:

1.视角:处于第二人称视角操控汽车的运动,从汽车的后上方向汽车前方观察,视角跟随汽车移动

2.光照:采用phong局部关照明模型实现自然光的照射效果。

3.纹理:采用纹理贴图,实现汽车外观以及场景中实体的仿真。

4.阴影:采用shadow mapping技术,实时渲染自然光的阴影效果。

5.模型导入: 导入汽车和野外场景的obj文件,使其加载于屏幕上。

Bonus:

1. 天空盒:场景的上方为天空盒,模拟自然天空的效果。

2. 文字:屏幕上会通过文本显示一些汽车相关的信息。

3. 粒子系统:通过粒子系统模拟汽车的尾气效果。

4.光照明的优化:通过改进phong局部光照模型,改善光照模型;使其更加真实。

5.流体模拟:在场景边界外模拟水波的效果。

6.抗锯齿:在渲染过程中使用多重采样的离屏渲染,减弱锯齿的效果。

功能点介绍

1. 游戏架构

游戏分为以下几个部分:

1). 游戏实体:包括汽车、地板、房子等各种物体。

2). 渲染器:用于渲染各种游戏实体。将游戏实体的指针以及着色器指针传入即可。

3). 游戏事件管理:用户按键事件等。

具体流程:在每一个游戏循环中,主线程将各种游戏实体传入到渲染器中,渲染器读取它们的状态,并渲染到画面,如果游戏事件触发,则改变当前实体的状态,在下一个游戏循环中渲染。

2. 模型加载

模型加载的部分基本和learnopengl网站上的一致。但是,直接使用上面的源码无法加载出没有纹理的模型(有的模型只有diffuse color,并没有使用纹理贴图),所以,我们要在加载的时候读取mtl文件中存储的颜色信息。

aiColor4D diffuseColor;

aiGetMaterialColor(material, AI_MATKEY_COLOR_DIFFUSE, &diffuseColor);

glm::vec3 dcolor = glm::vec3(diffuseColor.r, diffuseColor.g, diffuseColor.b);

这里我们利用assimp库,将mtl文件中的颜色信息读取到一个3维的向量中。

3. 光照和阴影

基本和作业一致。

4. 碰撞检测

AABB盒碰撞模型。在每个游戏实体中维护一个包围盒(在本项目中没有包含z轴),然后在每个游戏循环中判断,每个游戏实体的包围盒是否有重叠。如果有重叠则碰撞会发生。

bool BoundingBox::isCollided(const BoundingBox& src1, const BoundingBox& src2) {

  bool axis1 = false, axis2 = false;

  // axis x overlap

  if (src2.minx > src1.minx && src2.minx < src1.maxx) {

    axis1 = true;

  } else if (src2.maxx > src1.minx && src2.maxx < src1.maxx) {

    axis1 = true;

  }

  // axis y overlap

  if (src2.miny > src1.miny && src2.miny < src1.maxy) {

    axis2 = true;

  } else if (src2.maxy > src1.miny && src2.maxy < src1.maxy) {

    axis2 = true;

  }

  return (axis1 && axis2);

这里是碰撞发生的逻辑。

关键在于如何找到每个游戏实体的包围盒。其实比较简单。在物体加载的时候,我们会加载出很多个mesh,只要我们找出这些mesh里最大的xy和最小的xy即可。然后通过模型的位置坐标对其进行offset。

  1. 天空盒

天空盒其实就是一个覆盖场景四周的长方体,但它的各个面上贴有表示天空的纹理图片,即四周的4面纹理的边与顶面纹理的边相连,同时四面纹理前后相连. 在

实际的渲染中,将这个立方体始终罩在摄像机的周围,让摄像机始终处于这个立方体的中心位置.要实现这个的话OpenGL中这种纹理叫做立方体贴图(Cubemap)。为了从立方体贴图中采样,我们要采用3d纹理坐标而不是我们之前用的2d纹理坐标,所以首先加载图片的时候要设置成GL_TEXTURE_CUBE_MAP,而不是我们之前纹理中常用的2D纹理,最后,还要记得设置纹理的过滤和环绕方式,因为我们用的是纹理盒,所以除了平常使用的GL_TEXTURE_WRAP_S和GL_TEXTURE_WRAP_T之外,还需要设置GL_TEXTURE_WRAP_R的属性。

另外在设置shaderr中的view矩阵的时候,为了保持摄像机和天空盒的距离,达到不能靠近天空盒或者说是始终被天空盒包围的效果,我们要把view矩阵中平移的部分去掉,也就是第三列设置成0。

关于着色器部分的话,要对一个3D盒子使用纹理盒有一个巨大的好处就是不需要额外指定纹理坐标。只要盒子是被放置在世界原点上,盒子本身的坐标就可以作为纹理坐标使用,因为在3D世界中位置本身就是一个向量,表示一个方向,我们要的就是这个方向。所以在顶点着色器中我们直接把输入的坐标当做纹理坐标传出,在片段着色器中,我们可以直接用这个坐标对纹理盒进行采样,同时我们可以在片段着色器中设置一些雾的效果,根据坐标的高度来计算出雾的颜色和采样得到的纹理颜色的混合。

  1. 文字渲染

首先就是根据教程安装好那个freetype的库,能够用于加载字体并将他们渲染到位图以及提供多种字体相关的操作的软件开发库。在安装的时候还是最好通过自己的vs来进行编译一次,避免产生各种问题。

FreeType是一个能够用于加载字体并将他们渲染到位图以及提供多种字体相关的操作的软件开发库。它是一个非常受欢迎的跨平台字体库,它被用于Mac OS X、Java、PlayStation主机、Linux、Android等平台。FreeType的真正吸引力在于它能够加载TrueType字体。TrueType字体不是用像素或其他不可缩放的方式来定义的,它是通过数学公式(曲线的组合)来定义的。类似于矢量图像,这些光栅化后的字体图像可以根据需要的字体高度来生成。通过使用TrueType字体,你可以轻易渲染不同大小的字形而不造成任何质量损失。

FreeType所做的事就是加载TrueType字体并为每一个字形生成位图以及计算几个度量值(Metric)。我们可以提取出它生成的位图作为字形的纹理,并使用这些度量值定位字符的字形。

要加载一个字体,我们只需要初始化FreeType库,并且将这个字体加载为一个FreeType称之为面(Face)的东西。

使用FreeType加载的每个字形没有相同的大小(不像位图字体那样)。使用FreeType生成的位图的大小恰好能包含这个字符可见区域。例如生成用于表示’.’的位图的大小要比表示’X’的小得多。因此,FreeType同样也加载了一些度量值来指定每个字符的大小和位置。下面这张图展示了FreeType对每一个字符字形计算的所有度量值。

每一个字形都放在一个水平的基准线(Baseline)上(即上图中水平箭头指示的那条线)。一些字形恰好位于基准线上(如’X’),而另一些则会稍微越过基准线以下(如’g’或’p’)(译注:即这些带有下伸部的字母,可以见这里)。这些度量值精确定义了摆放字形所需的每个字形距离基准线的偏移量,每个字形的大小,以及需要预留多少空间来渲染下一个字形。下面这个表列出了我们需要的所有属性。

  1. 水波模拟

    要模拟一个比较真实的流动水面不是一件容易的事情,需要考虑如何让波浪看起来自然、如何让水面反射等问题。在OpenGL中,可以将水面绘制成一个连续的高度场。如果以(x, y, z)来描述水面(xz平面)上的一个点,则它的高度y应该有`y = H(x, z, t)`的关系(t表示时间)。这次实现参考了[Ocean simulation part one: using the discrete Fourier transform](https://www.keithlantz.net/2011/10/ocean-simulation-part-one-using-the-discrete-fourier-transform/) 一文,基于傅里叶变换模拟较为真实的水面。

总结来说,计算水面高度的方法为  

这堆公式涉及到的变量很多,我们需要控制这些变量来模拟出理想的水面效果。这些变量包括:

  1) 水面大小 Lx, Lz

  2) 顶点网格密度 N,M

  3) 风的方向 Dw 以及 速度 Vw

  4) 波浪幅度 A

  5) 满足正态分布的两个独立变量 Er 和 Ei

其中函数h的计算,实际上就是二位傅里叶变换的求和,由于水面的波浪是实时计算更新的,为了能有更好的效率,利用二维快速傅里叶变换FFT降低计算复杂度。

总体来说,水面模拟基本涉及到的都是数学计算。在计算机图形学中,通过由三维顶点坐标组成的集合以及顶点之间的连接关系集合,表示一个三角形集合。三角形之间拼接成了水面,顶点的上下波动模拟出水面的波动效果。最终实现效果如下:  

8.抗锯齿:

在渲染的时候,如果仔细观察一些边缘的位置,能够看到锯齿状的图案。这很明显不是我们想要在最终程序中所实现的效果。你能够清楚看见形成边缘的像素。这种现象被称之为走样(Aliasing)。有很多种抗锯齿(Anti-aliasing,也被称为反走样)的技术能够帮助我们缓解这种现象,从而产生更平滑的边缘。

我们使用多重采样的方式,多重采样所做的正是将单一的采样点变为多个采样点(这也是它名称的由来)。我们不再使用像素中心的单一采样点,取而代之的是以特定图案排列的4个子采样点(Subsample)。我们将用这些子采样点来决定像素的遮盖度。当然,这也意味着颜色缓冲的大小会随着子采样点的增加而增加。

MSAA真正的工作方式是,无论三角形遮盖了多少个子采样点,(每个图元中)每个像素只运行一次片段着色器。片段着色器所使用的顶点数据会插值到每个像素的中心,所得到的结果颜色会被储存在每个被遮盖住的子采样点中。当颜色缓冲的子样本被图元的所有颜色填满时,所有的这些颜色将会在每个像素内部平均化。

在openGL中要使用离屏渲染的方式进行多重采样,大体思路是为每个像素点产生多个缓冲区,最终渲染的时候将其平均。

最终对比:左图为未使用抗锯齿时的边缘,右图为使用抗锯齿的边缘

 

9.粒子系统:

基本达到的目标是能够跟随汽车移动,并且在汽车移动速度加快的时候能够与汽车拉开更大的距离。

事实上,一个微粒,从OpenGL的角度看就是一个总是面向摄像机方向且(通常)包含一个大部分区域是透明的纹理的小四边形.一个微粒本身主要就是一个精灵(sprite),当你把成千上万个这些微粒放在一起的时候,就可以创造出令人疯狂的效果。当处理这些微粒的时候,通常是由一个叫做粒子发射器或粒子生成器的东西完成的,从这个地方,持续不断的产生新的微粒并且旧的微粒随着时间逐渐消亡。

这里尾气粒子的基本的实现使用的粒子是相对于车很小的立方体,贴图使用于汽车尾气颜色相近的灰色贴图,其运动是离开车后向外扩散,并且有跟随效果。最后其存活时间存活时间是线性递减的,死亡后重新生成。

最终效果如下:

10.白天与黑夜的变换

后期想要做到一个白天与黑夜渐变的效果,首先是通过两个帧之间相减获得时间差,然后累计时间,得到一个虚拟的时间worldtime,然后通过这个abs((time - 12) / 12)来计算出factor,然后传给天空盒着色器,然后通过mix函数将计算出来的天空盒颜色与黑色通过这个factor进行混合,达到渐变成晚上的效果。另外为了达到更逼真的效果,还将这个factor传给渲染物体的着色器,将环境光ambient根据时间进行调节,使之看起来更加真实

11.车的运动和照相机跟随

为了让照相机跟随车的运动和转弯,首先要设定一个和车的相对高度relativeheight和相对方向relativedirection,然后相机的位置就是车的位置加上相对位置,相对位置的计算车的方向在相对方向的分量,以及相对高度得到

glm::vec3 relativePosition = glm::vec3(-relativeDirection * car.direction.x, relativeHeight, -relativeDirection * car.direction.z);

camera.position = car.position + relativePosition;

    然后为了使相机跟随旋转,还要调整相机的yaw角

camera.yaw -= car.angle - preAngle;

12.模型的摆放

为了达到更加简洁的效果,在model类中可以设置一个objnum变量,一个model类可以含有相同模型的多个不同实例,主要是存放多个model矩阵,然后通过render进行渲染的时候就可以根据objnum和多个model矩阵进行渲染,这样可以避免同一个模型的多次加载。

至于位置的摆放的话,影响的音素比较多,和模型本身制作时的大小,位置都有关,可以通过对model矩阵进行scale,translate,rotate等变换进行摆放

遇到的问题和解决方案

1.天空盒中遇到的问题就是设置天空盒大小的问题,如果设置的太小的话会阻挡住盒子里加载的模型,但是我找的一个地形的模型中自带了一个天空盒,但是这个天空盒却没有那个无法靠近的那个效果,所以如果一直往边界走会越过这个模型自带的天空盒,所以还是要我们自己来实现天空盒的效果的,所以要想办法把那个模型中自带的天空盒给去掉。

2.模型加载的时候遇到的问题就是我找了很多的模型,在加载的时候很多都或多或少有一些问题,像有些3d模型的格式assimp库是无法加载的,有些的纹理路径等又不对,所以有的时候还需要自己打开mtl文件来修改一下路径之类的。并且关于纹理部分的话,有些模型又是没有纹理的,是通过直接用颜色信息的,所以在shader中还要考虑清有纹理和没纹理的情况来考虑。并且关于纹理的那个bindTexture也要十分谨慎,否则在着色器中通过sampler获取纹理的颜色信息的时候就很容易出错。

3. 本来想做一个换车的功能,但是不知道哪里出了问题,在访问纹理的时候会出现冲突,调试了很久都无法解决

 完整代码下载地址:基于C++实现的3D野外赛车驾驶游戏

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot与Spring Security的跨域解决方案

目录 一、什么是跨域问题 二、Spring Boot和Spring Security介绍 三、如何解决Spring Boot与Spring Security的跨域问题 一、什么是跨域问题 跨域问题&#xff08;Cross-Origin Resource Sharing&#xff0c;简称CORS&#xff09;是指在Web开发中&#xff0c;浏览器出于安全…

vue+express、gitee pm2部署轻量服务器

一、代码配置 前后端接口都保持 127.0.0.1:3000 vue创建文件 pm2.config.cjs module.exports = {apps: [{name: xin-web, // 应用程序的名称script: npm, // 启动脚本args: run dev, // 启动脚本的参数cwd: /home/vue/xin_web, // Vite 项目的根目录interpreter: none, // 告…

[动物文学]走红年轻人化身“精神动物”,这届年轻人不想做人了

数据洞察流行趋势&#xff0c;敏锐把握流量风口。本期千瓜与您分享近期小红书八大热点内容&#xff0c;带您看热点、追热门、借热势&#xff0c;为您提供小红书营销布局风向标。 「动物文学」走红 年轻人化身“精神动物” 其实&#xff0c;这届年轻人“不想做人”很久了………

Kubernetes组件和架构简介

目录 一.概念简介 1.含义&#xff1a; 2.主要功能&#xff1a; 3.相关概念&#xff1a; 二.组件和架构介绍 1.master&#xff1a;集群的控制平面&#xff0c;管理集群 2.node&#xff1a;集群的数据平面&#xff0c;为容器提供工作环境 3.kubernetes简单架构图解 一.概…

使用vue-cli脚手架工具搭建vue工程项目以及配置路由

vue-cli是用node编写的命令行工具&#xff0c;我们需要进行全局安装。打开命令行终端&#xff0c;输入如下命令&#xff1a; 1 $ npm install -g vue-cli 这里使用的是npm&#xff0c;为了开发的便利&#xff0c;推荐安装cnpm&#xff0c;这样运行指令会更迅速&#xff0c;安…

描述符——设备描述符

文章目录 描述符定义描述符实现描述符含义 描述符定义 描述符实现 /*** brief Device descriptor.*/ typedef struct __attribute__ ((packed)) {uint8_t bLength ; /**< Size of this descriptor in bytes. */uint8_t bDescriptorType ; /**< DEVICE D…

基于springboot+vue的大学生创新创业系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

Vue组件库Element

目录 Vue组件库ElementElement简介Element快速入门环境配置Element常用组件Table表格Table表格演示Table表格属性详解 Pagination分页Pagination分页演示Pagination分页属性详解Pagination分页事件详解 Dialog对话框Dialog对话框组件演示Dialog对话框属性详解 Form表单Form表单…

Chatbot UI集成LocalAI实现自托管的ChatGPT

本文比惯例提前了一天发&#xff0c;因为明天一早&#xff0c;老苏就踏上回乡的路了&#xff0c;三年没回老家&#xff0c;这次专门请了 2 天的假 难得回家&#xff0c;打算多陪陪家人&#xff0c;和多年不见的朋友聚聚&#xff0c;当然如果有网络条件&#xff0c;还是会正常发…

基于STM32的蔬菜大棚温湿度智能控制系统设计

一、前言 随着人们对健康和可持续生活方式的关注不断增加&#xff0c;蔬菜大棚成为了现代农业中的重要组成部分。蔬菜大棚提供了一个受控的环境&#xff0c;使得农民能够在任何季节种植蔬菜&#xff0c;并根据需要进行调节。为了实现最佳的蔬菜生长和产量&#xff0c;对温度和…

RedisTemplate出现\xac\xed\x00\x05t\x00\x0f前缀解决

问题描叙 出现这种乱码前缀的原因是没有进行序列化&#xff0c;因此导致在传输过程出现乱码问题&#xff0c;存到数据库&#xff0c;发现 key,hash key/value 都有 \xAC\xED\x00\x05t\x00 前缀。RedisTemplate类中默认是没有设置序列化的。 解决方法 设置RedisTemplate的序列…

微机原理与接口技术

一、微型计算机系统概述二、总线 总线(Bus)&#xff1a;是连接计算机各部件的一组公共信号线;总线上能同时传送二进制信息的位数称为总线宽度。 优点&#xff1a;大大减少传输线数目&#xff0c;结构简单&#xff0c;便于扩充 总线按传送信息分类:地址总线、数…

华为小型智能园区网络解决方案

云时代来袭&#xff0c;数字化正在从园区办公延伸到生产和运营的方方面面&#xff0c;智慧校园&#xff0c;柔性制造&#xff0c;掌上金融和电子政务等&#xff0c;面对各种各样的新兴业态的涌现&#xff0c;企业需要构建一张无所不联、随心体验、业务永续的全无线网络&#xf…

burpsuite只有intruder中文乱码

burpsuite 只有intruder模块中文乱码 现象&#xff1a;解决方案 现象&#xff1a; 在proxy、repeater等模块下中文均可正常显示&#xff0c;如下图&#xff1a; 在intruder模块&#xff0c;中文显示乱码 解决方案 在payloads标签下payload processing中添加“Decode”

web服务基础

前言&#xff1a;web服务怎么做&#xff0c;怎样使用&#xff0c;这是一个长期的任务 1、DNS解析原理 在windows 客户端查看本地缓存的DNS解析记录 C:\Users\86157>ipconfig /displaydns #其中这两个字符之间会有空格 在windows11 中的命令行运行结果如下 清除win客户端…

【区块链 | DID】白话数字身份

《十四五数字经济发展规划》提出建立健全政务数据共享协调机制&#xff0c;加快数字身份统一认证和电子证照、电子签章、电子公文等互信互任&#xff0c;推进发票电子化改革&#xff0c;促进政务数据共享、流程优化和业务协同。在数字经济逐渐成形的背景下&#xff0c;推进数字…

Elasticsearch 集群时的内部结构是怎样的?

Apache Lucene : Flush, Commit Elasticsearch 是一个基于 Apache Lucene 构建的搜索引擎。 它利用 Lucene 的倒排索引、查询处理和返回搜索结果等功能来执行搜索。 它还扩展了 Lucene 的功能&#xff0c;添加分布式处理功能以支持大型数据集的搜索。 让我们看一下 Apache Luc…

Learn Prompt- Midjourney Prompt:Prompt 提示语

基础结构​ 一个基本的提示可以简单到一个单词、短语或表情符号。非常短的提示将在很大程度上依赖于 Midjourney 的默认样式。 完整 prompt&#xff1a;可以包括一个或多个图像链接、多个文本短语或单词&#xff0c;以及一个或多个后缀参数 Image Prompts: 可以将图像 URL 添加…

比特币的蒙提霍尔问题

把钱放在嘴边 我们在比特币上建立了蒙提霍尔问题模拟。 如果您知道概率谜题的正确答案&#xff0c;不仅炫耀您的数学技能&#xff0c;还会获得金钱奖励。 它完全无需信任地在链上运行。 蒙提霍尔问题 蒙提霍尔问题&#xff08;三门问题&#xff09;是一个以蒙提霍尔命名的概率…

数据一致性:核心概念与实现策略

在当今的信息时代&#xff0c;数据已经成为了企业的核心资产之一。然而&#xff0c;随着数据量的不断增长和应用场景的不断扩大&#xff0c;如何保证数据的一致性成为了一个重要的挑战。数据一致性不仅关系到系统的正确性和可靠性&#xff0c;也直接影响到用户的体验和企业的业…