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

news2024/11/25 16:54:27

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

完整代码下载地址:基于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.coloradmin.cn/o/1040478.html

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

相关文章

Kubernetes组件和架构简介

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

Windows迁移文件的快速方法

文章目录 1.简单比较2.传输方法介绍&#xff1a;有线&#xff08;直连网络&#xff09;3.传输方法介绍&#xff1a;无线热点传输4. 共享文件夹的设置5.挂载共享文件夹 1.简单比较 方法传输速度有线传输接近900Mb无线热点传输接近500MbU盘传输基本上不超过100Mb 2.传输方法介绍…

小程序-uniapp:URL Link / 适用于在移动端 从短信、邮件、微信外网页 等场景打开小程序任意页面

一、背景介绍 小程序URL Scheme、URL Link是微信小程序后台生成的一种地址&#xff0c;适用于从短信、邮件、微信外网页 等场景打开小程序任意页面。所以&#xff0c;适用性极强。可与微信扫码携带参数跳转到小程序指定页面技术互补 若在微信外打开&#xff0c;用户可以在浏览…

【C++】C++ 类中的 this 指针用法 ③ ( 全局函数 与 成员函数 相互转化 | 有参构造函数设置默认参数值 | 返回匿名对象与返回引用 )

文章目录 一、全局函数 与 成员函数 相互转化1、成员函数转为全局函数 - 多了一个参数2、全局函数转为成员函数 - 通过 this 指针隐藏操作数 二、有参构造函数设置默认参数值三、返回匿名对象与返回引用四、完整代码示例 一、全局函数 与 成员函数 相互转化 1、成员函数转为全局…

2023-Chrome插件推荐

Chrome插件推荐 一键管理扩展 链接 https://chromewebstore.google.com/detail/lboblnfejcmcaplhnbkkfcienhlhpnni 介绍 一键开启、禁用Chrome插件。 Checker Plus for Gmail™ 链接 https://jasonsavard.com/zh-CN/Checker-Plus-for-Gmail https://chromewebstore.goo…

ElementUI之首页导航与左侧菜单

目录 一、Mock 1.1 什么是Mock.js 1.2 安装与配置 1.2.1 安装mock.js 1.2.2 引入mock.js 1.3 mock.js使用 1.3.1 定义测试数据文件 1.3.2 mock拦截Ajax请求 1.3.3 界面代码优化 二、总线 2.1 定义 2.2 类型分类 2.3 前期准备 2.4 配置组件与路由关系 2.4.1 配置…

计算机毕业设计 基于微信小程序的校园商铺系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

2023 “华为杯” 中国研究生数学建模竞赛(A题)深度剖析|数学建模完整代码+建模过程全解全析

华为杯数学建模A题 当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2021年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 让我们一起看看研赛的A题呀&#xff01; …

络安全开发和音视频开发哪个方向更有前景?

网络安全开发和音视频开发哪个方向更有前景&#xff1f; 随着互联网和移动互联网的飞速发展&#xff0c;音视频应用已经成为人们日常生活和工作中不可或缺的一部分。 从视频会议、在线教育、直播、短视频到游戏&#xff0c;音视频技术的应用场景越来越广泛&#xff0c;市场需求…

面试必杀技:Jmeter性能测试攻略大全(第二弹)

1. JMeter介绍与安装 JMeter介绍 JMeter是Apache组织开发的基于Java的压力测试工具。具有开源免费、框架灵活、多平台支持等优势。除了压力测试外&#xff0c;JMeter也可以应用的接口测试上。JMeter下载、安装及启动 下载&#xff1a; 访问JMeter官网&#xff1a;https://j…

机器学习——seaborn实用画图方法简介

0、seaborn简介: 前言:下面的总结只是介绍seaborn有哪些方法和属性,至于具体使用,通过下面给出的名称稍作查找即可。重点应该关注本文介绍的seaborn的使用方法seaborn与机器学习的关系: 知识图谱 0.1、了解即可的知识: seaborn:在matplotlib的基础上画一些更好看的图,在…

快速排序与代码

快速排序&#xff08;Quicksort&#xff09;是一种常用的排序算法&#xff0c;它基于分治的思想。 时间复杂度&#xff1a;O&#xff08;nlogn&#xff09; 空间复杂度&#xff1a;O&#xff08;logn&#xff09; 快速排序的基本思想如下&#xff1a; 选择一个元素作为基准&a…

Android Jetpack组件架构 :LiveData的使用和原理

Android Jetpack组件架构&#xff1a; LiveDate的使用和原理 导言 继Lifecycle组件之后我们接下来要介绍的就是LiveDate组件&#xff0c;所谓LiveDate字面意思上就是有声明的数据&#xff0c;当数据有改动时该组件可以感知到这个操作并将该事件通知到其观察者&#xff0c;这样…

STM32单片机入门学习(四)-蜂鸣器

蜂鸣器接线 低平蜂鸣器&#xff0c;低电平发声&#xff0c;高电平不发声&#xff0c; 三个排针&#xff0c;VCC接3.3v&#xff0c;GND接地&#xff0c;I/O接A0口&#xff0c;如图&#xff1a; 蜂鸣器代码&#xff1a;响一秒停半秒 #include "stm32f10x.h" #includ…

SQL server 创建存储过程

SQL Server如何创建存储过程 存储过程&#xff1a; 可以理解为完成特定功能的一组 SQL 语句集&#xff0c;存储在数据库中&#xff0c;经过第一次编译&#xff0c;之后的运行不需要再次编译&#xff0c;用户通过指定存储过程的名字并给出参数&#xff08;如果该存储过程带有参数…

spring源码解析——IOC之自定义标签解析

概述 之前我们已经介绍了spring中默认标签的解析&#xff0c;解析来我们将分析自定义标签的解析&#xff0c;我们先回顾下自定义标签解析所使用的方法&#xff0c;如下图所示&#xff1a; 我们看到自定义标签的解析是通过BeanDefinitionParserDelegate.parseCustomElement(ele…

Neo4j-双向关系

概述 这是GraphAware中关于双向关系的解释。 网址链接Modelling Data in Neo4j: Bidirectional Relationships | GraphAware 定向关系 Neo4j中的关系必须有一个语义化的类型和方向。 没有方向关系是模棱两可的&#xff0c;上面A队打败B队&#xff0c;如果没有方向&#xff0c…

PTE深度了解(一)

目录 PTE模板开始大审查吗&#xff1f;我的模板还能用吗&#xff1f; 使用模版&#xff0c;不会额外扣你分 类型一&#xff08;前20秒说模版&#xff09; 类型二&#xff08;老实巴交&#xff09; 类型三&#xff08;就是都说简单句&#xff09; 1.查重复 2.增加内容分识…

算法经济:数据驱动的新智能世界

随着计算机技术和信息科学的发展&#xff0c;以及云计算、大数据、区块链、人工智能等先进技术的融合&#xff0c;一场关于“数据”的革命正在全球范围内蓬勃展开。这种现象被称为“算法经济”&#xff0c;它是以数据为驱动、算法为核心的新的经济形态。 首先&#xff0c;我们需…

代码随想录day49:动态规划part10

121.买卖股票的最佳时机 贪心&#xff1a; class Solution { public:int maxProfit(vector<int>& prices) {int low INT_MAX;int result 0;for (int i 0; i < prices.size(); i) {low min(low, prices[i]); // 取最左最小价格result max(result, prices[i…