本期为GAMES104《现代游戏引擎:从入门到实践》视频公开课文字实录第6期。本课程由GAMES(图形学与混合现实研讨会)发起,游戏引擎技术专家王希携手游戏引擎一线开发者共同研发。
课程共计22个课时,将介绍现代游戏引擎所涉及的系统架构,技术点,引擎系统相关的知识。为配合学习实践,课程组在 GitHub 上开源了小引擎Piccolo,上线1个月即获得了2900+star, 累计下载量已超过20000+。
以下内容为公开课视频转文字版本,为阅读通顺,有删减
01「核心层」
数学库
上节课我们讲到了游戏引擎的5+1个层次,以小明同学想做一个动起来的角色为挑战,介绍了资源层、平台层的具体内容。(上节课程回顾:GAMES104实录 | 引擎架构分层(中))
小明同学要做一个游戏引擎,他一定会接触到真正的核心层,核心层里最容易引起大家关注的就是数学库。
其实在游戏引擎里面,数学用的并不是特别高深。(当然有些特殊的场合像物理用的数学是非常深的)。最简单的比如绘制,包括游戏逻辑,我个人认为大学的线性代数基本够用了。想知道矩阵、向量这些概念,比如说怎么去旋转一个坐标,对一个物体进行二维、三维的放缩,把自己关在自习教室里好好学一个周末,做几套演算题,基本上就能搞得清楚。游戏引擎所需要的数学库,基本的数学概念并不是特别复杂。
那为什么在游戏引擎里面,会单独写这些数学库呢?这里面牵扯到游戏的一个很独特的需求,就是在第一讲提到的:游戏引擎的一切都是为效率服务的,它是一个实时(real time)的应用。所有用户的输入和反馈必须是实时(real time)的,数学库需要对效率非常敏感。
有个很著名的案例,上古神人John Carmack在Quake III的代码中,他发现算一个数值的倒数平方根(1.0/sqrt(x))特别慢。大家想想,开个根号再做除法,如果转换成计算机指令是无数条ALU指令,而且是浮点型的,确实效率会非常慢。Carmack就写了下面的一段代码,这段代码不知道在座的同学有多少人能读懂。(实话实说,我也没有完全读懂)
你可以发现这里面还有一个magic number,他自己会说这是个hack,其实它并不是hack,首先求了个近似解,后面用牛顿迭代法快速地去逼近那个数值。在游戏引擎中,很多时候不追求你算的数值绝对正确,大致正确就可以了,这个Carmack实现的算法效率会远高于你直接调用系统数学库中的平方根再做除法。
当然Carmack那个时代是很古老的,但在现代计算机中,有个东西非常值得同学们注意——SIMD。在游戏引擎中,很多时候就是矩阵和向量的加减乘除。SIMD就是一条指令把4个数x4个数,4个数+4个数全部做完。
我们想到的,CPU设计厂商也想到了。所以他们提出了SIMD的概念,相当于一个ALU把4个运算全部做掉,这其实也是在引擎数学库中广泛使用的一个技术。
大家在读引擎代码的时候,看到一个注释叫SSE的时候,你要知道那一段代码实际上是做向量运算的,它直接扩展CPU的并行化向量运算能力,它的性能要比直接调浮点数的快好几倍,引擎中的数学库对效率的要求是非常高的。
数据结构
核心层不止于数据,核心层为上层所有的逻辑提供了基础服务,这里面有个很重要的东西——数据结构。
如果真的想做引擎开发,你一定要理解数据结构,那数据结构是什么呢?
其实比大家想象的简单,比如需要一个array存储一串数据,需要一个链表一个连一个,需要一个二叉树查找一些东西,这些都是一些非常基础的概念。包括在C++语言标准中,str中是有标准的容器实现的,那为什么在引擎的核心层要全部重新做一遍呢?
如果仔细研究,大家会发现C++提供的标准容器,当进行高频的增加/删除数据的时候,它会在内存中产生大量的空洞,而且它的内存使用不受控制。
给大家讲个小的细节:Vector。比如一个数组,在C++标准实现中,一开始分配了100个元素,假设出现了101个元素,它会怎么办呢?我记得上一版是直接很粗暴地把它的空间放大一倍,就是说它的空间是以倍数往上涨的,所以,当你的游戏跑起来,内存消耗多少是完全不知道的,而且这些内存会分散得非常开,游戏就会变得非常慢。所以在核心层需要做一套我们自己的数据结构,要让它几乎没有内存碎片,并且访问效率要非常高。
核心引擎最重要的一个功能就是内存管理。在第一讲跟大家讲过,一个游戏引擎的开发非常接近于一个操作系统,内存管理是操作系统最重要的一个工作,比如通过堆栈来管理操作系统,管理我们的内存资源。
在游戏引擎中,我们预先申请了一大块内存,这块内存将由我们自己管理,为什么呢?因为我们在追求最高的效率。
很多时候大家去买计算机的时候,会买那种CPU主频非常快、内存也很大的。但是同学们有没有注意过:同一个厂家的两块CPU,主频一样快,但是其中一块比另外一块贵很多?差别是在于它的cache紧贴着CPU的缓存,缓存非常非常贵,缓存越大,CPU从下面取数据的效率就越高。比如游戏跑分的时候,效率就会高很多,所以同学们以后买CPU的时候,不要只买主频高的,还要看它缓存有多大。内存不是越大越好,CPU也不是越快越好,缓存很多时候是核心的卡点。
在核心层做内存管理的时候会用一个pool先把所有的内存资源管起来,然后管各种各样的数据,比如向量、列表等数据资源的分配。
这些讲起来比较抽象,理解起来其实也比较简单。
上古大神图灵发明的图灵机的结构非常简单,它就是一个穿孔纸带,里面有代码纸带进来,拉来拉去,读写读写就可以跑出所有的逻辑。那你想想看,如果想图灵机效率高,我该怎么去处理/管理数据呢?
其实它的规则就三条。第一,把数据放在一起,这样纸带依次访问的时候,速度就会快一点。第二,去访问的时候,数据尽可能沿着纸带的顺序来,就是1234567这种,不要是1473256那种。第三,如果要申请写/抹掉数据,不要一个一个把它写/抹掉,而要一次一批的写/抹掉。
游戏引擎的内存管理,比如C++17有很多很高端的功能,但总结它的底层逻辑就以上三条:把数据放在一起、访问数据的时候尽可能顺序访问、读写的时候,尽可能批量读写。
如果有人去考高端的内存管理知识,上过GAMES104课程的同学用这三条金科玉律,基本上可以去混个初级架构师了。(小小凡尔赛一把 :-))
02「结语」
特别推荐同学们都学一点引擎的知识,学完这些东西的时候,比如面对一个剧情软件系统,你就不会慌张,你就知道它是怎么从底座往上一层层构建起来的。
核心层这一层,实际上是操作系统。如果把Core Layer这层东西学完,我觉得你已经非常强了,因为核心层的代码质量要求非常高,而且轻易不要去动它。
一般来讲,一个游戏引擎公司,可能有几十上百个开发人员,但是能写Core Layer的程序员不会很多,一般都是很资深的同学去写,因为一些核心的东西会放在这一层维护起来,要保证它的绝对安全性和效率。
下节课将结束游戏引擎分层结构的介绍,接下来会给大家介绍“如何构建一个游戏世界”。
敬请期待。
本文编辑:Piccolo 社区编委会 张嘉瑶
如对本节课有任何问题,欢迎加入我们的社群或给我们发送邮件:
piccolo-gameengine@boomingtech.com
关于我们
Piccolo游戏引擎社区
Piccolo社区是中国开源游戏引擎分享、学习的非营利性平台,由游戏引擎行业大佬、共创官、学习者共同建立。你可以在我们的社区里交流技术、互助问答、参加活动,你也可以参与Piccolo的共建,如撰写贡献代码、撰写技术文章、参与技术挑战等。
Piccolo游戏引擎
由中国游戏引擎社区Piccolo开源的一款Mini游戏引擎。采用世界-关卡-游戏对象-组件的简洁架构,便于理解游戏引擎架构思想,它不仅能有效的帮助开发者学习游戏引擎架构知识,也能帮助一线开发者实验引擎算法与第三方库、辅助个人项目快速启动。截止目前,Github点赞已突破3600+,累计下载量已超过20000+
Piccolo GitHub地址:https://github.com/BoomingTech/Piccolo/discussions
暂无评论内容