Games104 学习笔记 12~14

目录

课程作业

https://games104.boomingtech.com/sc/course-work/

第十二节 游戏引擎中的粒子和声效系统

Particle System

Fundamentals

所谓的粒子是指具有一些物理信息的物体,常见的物理量包括位置、速度、大小、颜色等。

同时粒子还需要考虑自身的生命周期(life cycle),当粒子的生命周期结束后需要被系统回收。

Particle Emitter

每一种不同的粒子都是由相应的粒子发射器(particle emitter)生成的。每一种粒子发射器需要指定自身的生成规则同时为粒子赋予相应的仿真逻辑。

在一个粒子系统中往往会同时具有多个不同的例子发射器进行工作,它们之间相互配合就实现了丰富的粒子效果。

Particle Spawn

粒子系统在生成粒子时可以根据需求使用不同的生成策略。比较简单的生成方式是从单点生成粒子,而在现代粒子生成器中则可以从某个区域甚至从物体的网格来生成粒子。同时粒子生成器也可以根据需求只产生一次性的粒子,或是源源不断地生成新的粒子。

Simulation

完成粒子的生成后就可以利用质点运动学的相关方法对粒子进行仿真。由于粒子系统一般不需要严格地遵守物理规律,在实践中往往只会使用最简单的前向积分来实现对粒子状态进行更新。

需要模拟重力或者碰撞

Particle Type

早期的粒子系统会假定粒子都位于某个平面上进行运动,这种方法称为 billboard particle。当观察者的视角发生变化时,billboard 会随着观察者的视角一起变化从而保证它一直位于观察者的正前方。

Mesh Particle

随着游戏技术的进步,后来还出现了 mesh particle 这种带有几何信息的粒子。这种形式的粒子可以用来模拟岩石、碎屑等带有明显几何信息的颗粒。

Ribbon Particle

在很多游戏中还使用了 ribbon particle 这种带状的粒子用来模拟各种拖动的效果,比如说游戏中各种武器的特效一般都是使用这种技术来制作的。在使用 ribbon particle 时一般还会结合 Catmull 曲线来形成光滑连贯的特效。

Particle System Rendering

Particle Sort

粒子在进行渲染时的一大难点在于如何进行排序。按照 alpha 混合的理论,我们需要从最远端开始由远及近地对场景中的物体进行绘制。

在这里插入图片描述
由于场景中往往同时存在巨量的粒子,对这些粒子进行排序往往会耗费大量的计算资源。目前对于粒子进行排序主要有两种做法:其一是全局排序即无考虑发射器的信息单纯对所有的粒子根据到摄像机的距离进行排序,这种做法可以获得正确的结果但需要非常多的计算资源;另一种做法是按照发射器的 bounding box 进行排序,这种方法可以极大地减少计算资源但可能会出现错误的排序结果。

Resolution

当粒子充满场景时非透明的粒子会导致我们必须在同一像素上进行反复的绘制,这往往会导致帧数极大的下降。

因此在对粒子进行渲染时还会结合降采样的技术来减少需要进行绘制的像素数。把降采样后的图像和非透明的粒子按照透明度混合后在通过上采样来恢复原始分辨率。

例如把屏幕长宽都缩小一半,Z-Buffer 也是长宽缩小一半,然后

在这里插入图片描述
先 down-sampling 然后再 up-sampling 是常见的技术

GPU Particle

显然粒子系统这种天然并行的系统非常适合使用 GPU 进行计算,在现代游戏中也确实是使用 GPU 来实现对粒子系统的仿真。稍后我们会看到使用 GPU 来对粒子系统进行计算不仅可以节约 CPU 的计算资源,更可以加速整个渲染的流程。

但是粒子是有生命周期的

在这里插入图片描述

Particle Lists

我们可以通过维护若干个列表来实现对粒子系统的仿真。首先我们把系统中所有可能的粒子及其携带的信息放入 particle pool 中,并且在 deal list 中初始化所有的粒子编号。

在这里插入图片描述

当 emitter 生成新的粒子时只需要将 dead list 中的粒子推入当前帧的 alive list 即可。

在这里插入图片描述
对粒子进行仿真时只需要考虑 alive list 中的粒子。

设置两个 alive list,在遍历 alive list 0 的同时,也在将遍历到的元素写入另一个 alive list 1

在遍历 alive list 0 的时候,如果某个粒子的生命周期结束了,则需要把该粒子编号重新放入 dead list 中,然后在写入下一帧的 alive list 时跳过该编号,也就是不写入 alive list 1。当需要切换到下一帧时只需交换两个 alive list 即可。

这里虽然他没说,但是我觉得应该是,两个 alive list 交换之后,alive list 1 就会被清空,或者是在遍历 alive list 0 之前会清空 alive list 1,不然的话,alive list 1 应该会残留历史记录

在这里插入图片描述

这些都可以在 compute shader 中写

由于所有的数据都在GPU上,我们还可以方便地对粒子进行frustum culling以及排序。

在这里插入图片描述
集群的管理都可以用这个方法,双 alive list 和一个 dead list 的形式

Parallel Mergesort

在GPU上进行排序时需要使用并行的排序算法,其中比较经典的算法是 parallel mergesort。

也就是并行版的归并排序

在合并两个有序数组时还可以使用从目标队列出发寻找源队列的方式进一步提升性能。

因为如果指针在两个源数组中来回跳跃的话,如果源数组比较长,就可能频繁发成缓存未命中,效率比较低

所以我要从目标数组找源数组

在这里插入图片描述

Depth Buffer Collision

除此之外还可以在 GPU 中进行粒子和场景的碰撞检测。出于计算效率方面的考虑,在对粒子进行碰撞检测时一般只会使用屏幕空间和深度图来简化计算。

在这里插入图片描述

Advanced Particles

Crowd Simulation

使用粒子来模拟人群

此时每个粒子不仅仅具有常见的物理属性,还会携带顶点等几何信息。

Animated Particle Mesh

要用粒子模拟人群,首先要用粒子来表达单个人

可以假设一个 Mesh 粒子受一个骨骼控制

虽然会出现 Mesh 之间的“断裂”的效果,但是既然是作为粒子来模拟,就不需要那么精细

Particle Animation Texture

基于粒子的几何信息还可以让 NPC 动起来,甚至可以利用状态机的理论制作简单的动画。

Navigation Texture

基于 SDF 的相关技术还可以控制群体的运动行为。

控制 Particle 人群的方法就是施加力,配合 SDF 生成的 Direction Texture 可以得到远离障碍物的方向,就可以控制人群的走向

摄像机距离粒子太近的时候可以给粒子一个力,制造出摄像机有体积的错觉

在这里插入图片描述

Utilizing Particle System in Games
Design Philosophy - Preset Stack-Style Modules

在这里插入图片描述

Design Philosophy - Graph-Based Design

在这里插入图片描述

Hybrid Design

在这里插入图片描述

Sound System

声音的大小称为音量(volume),它表示声波的振幅。

从物理的角度上讲,声音的本质是空气的振动。当空气发生振动时会产生相应的压强,这个压强的大小就对应人感知到的音量。音量的单位是分贝(dB),它是基于人对于声音的感知来定义的。

L p = 20 log ⁡ 10 ( p p 0 ) d B L_p = 20\log_{10}(\dfrac{p}{p_0}) dB Lp=20log10(p0p)dB

p 0 = 20 μ P a p_0 = 20 \mu Pa p0=20μPa

可见,音高并不是随压强线性的

音高(pitch)是描述人耳对声音调子感受的物理量,它取决于声音振动的频率。音高越高,声音就越尖锐。

音色(timbre)是描述声波形状的量。不同的乐器在演奏时会产生不同形式的基波,因此即使声波的频率相同也会产生不同的音色。

由于声音的本质是空气振动,我们可以在接收端叠加一个与当前振动相反的振动从而产生静音的效果。这就是现代降噪耳机的基本原理。

人耳对不同频率声音的感受范围可参考下图:

在这里插入图片描述

Digital Sound
Pulse-code Modulation (PCM)

自然界中的声音是连续的信号,因此要使用计算机存储或者表达声音就需要对连续的信号进行离散。最常用的声音采样设备是PCM(pulse-code modulation),它可以把连续的信号量化为离散的数字信号。

根据Nyquist采样定理,我们只需要2倍于人耳接收频率的采样频率就可以完美的重建原始信号。不过在实际采样时往往会使用更高一些的采样频率来获得更好的音质。

采样后的信号需要通过量化的过程编码为数字。

基于上面介绍的过程就可以把声音使用计算机可以识别和保存的数据。目前常用的声音格式如下:

在这里插入图片描述

3D Audio Rendering

Listener

在游戏设计中我们需要在三维的环境里设置音效从而让玩家有身临其境的感受,因此我们需要设置一个虚拟的麦克风来采集场景中的声音。通常情况下这个虚拟麦克风需要包含位置、速度以及朝向等物理信息。

对于人耳而言,我们可以利用双耳之间接收声音细微的变化来判断声源的大致位置。因此在游戏音效系统中可以利用人耳的这种效应来制造空间感。

Listener 包含位置、速度、朝向等属性

Spatialization

在这里插入图片描述
Panning 声像

Soundfield 声场

Binaural Audio

Panning

人在判断一个声源的方位时,两只耳朵会分别接收到不同响度和不同频率的信号,当左耳响度大时,声源就位于左方,反之则在右方,这就是所谓的双耳效应。

当音响有多个通道时我们可以调整不同音响的参数来产生空间感,这种方法称为panning。最简单的 panning 是进行线性插值,假设有某个声源从前方经过,我们可以对左右两通道的声音进行线性插值来模拟这种效果。

实际上人耳对于声音的感知是一个非线性函数,因此我们可以使用平方函数来改进线性插值的效果。此时会出现当声源经过正前方时接收到的声音会小一点。

在这里插入图片描述
在这里插入图片描述
更进一步还可以使用三角函数来描述声音的变化,这样可以得到更逼真的效果。

在这里插入图片描述
当然实际游戏中的 panning 算法会比上面介绍的要复杂得多,也可以表达更加复杂的空间位置变化。

Attenuation

当声源远离麦克风时会出现衰减(attenuation)的现象,随着距离的增加虚拟麦克风能接受到的声音会不断减少。实际上不仅是接收到的音量会发生变化,距离也会对接收到声音的频率产生一定的影响。

最简单的衰减模型是球形衰减,此时声音的变化只与声源和麦克风之间的距离有关。

更复杂的模型是胶囊形的衰减模型,它主要与麦克风到中心轴的距离相关。当接收端沿轴方向运动时声音的衰减基本保持不变。用于小溪等

对于室内场景可以考虑盒子形的衰减模型。

对于高音喇叭这种声源则可以使用锥形的衰减模型描述声源的朝向。

Obstruction and Occlusion

由于声音的本质是波,在封闭环境中还需要考虑声波与环境的互动。当声音被场景中的障碍物阻挡时可以通过衍射的方式继续传播,而如果声音的传播被完全阻挡它也可以通过阻挡物自身的振动继续向外传播。

因此在复杂的场景中往往需要使用一些采样的方法来模拟声音传播的效果。

Reverb

除此之外声音在室内场景中还会出现混响(reverb)的现象,这是由于声波在场景中不断反射所导致的。

混响的效果很大程度上取决于材质的吸声特性,不同材质往往在不同的频率上有着巨大的性能差异。另一方面混响也取决于场景的几何特征。

direct dry 干音

early reflections echo 第一次反射的回音

Late reverberations (tail) 之后多次反射的尾音

在这里插入图片描述
通过调整不同的混响组合比例可以实现丰富的声学效果。

Pre-delay(seconds) 回音时间

Wet level 湿音的比例

Dry level 干音的比例

HF ratio 对高频或低频的混音效果

在这里插入图片描述

Sound in Motion

当声源发生运动时由于 Doppler 效应会导致接收端接收到的频率发生变化。

打击感和速度感的来源

Sound Field

很多时候还可以对整个声场进行采集。

Common Middlewares

目前市面上常用的专业级声学引擎包括fmod或wwise等,这些引擎可以更好地辅助专业的声学设计。

Modeling Audio World

目前想要表现大规模场景的声学特性仍是非常复杂的。

例如蜘蛛侠中对整个世界的混音效果都做了建模

在这里插入图片描述

想写一个 CPU 上的粒子系统,是不是很适合用 ECS 模型来做

有一定价值,CPU 上的复杂命令还是没有办法在 GPU

粒子系统可以和之前将的动画系统和物理系统相结合吗

可以的,有这个需求

比如之前的人物粒子

houdini 也有

crowd 系统中的 sdf 是预先算好的吗

sdf 可以运行时算,也用来被做全局光照

游戏中的音效数据是怎么组织的

先使用中间件,然后使用延迟加载,加载音效资源,用到了现在加载再播放

第十三节 引擎工具链基础

Tool Chain

工具链(tool chain)是沟通游戏引擎用户以及更底层 run time(渲染系统、物理引擎、网络通信等)之间的桥梁。对于商业级游戏引擎来说,工具链的工程量往往要比 run time 大得多。

在这里插入图片描述
另一方面,工具链也是连接游戏引擎以及第三方 DCC 工具的核心。在现代游戏工业中需要使用到大量的第三方工具如 MAYA、3DS MAX 等,在进行游戏开发时需要通过工具链将这些第三方 DCC 的资源加载到游戏引擎中。

从更高级的视角来看,工具链的本质是调和不同背景和思维方式用户的一套平台。对于开发者,工具链需要方便开发者管理游戏中大量资源和对象;对于设计师,工具链需要帮助他们快速实现不同的游戏逻辑;而对于艺术家,工具链则需要帮助他们表达不同的创意。工具链需要服务这些拥有不同知识背景的用户以便更好地完成游戏开发的过程。

Complicated Tool

GUI

GUI 是工具链与用户直接进行交互的接口,在现代软件工程中 GUI 是人机交互的必要模块。

目前 GUI 主要有两大类实现方式,其一是 immediate mode。在 immediate mode 中用户的操作会直接调用GUI模块进行绘制,让用户立刻看到操作后的效果。这种模式的特点是它非常直观而且易于实现,但它的效率和可拓展性往往不尽如人意。

在现代游戏引擎中更常用的 GUI 实现方式是 retained mode。在 retained mode 中用户的操作不会直接进行绘制,而是会把用户提交的指令先存储到一个 buffer 中,然后在引擎的绘制系统中再进行绘制。这样做的好处是可以极大地提高系统的运行效率和可拓展性,当然代价是这种方式的实现要更加复杂。

还有一个好处是,如果玩家输入没有改变,那么我就可以使用最后一次的指令来绘图

Design Pattern

在设计GUI系统时需要设计模式(design pattern)相关的知识,这里简要介绍一些在GUI设计中常用的设计模式。

MVC

MVC是经典的人机交互设计模式。MVC的思想是把用户(user)、视图(view)和模型(model)进行分离,当用户想要修改视图时只能通过控制器(controller)进行操作并由控制器转发给模型,从而避免用户直接操作数据产生各种冲突。

在这里插入图片描述
感觉最大的作用就是清晰了数据的单向流动

MVP

MVP 可以看做是对 MVC 的演变。MVP 模式对视图和模型进行了更彻底的分离,视图只负责对数据进行展示而模型只负责对数据进行处理,它们之间的通信则通过展示者(presenter)来实现。当用户想要修改数据时,用户的请求会通过视图提交给展示者,然后再由它转发给模型进行处理。

在这里插入图片描述
主要压力在 presenter,既需要听懂 view 又需要听懂 model

MVVM

MVVM 是目前游戏引擎中大量使用的 UI 设计模式,在 MVVM 中视图和模型的中间层称为 ViewModel。在 MVVM 模式中,视图只包含简单的 UI 状态数据,这些数据通过 ViewModel 解析成合适的数据结构再提交给模型进行处理。

在这里插入图片描述
view 就是表现 layout,例如使用 xml,尽量所见即所得

viewmodel 将 view 类型的数据转换成 model 类型的数据

model 负责逻辑

在这里插入图片描述
优点就是比较独立,方便测试,方便服用

缺点是对于简单的 UI 可能比较笨重,数据绑定的过程难以 debug,不把系统跑起来就不知道绑没绑对

Load and Save

加载和保存各种不同类型的数据是工具链的核心功能。在保存数据时需要使用序列化(serialization)的技术来将各种不同的数据结构或是 GO 转换成二进制格式,而当需要加载数据时则需要通过反序列化(deserialization)从二进制格式恢复原始的数据。

最简单的序列化方法是把数据打包成 text 文件。text 文件虽然简单,但实际上目前很多系统仍然是使用 text 文件进行信息的传输。目前常用的 text 文件格式包括 json、yaml、xml 等。

text 文件可以方便开发人员理解存储数据的内容,但计算机对于文本的读取和处理往往是比较低效的,比如要做语义的处理。当需要序列化的数据不断增长时就需要使用更加高效的存储格式,通常情况下我们会使用二进制格式来对数据进行存储。

和 text 文件相比,二进制文件往往只占用非常小的存储空间,而且对数据进行读取也要高效得多。因此在现代游戏引擎中一般都会使用二进制文件来进行数据的保存和加载。

debug 二进制序列化的时候,可以序列化成一套 json 一套二进制,去 json 找问题

Asset Reference

在很多情况下游戏的资产是重复的,此时为每一个实例单独进行保存就会浪费系统的资源。

因此,在现代游戏引擎中会使用资产引用(asset reference)的方式来管理各种重复的资产。实际上资产的引用和去重是游戏引擎工具链最重要的底层逻辑之一。

在游戏开发过程中工具链往往还需要提供对 GO 进行修改,从而实现不同的艺术效果。例如要对 GO 提供 Variance 的能力

如果直接用数据拷贝来做 Variance,这个拷贝出来的独立数据与源数据没关系,那么管理很难,在调整和修改数据时直接进行复制很可能会破坏GO之间的关联而且容易造成数据的冗余,因此在现代游戏引擎中对于数据引入了继承(inheritance)的概念。数据之间的继承可以很方便地派生出更多更复杂的游戏对象,从而方便设计师和艺术家实现不同的效果。

How to Load Asset

Parsing

在上一节我们主要是考虑如何对数据进行保存,而游戏引擎中工具链的一大难点在于如何加载不同的资产,即反序列化的过程。反序列化的过程可以理解为对文件进行解析(parsing),文件中的不同字段往往有着不同的关键字以及域。我们需要对整个文件进行扫描来获得整个文件的结构。

在这里插入图片描述

对文件完成解析后可以得到一棵由< key-value >对组成的树来表达不同类型的数据。

在这里插入图片描述

实际上这样的过程与对文本文件的解析过程是非常类似的。

在这里插入图片描述
例如首先确定数据长度,然后根据这个长度读取数据

Endianness

对二进制文件进行反序列化和解析时需要额外注意 endianness 的问题。在不同的硬件和操作系统上同样的二进制文件可能会被解析为不同的数据,这对于跨平台的应用需要额外注意。

例如不同系统上可能大端存储或者小端存储

在这里插入图片描述

Version Compatibility

在工具链的反序列化过程中还需要考虑资产的兼容性问题。游戏的开发周期往往是比较长的,在这一过程中可能会不可避免地出现引擎以及各种工具的升级。而我们希望新版本可以对旧版本中设计好的资源进行兼容,从而避免重复的劳动。

在版本更迭中最常见的情况是数据的域发生了修改,新版本的数据定义可能会添加或删去老版本定义的域。

为了处理这种问题可以手动为数据文件添加版本号,在加载数据时根据版本号来控制加载过程。

在这里插入图片描述

更好的处理方法是使用 guid 来进行管理。如 Google 就提出了使用 protocol 来为每一个域赋予一个 uid,在进行反序列化时只需要对域的 uid 进行比较即可。

在这里插入图片描述

How to Make Robust Tools

在游戏引擎中工具链对于鲁棒性有非常高的要求,一旦游戏引擎的工具链出现问题会对整个游戏开发流程产生巨大的影响。鲁棒性最基本的要求是允许程序从崩溃中进行恢复,从而还原初始的开发状态。为了实现这样的功能我们需要将用户所有的行为抽象为原子化的命令(command),通过命令的序列来表示整个开发的过程。

有些时候,用户的一个操作可能需要分解为多个命令

在这里插入图片描述
对 command 类进行抽象时需要为每一个 command 实例赋予单调的 UID 从而保证顺序的正确性,同时每一个 command 定义都需要实现 Invoke() 和 Revoke() 方法表示执行命令以及恢复到执行命令前的状态。除此之外还需要实现 Serialize() 和 Deserialize() 方法来控制生成的数据序列化以及反序列化过程。

Serialize() 和 Deserialize() 由 GO 提供

在这里插入图片描述

整个 command 系统可以划分为三种不同类型的指令,包括添加数据、删除数据以及更新数据。实际上几乎所有的 command 都可以视为这三种基本指令的组合。

在这里插入图片描述

How to Make a Tool Chain

现代游戏引擎的工具链往往包含成百上千个不同的工具程序,这些程序会面向不同背景的开发人员并实现相应的功能。

而对于工具链来说,一个基本要求是要保证不同工具之间的沟通以及整个系统的可拓展性。我们不希望每个工具程序都使用单独的一套数据定义方式,这会导致整个工具链系统过于庞大而且难以进行维护。

因此我们需要去寻找不同工具中的一些共性,并把这些共同的数据封装为基本的单元。利用对这些基本单元的组合来描述更加复杂的数据结构。

Schema

schema 是一种对数据进行描述的结构,它描述了具体的数据结构是由哪些基本单元构成的。在工具链系统中所有流动的数据都要通过 schema 来进行描述,从而保证不同的程序都可以对数据进行解读。

例如我可以用三个 Float XXX 来描述一个长方体,也可以用一个 Float r 来描述一个圆,这个 Float XXX 就是 schema

在这里插入图片描述
在这里插入图片描述

在 schema 的实现中一般也需要实现继承和引用功能来方便定义新的数据类型。

在这里插入图片描述
在这里插入图片描述
不难发现 schema 与高级语言有着很多相似之处,实际上 schema 确实可以直接使用高级语言来进行定义。目前游戏引擎中的 schema 系统主要有两种实现方式,其一是单独实现 schema 的定义,例如使用 xml,而另一种则是使用高级语言进行定义,例如 Unreal 在 C++ 中的反射。

在这里插入图片描述
在这里插入图片描述
这两种实现方式各有各的优缺点,它们的特点可以总结如下:

standalone

定义与工程分离

需要生成代码,版本不兼容时

写 api 比较

defined in code

优点:

  1. 容易实现函数反射

  2. 自然支持继承关系

缺点:

  1. 难以理解

  2. 高度组合性,容易崩

Three Views for Engine Data

基于 schema 系统我们可以发现同样的数据在游戏引擎的不同系统和工具中可能会有不同的表现形式。

在 runtime 中一般会以运行和计算效率为第一要务。

而在进行存储时则要游戏考虑数据的读写速度和空间需求。

而在面向开发者的工具程序中需要根据不同使用者的背景和需求来设计不同的数据表现形式。

What You See is What You Get

所见即所得(what you see is what you get, WYSIWYG)是我们设计构建整个工具链系统的核心精神,它的目标是保证设计师和艺术家在工具链中的设计结果能完美地重现在实际的游戏场景中。在早期的游戏引擎中一般会设计一个独立工具层用来辅助开发者进行设计,但这种设计方式往往会违背 WYSIWYG 原则因此在现代游戏引擎中基本已经被弃用。

目前商用级游戏引擎一般会把工具层设计在整个游戏引擎的最上层,换句话说工具层会调用底层的模块来辅助游戏开发者的工作。这种设计方式的好处是可以严格遵循WYSIWYG原则,提高开发效率。当然其缺陷在于此时的工具层依赖于整个引擎的实现,当引擎崩溃时工具链也会直接崩溃。

在这里插入图片描述
在这里插入图片描述

Play in Editor

基于 in game 的设计模式就可以实现在工具层中进行游戏,当然这也要求我们在工具层上再设置一个编辑器来编辑游戏的功能。

在编辑器中进行游玩时同样有两种实现方式,包括直接在编辑器中进行游戏或是基于编辑器当前的状态生成一个新的游戏窗口进行游戏。

直接在编辑器中进行游戏(Play in Editor World)可以无缝地对当前游戏场景进行编辑,但需要注意在进行编辑时不要污染游戏场景中的数据。

另一种实现方式是新建一个沙盒来重现当前的游戏环境(Play in PIE World),整个游戏过程都在沙盒中进行。这种设计方式可以保证编辑器中的数据与实际游戏中的数据保持相互独立,避免出现数据污染的情况。在大型游戏引擎的开发中一般会使用这种模式。

Plugin

在工具链中我们往往还需要允许用户根据自身的需要自行设计并开发新的工具,也即插件(plugin)。实际上这些插件定义了整个游戏引擎的可拓展性,在现代游戏开发过程中需要使用到各种各样的插件以实现不同的功能。

目前市面上的商业级游戏引擎都对插件有很好的支持,方便用户自行开发和分享各种插件。

Framework

需要提供 API

在这里插入图片描述
在这里插入图片描述
举例:添加一个按钮的插件的编写方式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考资料

• Tools Tutorial Day: A Tale of Three Data Schemas, Ludovic Chabant, GDC
2018:https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/gdc18-tools-tutorial-day-a-tale-of-
three-data-schemas.pptx
• Creating a Tools Pipeline for ‘Horizon: Zero Dawn’, Dan Sumaili, Sander van der Steen, GDC 2017:
https://www.guerrilla-
games.com/media/News/Files/GDC2017_Sumaili_VanDerSteen_CreatingAToolsPipelineForHorizonZeroD
awn.pdf
• Unreal Engine UProperties:https://docs.unrealengine.com/5.0/en-US/unreal-engine-uproperties/
• Command Pattern:https://www.tutorialspoint.com/design_pattern/command_pattern.htm
• Unreal Plugins:https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/Plugins/

• Model–view–controller:https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
• Trygve Reenskaug:https://en.wikipedia.org/wiki/Trygve_Reenskaug
• MVC:https://developer.mozilla.org/en-US/docs/Glossary/MVC
• MVC:https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-index.html
• Benefits and Drawbacks of MVC Architecture:https://shreysharma.com/benefits-and-drawbacks-of-mvc-
architecture/
• Model–view–presenter:https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter
• Model–view–viewmodel:https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel

QA

协同编辑有没有很好的实现

第十四节 引擎工具链高级概念与应用

在现代游戏的开发过程中,设计师和艺术家往往需要使用大量的第三方工具来辅助进行游戏角色和场景的建模。同时对于不同类型的游戏,游戏玩法和关卡设计也往往存在着巨大的差别。因此对于工具链来说,它需要实现和不同开发工具的通信也要考虑不同用户的使用需求。除此之外 WYSIWYG 原则又要求开发者在引擎中的体验必须和实际游戏时完全一致的,这对工具链的设计提出了更大的挑战。

在这里插入图片描述

World Editor

世界编辑器(world editor)是整合了游戏引擎中几乎所有功能的平台。以虚幻引擎为例,虚幻中的世界编辑器界面包括各种不同的面板以服务不同的开发者:

重要的是,它是一个平台

Editor Viewport

viewport 可以说是整个编辑器最重要的窗口,它是连接开发者与所构建的游戏世界的接口。实际上在 viewport 中所展示的正是游戏世界在运行时的状态(editor mode 的游戏),从而方便开发者对游戏内容进行各种调整。

在这里插入图片描述
如果不小心把 editor-only 的代码发布在 release 版本里面了,就可能被用来做外挂

需要包在 editor only 宏中

还需要兼容多个 view 的需求

Editable Object

在编辑器中所有的对象都是可编辑对象(editable object),开发者可以根据需要调整这些对象的位置、姿态、材质等属性。因此游戏中所有的元素都必须抽象为这样的可编辑对象。

在同一个游戏场景中可能有成千上万个对象,因此我们需要一些高效的管理工具。在编辑器中往往会使用树状结构或是分层的方式来管理场景中的对象,有时还会根据对象自身的特点设计相应的管理工具。

当开发者选中某个对象时需要使用 schema 来获取该对象自身的信息。

在这里插入图片描述

Content Browser

在编辑器中还需要实现 content browser 用来管理开发过程中设计的各种美术、场景资产。在游戏开发过程中很多的资产是可以重复利用的,通过 content browser 可以方便地查看、检索以及分享现有的资产,从而提升游戏开发的效率。

相当于把资产放到池子里

Editing

世界编辑器的核心功能是方便开发者去编辑游戏场景中的各种对象。

Mouse Picking

而在编辑器的实现中,首先也是最重要的功能是如何实现通过鼠标来选取物体。

最简单的实现方法是使用渲染系统中的ray casting功能,利用鼠标的位置来发射光线并通过与物体bounding box求交来来选择物体。这种实现的缺陷在于当物体比较复杂时bounding box是不能完全反应物体的几何形状的,此时使用ray casting的效率可能会比较低。

另一种实现方法是在渲染流程中添加一个额外的选取帧,为图像上每一个像素赋予一个物体编号,这样使用鼠标进行选取时只需根据物体编号进行查询即可。当然这种实现方式对计算机的硬件提出了更高的要求,同时需要注意在游戏发布时去掉这部分编辑环境下的代码。

这个方法还有一个问题是,怎么选择透明物体,以及粒子发射器等物体,常见的做法是给一个虚拟体积,也有一些特殊操作

在这里插入图片描述

Object Transform Editing

选取得到物体后一般还会对物体进行一些几何变换,包括平移、旋转和缩放等,这些操作的具体实现往往还需要根据使用者的习惯来进行设计。

Terrain

对地形进行设计时需要结合高度场、地形纹理以及各种装饰件等。

对于边界器来说地形设计最常用的工具是高度笔刷(height brush),如何对设计出的高度场进行平滑需要很多相关的经验。当然很多商业级引擎中还提供了自定义的工具来帮助设计师对效果进行定制。

另一种常用的笔刷是 instance brush,它通常用来在设计好的地形上添加各种装饰件。

除此之外编辑器还需要提供各种环境元素的实现。

Rule System

游戏中的各种对象需要按照一定的规则来组织起来,因此规则系统(rule system)也是编辑器的重要组件。

例如道路系统,不能有植被,装饰物在路上,道路也会影响地形,石头会散布在道路旁边等等

在这里插入图片描述
早期的游戏引擎往往不包括这样的系统,为了保证游戏世界的合理性需要设计师人工调整游戏中的各种对象。而在现代游戏引擎中则需要提供自动化的规则系统,通过程序化的方式来保证游戏中的各种对象遵循相互之间的规则。

程序化生成的东西也要满足能够局部修改

也可能有各种风格的生成规则

Editor Plugin Architecture

显然整个世界编辑器是一个非常复杂的软件程序,我们很难直接在开发过程中实现所有需要的功能。因此在现代游戏引擎中一般会设计相应的插件系统来帮助用户根据自身的需求来丰富编辑器的功能。实际上不仅是游戏引擎,很多现代软件都使用了类似的策略来允许用户对系统进行定制。

在这里插入图片描述

A Cross Matrix between Systems and Objects

而对于游戏引擎而言,插件系统需要考虑的一个问题是如何对插件种类进行划分。我们可以按照游戏对象的种类(网格、粒子、动画等)对插件进行分类,也可以根据对象的内容(NPC、建筑、人群等)进行分类。因此现代游戏引擎的编辑器往往需要支持这种矩阵式的分类方法,允许用户根据喜好来选择和定制插件。

在这里插入图片描述
在对插件系统进行整合时还需要考虑不同插件之间的版本问题。不同的版本之间可以按照覆盖(covered)或是分布式(distributed)的方式进行协作。

Combination of Multiple Plugins

覆盖式

分布式

在这里插入图片描述
管道 数据在管道中顺序处理

洋葱圈

在这里插入图片描述

随着编辑器以及各种插件之间版本的迭代,插件系统一般还需要考虑版本控制的问题。

Design Narrative Tools

除了游戏资产的设计外,叙事(story telling)在整个游戏开发流程中同样是非常重要的一环。叙事可以看做一个线性的过程,相关的游戏资产需要在一个时间轴上按照顺序进行调度。

在这里插入图片描述

在虚幻引擎中使用了 sequencer 来跟踪游戏对象及其属性在时间轴上的变化。当我们把不同的对象利用 sequencer 在时间轴上组织起来就实现了简单的叙事。

在这里插入图片描述

要使用 sequencer 首先需要选中对象然后把 sequencer 绑定到对象上,然后选择相关的属性并利用关键帧设置这些属性的变化,最后利用插值就完成了叙事。

Reflection and Gameplay

反射(reflection)是 sequencer 乃至整个游戏引擎编程中都非常重要的技术,通过反射我们可以让游戏引擎在运行阶段获取操作对象具有的各种属性。实际上对于游戏开发流程而言,游戏引擎的开发者很难实现预判用户的需求。因此反射对于现代游戏引擎而言几乎是一个必备的工具。

早期的游戏引擎是不基于反射技术来实现的,这导致开发者在进行编程时必须手动为游戏对象添加所有需要的属性和数据,使得工具链的开发很难与游戏开发进行匹配。

反射是现代高级语言中引入的一个非常实用的概念。通过反射,系统可以获知每个新实现的类提供的接口。

对于硬编码,只能将方法名或者属性名写死

在这里插入图片描述在这里插入图片描述

但是使用反射,是根据方法名或者属性名去获取某个示例中相应的方法或者属性

在这里插入图片描述

反射可以理解为沟通代码和工具之间的一座桥梁。

在这里插入图片描述

How to Implement Reflection in C++

在C++中默认是不支持反射的,不过可以基于编译器来实现反射的功能。

  1. 从代码中收集类型

  2. 生成代码,提供方法和属性的接口

  3. 建立 <string, 接口> 的 map

How to Get Type Info from Code

具体来说,C++ 编译过程是分了很多步的,编译过程中会形成一个抽象语法树,从抽象语法树生成 Schema

在这里插入图片描述
在这里插入图片描述

Generate Schema From AST

解析类名、字段名、字段类型等

在内存中构建一个临时的 Schema

Precise Control of Reflection Scope

实际使用的时候,通过添加 Tag 实现精准反射

在这里插入图片描述

Use Marco to Add Reflection Controls

通过添加宏定义来实现反射控制

在这里插入图片描述

Reflection Accessors

反射对于三类对象要生成的东西:

在这里插入图片描述

Code Rendering

代码渲染:自动化生成代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Collaborative Editing

合并冲突

  1. 资产分割

    1. 分层(地形层、植被层、房屋层)

    2. 分割世界(地图分块)

      难以支持跨界的物体

  2. 一个对象一个文件(预制)

    小文件多,操作系统不友好

  3. 实时编辑 同步 command

    1. 怎么解决 Undo/redo?

    2. 怎么解决 Operation merge

    3. 加锁(不能同时操作同一个对象)

      Instance lock

      Asset lock

How to Solve These Problems Thoroughly

在这里插入图片描述

Traditional Workflow vs. Collaborative Editing Workflow

在这里插入图片描述

参考资料

• C++ Reflection, Austin Brunkhorst, 2016:https://austinbrunkhorst.com/cpp-reflection-part-1/
• Clang:https://en.wikipedia.org/wiki/Clang
• Reflective programming:https://en.wikipedia.org/wiki/Reflective_programming
• Unreal Blueprints Visual Scripting:https://docs.unrealengine.com/5.0/en-US/blueprints-visual-scripting-
in-unreal-engine/
• Unreal Engine UProperties:https://docs.unrealengine.com/5.0/en-US/unreal-engine-uproperties/
• GPU-Based Run-Time Procedural Placement in ‘Horizon: Zero Dawn’, Jaap van Muijden, GDC
2017:https://www.gdcvault.com/play/1024700/GPU-Based-Run-Time-Procedural
• Ray casting:https://en.wikipedia.org/wiki/Ray_casting
• Level (video games):https://en.wikipedia.org/wiki/Level_(video_games)

• Unreal One File Per Actor:https://docs.unrealengine.com/5.0/en-US/one-file-per-actor-in-unreal-
engine/
• Uses of layers in Unity:https://docs.unity3d.com/Manual/use-layers.html
• Unreal World Partition:https://docs.unrealengine.com/5.0/en-US/world-partition-in-unreal-engine/
• How Figma’s multiplayer technology works, Evan Wallace, 2019:https://www.figma.com/blog/how-
figmas-multiplayer-technology-works/
• Unreal Multi-User Editing:https://docs.unrealengine.com/4.27/en-
US/ProductionPipelines/MultiUserEditing/
• Conflict-free Replicated Data Types, Marc Shapiro,Nuno Preguiça, Carlos Baquero, Marek Zawirski,
2011:https://pages.lip6.fr/Marc.Shapiro/papers/RR-7687.pdf

• Operational Transformation Frequently Asked Questions and
Answers:https://www3.ntu.edu.sg/scse/staff/czsun/projects/otfaq/
• Unreal Sequencer Overview:https://docs.unrealengine.com/4.27/en-
US/AnimatingObjects/Sequencer/Overview/
• Unity Timeline:https://docs.unity3d.com/Packages/com.unity.timeline@1.7/manual/index.html
• Plug-in (computing):https://en.wikipedia.org/wiki/Plug-in_(computing)

QA

反射会影响效率吗

反射主要在 editor 中用,不会影响 runtime 的效率

DCC 工具和引擎工具的分工是什么

两边在融合,艺术家怎么方便怎么来

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值