【游戏引擎入门到实践】一.游戏引擎的五层架构(5+1)

885 阅读8分钟

由底到上5层:平台层、核心层、资源层、功能层和工具层。

贯穿全部都会有各种成熟的第三方插件库

1.平台层

(1)现代游戏引擎中,非常重要的一层叫做RHI——Render Hardware Interface。

它重新定义了一层Graphics API把不同平台硬件的SDK差别封装起来。

(2)平台层很难写,很容易被大家忽略的一层,但它是引擎水平高下的一个很重要的分水岭;

2.核心层

1.数学

(1)大部分场合用数学不是很深(物理场合使用数学是非常深的)

最简单的比如绘制,包括游戏逻辑,大学的线性代数基本够用了。想知道矩阵、向量这些概念,比如说怎么去旋转一个坐标,对一个物体进行二维、三维的放缩,把自己关在自习教室里好好学一个周末,做几套演算题,基本上就能搞得清楚。游戏引擎所需要的数学库,基本的数学概念并不是特别复杂。

(2)数学库是为了解决计算效率问题

比如一个例子,算一个数值的倒数平方根(1.0/sqrt(x))特别慢,开个根号再做除法,转换成计算机指令是无数条ALU指令,而且是浮点型的,确实效率会非常慢;有个远古大佬写了一个hack代码,求了个近似解,后面用牛顿迭代法快速地去逼近那个数值。

在游戏引擎中,很多时候不追求你算的数值绝对正确,大致正确就可以了。算法效率会远高于你直接调用系统数学库中的平方根再做除法。

(2-1)补充,ALU~算术逻辑单元(arithmetic and logic unit) ,实现多组算术运算和逻辑运算的组合逻辑电路,包括:

整数算术运算(加、减,有时还包括乘和除,不过成本较高)

位逻辑运算(与、或、非、异或)

移位运算(将一个字向左或向右移位或浮动特定位,而无符号延伸),移位可被认为是乘以2或除以2。

(3)CPU厂商也优化提出了SIMD概念;SIMD就是一条指令把4个数x4个数,4个数+4个数全部做完。相当于一个ALU把4个运算全部做掉,效率很高;

(3-1)补充,SIMD 的全称是 Single Instruction Multiple Data,“单指令多数据”。顾名思义,一条指令处理多个数据。

(4)引擎源码可能会有SSE的内容,实际上是做向量运算;直接扩展CPU的并行化向量运算能力,它的性能要比直接调浮点数的快好几倍。

2.数据结构

(1)游戏引擎中的数据结构,追求最高效率的内存管理,往往会自己实现一遍

C++语言标准有各种数据结构的实现,比如str,但是当进行高频的增加/删除数据的时候,它会在内存中产生大量的空洞,而且它的内存使用不受控制。

所以在核心层需要做一套我们自己的数据结构,要让它几乎没有内存碎片,并且访问效率要非常高。

(2)缓存的重要

同一个厂家的两块CPU,主频一样快,但是其中一块比另外一块贵很多?差别是在于它的cache紧贴着CPU的缓存;缓存非常非常贵,缓存越大,CPU从下面取数据的效率就越高。

所以以后买CPU的时候,不要只买主频高的,还要看它缓存有多大。

内存不是越大越好,CPU也不是越快越好,缓存很多时候是核心的卡点。

(3)内存管理的三个核心底层逻辑

比如C++17有很多很高端的功能,但总结它的底层逻辑也就这三条:

把数据放在一起;访问数据的时候尽可能顺序访问;读写时尽可能批量读写。

3.资源层,

1.把资源数据resource变成资产assets

(1)各种各样的图片格式、音乐格式,模型格式,把各种各样的原始数据全部转换成引擎的高效数据,这个转换之后,就把它叫做Asset(资产)。就像word文件内容用txt保存,文件小很多,只需有用的数据;

2.GUID,游戏资产的身份识别号

(1)composed asset

相当于一个关系脚本,定义各种资源之间的关系。

(2)做GUID(Global Unique ID)全局唯一编号,实际上是游戏资产的身份识别号。

3.runtime asset manager 运行时资产管理器

引擎读取了很多资产,需要进行管理。

(1)handle系统。

handle系统简单解释就像邮箱一样,你可以搬来搬去,我也不知道你在不在,但是我始终有你邮箱的钥匙,我知道你是105号邮箱,邮箱也知道你是105号邮箱的主人,这样,邮箱的主人在不在,我只要问这个邮箱就知道了。

(2)管理资产的生命周期。

GC垃圾回收。比如闯关游戏。

延迟加载。

4.功能层

1.Tick,主要分为Tick Logic和Tick Render

(1)现实生活也可以看做是上帝在tick我们,tick的时长就是普朗克时间。任何一个物理过程可能不能小于普朗克时间。

普朗克时间的值 t=5.4×10−44s

补充:为什么普朗克时间是最短的时间了?它又是根据什么,怎么测量出来的? - yubr的回答 - 知乎 www.zhihu.com/question/40…

(1-1)关于普朗克还有,普朗克速度、普朗克长度、普朗克能量等。

(2)看游戏引擎源码,优先看这个tick函数

(3)先计算逻辑 tick logic,再进行渲染 tick render。

逻辑和渲染要区分开,不要耦合。

(4)一帧一帧地在变化,依靠人的视觉残留感,从而产生一个连续的世界。

2.多核时代的游戏引擎架构

1.最简单基础的多线程做法

把logic和render分到两个线程里面去,还有额外的线程,比如做加载等。

2.现代商业引擎比如unity、Unreal,进一步优化

把一些特别容易并行化的计算(比如物理、一些其他的animation等)单独地fork出来,分散到很多很多线程。

3.高级的Job系统

(1)把所有的任务变成一个原子的,我们叫做job。你有4个核、8个核、16个核,没关系,我就一直扔,把你每个核吃得满满的。

(2)实际更复杂,很多计算之间有依赖关系dependency,有先后关系。

做一个非常高效率的多核并行架构,难在dependency的管理上面,能够让它不出乱子。

4.做底层架构时,强烈推荐从多核开始去设计和思考整个底层代码。

5.工具层

(1)工具层实际上就是允许别人以Level Editor(地图关卡编辑器)为中心形成的一系列编辑器;

(2)游戏引擎需要单独做个材质编辑器,保证效果一致,给艺术家用的;

(3)除了开发各种编辑器之外,还有DCC数字内容创作——Digital Content Creation,通俗来说就是别人开发的资产生产工具,比如大名鼎鼎的3DMax、Maya、Photoshop、Fmod以及Houdini,很多大片效果都是Houdini做的;

(4)Asset Conditioning Pipeline 资产调节管线

上面的第三方工具生成的大量数字资产和我们自己编辑生成的数字资产通过一条管线全部变成游戏统一的asset;

名字听着高大上,简单讲就是各种各样的导出器和导入器,比如在3DMax、Maya里面,我们把资产导出去,然后再导入我们自己的Asset,最后进入到游戏引擎里面。

6.第三方插件库

(1)游戏引擎生态已经非常丰富了,基本上每一件事情都有人会专门做,比如说专门做物理的,像Havok, PhysX就做得非常好。

可以把这些第三方的代码也要集成到我们的游戏引擎的生态里面,这里面包括声音,Speedtree,Simplygon。

(2)第三方代码的集成

1)有的是通过SDK的形式直接集成,就是说引擎里面编译的时候,就要把第三方代码编译进去。

2)有的第三方插件,实际上是变成了一个独立的工具。它和引擎之间的数据交换,只是通过文件格式进行交换。比如说像Simplygon。

7.其他注意点

(1)各个层次之间的调用,一般只允许上面的层次调下面层次的功能,绝对不允许下面反向调上面一层的功能,这就是分层的一个核心的体系结构。

(2)原则 :越往底层的东西越不要去动它。

比如说平台层,这一层基本上代码拆开之后几年是不要改的。但是上面的功能层,代码可以改来改去。工具层几乎每天都在变,今天想到了要做个什么玩法,需要一个什么工具,明天就去做了。当需求不断来的时候,不需要从上到下去动它所有的系统,只需要动它外面的上层的系统,越往上,越灵活,越往下越稳定,这其实也是分层结构一个很大的好处。

做任何一个需求的时候,首先去想它属于哪一层,而不是着急地把算法写出来