约 20 分钟技术学习游戏引擎

Games104学习笔记(2)

GAMES104:引擎架构分层与整体 Pipeline

课程:GAMES104《现代游戏引擎:从入门到实践》
讲次:Lecture 02 / Layered Architecture of Game Engine
讲者:王希(Xi Wang,Booming Tech)
视频时长:约 66 分 51 秒
笔记性质:根据本地视频中的幻灯片与讲解主题整理,不是逐字稿;时间点为复习定位用的近似值。

一句话理解

现代游戏引擎通常采用分层架构:越靠下的层越稳定、越接近硬件,越靠上的层越面向内容和玩法、越容易定制。各层通过清晰的接口协作,把硬件、内存、资源、功能、编辑器和游戏内容连接成一条完整的生产与运行 Pipeline。

游戏项目 / 游戏内容
          ↑
Tool Layer       编辑器、DCC、资产处理、调试工具
          ↑
Function Layer   渲染、动画、物理、Gameplay、网络等功能
          ↑
Resource Layer   文件、资源导入、加载、缓存、生命周期
          ↑
Core Layer       数据结构、容器、内存、数学和通用基础设施
          ↑
Platform Layer   操作系统、CPU、GPU、内存、文件和平台 API

旁路:第三方库可以接入多个层,为某一类能力提供专门实现。

本讲学习目标

  • 理解为什么大型游戏引擎需要分层,而不能把所有功能写在一起。
  • 掌握 Tool、Function、Resource、Core、Platform 五层的职责和边界。
  • 理解“资产制作文件”如何经过处理,变成运行时可快速访问的资源。
  • 认识资源生命周期、Tick、多线程、容器和内存管理这些跨层问题。
  • 知道硬件架构为什么会影响引擎设计。

1. 为什么需要分层架构

1.1 控制复杂性

游戏引擎同时包含图形、物理、动画、音频、AI、网络、资源、工具和平台适配等大量模块。分层可以把复杂系统拆成若干组职责明确的子系统:

  • 上层使用下层提供的能力。
  • 下层不应该依赖上层具体游戏玩法。
  • 每一层可以在相对独立的边界内演进。
  • 平台差异不会扩散到所有业务代码。

1.2 稳定性与可定制性的权衡

本讲的核心结论之一是:

越底层的代码越稳定,越上层的代码越容易定制。

例如,内存分配、容器和平台抽象需要被大量系统共同依赖,频繁变化会造成巨大影响;而编辑器、玩法和具体游戏内容则需要不断调整,以适应项目需求。

1.3 通过接口隔离变化

分层并不意味着每一层完全封闭,而是要求依赖方向和接口清晰:

  • 上层不直接操作底层硬件细节。
  • 下层不应该知道某个具体游戏的关卡规则。
  • 系统之间通过数据和接口交互,而不是互相访问内部实现。
  • 当底层替换实现时,上层尽量不需要重写。

2. 五层架构总览

层 主要职责 典型内容 稳定性 / 定制性
Tool Layer 服务内容生产和开发协作 编辑器、DCC、资产处理、调试工具 定制性最高
Function Layer 提供游戏运行所需的功能系统 渲染、动画、物理、Gameplay、网络 面向项目扩展
Resource Layer 管理数据和运行时资源 导入、加载、缓存、依赖、生命周期 连接工具与运行时
Core Layer 提供通用底层基础 容器、数据结构、内存、数学、线程基础 稳定性较高
Platform Layer 屏蔽平台和硬件差异 OS、CPU、GPU、文件、窗口、图形 API 最底层、最稳定

此外,第三方库不一定属于某一个单独层。例如 Havok / PhysX 主要提供物理能力,Wwise / FMOD 主要提供音频能力,TrueSky、SpeedTree、Simplygon 等则提供天空、植被或几何处理能力。引擎需要负责把它们接入自己的资源、线程、内存和平台体系。


3. Platform Layer:平台层

3.1 职责

平台层位于架构底部,处理引擎与具体运行环境之间的差异:

  • 操作系统和系统调用。
  • 文件系统、窗口和输入设备。
  • CPU、GPU、内存和缓存结构。
  • 图形 API、音频 API 和线程 API。
  • 不同平台的编译、打包和发布方式。
  • 硬件资源限制和平台专有能力。

上层应尽量通过平台层提供的统一接口工作,而不是在渲染、资源或 Gameplay 代码中到处写平台特判。

3.2 硬件架构会影响引擎设计

视频以 PlayStation 3 的硬件架构作为例子,展示了:

  • PPU 与多个 SPU 的处理器结构。
  • SPU 的 Local Store。
  • 系统内存与显存。
  • DMA 控制器和数据搬运路径。
  • RSX GPU。
  • CPU、GPU、内存之间的带宽和访问方式。

这个例子说明,游戏引擎不是运行在抽象的“无限快计算机”上。数据应该如何布局、何时搬运、哪个处理器执行、是否适合并行,都与具体硬件有关。

需要区分两类常见内存组织:

  • 分离式内存:系统内存和显存相对独立,需要考虑数据上传和同步。
  • UMA(Unified Memory Access):CPU 和 GPU 共享或统一访问内存,数据交换方式不同,但仍需考虑带宽、缓存和访问竞争。

3.3 平台层的设计意义

平台层的目标不是抹平所有差异,而是把差异集中管理。这样:

  • Function Layer 可以使用统一的渲染、输入和线程接口。
  • Resource Layer 可以使用统一的文件和异步 IO 接口。
  • 工具和游戏逻辑不必了解每个平台的硬件细节。
  • 针对特定平台的优化仍然可以在底层保留。

4. Core Layer:核心层

核心层提供几乎所有上层都会使用的通用基础设施。本讲重点介绍了数据结构 / 容器和内存管理。

4.1 数据结构与容器

引擎中常见的数据组织包括:

  • 数组和向量。
  • 哈希表、映射和集合。
  • 树、图和层级结构。
  • 骨骼树。
  • 动画帧序列。
  • 场景对象、资源依赖和任务依赖。

引擎可能需要自己实现或封装专用容器,原因包括:

  • 标准容器的内存行为未必适合实时系统。
  • 需要更好地控制容量增长、对齐和分配策略。
  • 需要保证数据连续,提高 CPU 缓存命中率。
  • 需要支持序列化、反射、调试或跨平台行为。

设计数据结构时,不只要问“查找复杂度是多少”,还要问:

  1. 数据是否连续?
  2. 访问模式是否稳定?
  3. 是否会频繁分配和释放?
  4. 是否会跨线程访问?
  5. 是否适合批量处理?

4.2 内存管理

内存管理不只是防止内存不够,也是游戏性能的重要组成部分。常见问题包括:

  • 频繁分配和释放带来的开销。
  • 内存碎片化。
  • 数据分散导致缓存未命中。
  • 不同资源的大小和生命周期差异很大。
  • CPU 和 GPU 之间的数据存放、上传与同步。

引擎可能使用以下策略:

  • 自定义 allocator 和内存池。
  • Arena / Linear Allocation 等批量分配方式。
  • 按资源类型或生命周期分组分配。
  • 尽量让相关数据放在一起,提高局部性。
  • 通过内存统计、追踪和调试工具定位泄漏与峰值。
  • 使用 PMR(Polymorphic Memory Resource)等机制,将容器与分配策略解耦。

核心思想是:内存布局和访问方式会直接影响实时性能。引擎的内存系统应当让上层能够预测、观察和控制资源使用。

  • 相关的放一起
  • 顺序访问读写
  • 批量同次删除

5. Resource Layer:资源层

资源层负责把各种文件和数据,变成运行时可以高效使用的资源。

5.1 引擎面对的数据与文件

内容团队会产生很多不同类型的源文件,例如:

  • 模型:Maya、3ds Max、Blender 等工具产生的模型文件。
  • 纹理:PSD、PNG 等图像文件。
  • 动画:骨骼、关键帧和动画剪辑数据。
  • 音频:MP3、WAV 及其他音频数据。
  • 场景与关卡:场景文件、关卡文件和对象布局。
  • 脚本与图表:脚本、节点图和游戏逻辑数据。

这些文件的格式、用途和编辑方式不同,不能简单地原样交给运行时处理。

5.2 离线资源导入

常见的资源 Pipeline 是:

DCC / 外部源文件
        ↓
导入与解析
        ↓
Asset Conditioning Pipeline
        ↓
转换、压缩、优化、建立依赖
        ↓
引擎内部资源 / 运行时资源
        ↓
加载、缓存和使用

离线处理的好处是把昂贵的解析、转换和优化工作放到运行前完成,避免玩家启动游戏时再解析复杂的制作格式。

运行时资源通常会:

  • 使用引擎内部更适合读取的格式。
  • 预先建立依赖关系。
  • 进行压缩、烘焙或平台特化处理。
  • 将多个相关数据组织成适合批量读取的资源。
  • 为每个资源生成稳定的唯一标识。

5.3 GUID:稳定地引用资源

资源不能只依赖文件路径来引用。路径可能发生移动、重命名或重新组织,因此引擎通常会为资源分配 GUID(全局唯一标识符)。

GUID 的作用包括:

  • 稳定地标识资源。
  • 记录资源之间的依赖关系。
  • 在资源移动后仍能找到原对象。
  • 让编辑器、资源数据库和运行时保持一致。

5.4 资源访问

资源访问通常需要经过资源管理器,而不是由每个系统自行打开文件:

系统请求资源
      ↓
Resource Manager 根据 GUID 查找
      ↓
检查缓存 / 依赖 / 当前状态
      ↓
必要时异步加载和转换
      ↓
返回运行时资源句柄

这样可以统一处理缓存、引用计数、异步加载、错误报告和资源释放。

5.5 资源生命周期

不同资源的生命周期不同:

  • 启动时就需要的核心资源。
  • 进入关卡后才需要的场景资源。
  • 只在某个特效或事件期间使用的临时资源。
  • 可以跨关卡复用的共享资源。
  • 占用显存较大的纹理、模型和 GPU 资源。

有限内存要求引擎在适当时机释放不再需要的资源。常见机制包括:

  • 按需加载。
  • 延迟加载或异步加载。
  • 缓存和淘汰。
  • 引用计数或句柄管理。
  • 分阶段加载和卸载。
  • 垃圾回收或其他回收策略。
  • 对 CPU 资源和 GPU 资源分别管理。

资源生命周期管理的目标是平衡三件事:

加载速度 ↔ 内存占用 ↔ 运行时访问效率

6. Function Layer:功能层

功能层提供游戏世界运行时需要的具体系统。典型内容包括:

  • 渲染。
  • 动画。
  • 物理。
  • 音频。
  • AI 和导航。
  • Gameplay 和脚本。
  • 相机、特效和网络。

本讲重点放在功能系统共同面对的两个问题:Tick 调度和多线程执行。

6.1 Tick:让虚拟世界随时间变化

一个静态数据文件不会自动变成游戏世界。引擎需要不断推进时间、读取输入、更新对象并产生新的输出。这个周期性执行的更新过程通常可以抽象成 Tick。

一帧开始
   ↓
读取输入和时间
   ↓
执行各系统 Tick
   ↓
更新世界状态
   ↓
准备并执行渲染
   ↓
提交画面和其他输出

视频最后的 Takeaways 强调:

虚拟世界由一组时钟(clocks)和 Tick 组成。

这意味着不同系统可能有不同的更新节奏和依赖关系:

  • 有些系统需要每帧更新。
  • 有些系统可以降低更新频率。
  • 有些系统需要固定时间步长。
  • 有些系统必须等待另一个系统完成。
  • 有些工作适合异步或后台处理。

6.2 Tick 的设计问题

设计 Tick 系统时,需要考虑:

  • 执行顺序是否正确。
  • 数据依赖是否明确。
  • 是否存在循环依赖。
  • 每个系统是否真的需要每帧运行。
  • 更新和渲染之间是否会读写冲突。
  • 多线程时是否需要同步。
  • 时间步长变化时结果是否稳定。

如果所有对象都简单地在主线程中依次更新,容易理解但扩展性和性能有限;如果完全并行,又会引入依赖、同步和确定性问题。

6.3 多线程演进

视频展示了从简单到高级的几种执行方式:

  1. 固定入口线程:所有工作集中在一个主线程,结构简单,但难以充分利用多核。
  2. 主线程 + 线程池:把部分独立工作交给工作线程。
  3. 任务 / Job System:把工作拆成更细的任务,按依赖关系调度到多个核心(coming in future)。
任务 A ─────┐
            ├─→ 任务 C ─→ 任务 E
任务 B ─────┘
                  ↑
            多核任务调度器

多线程的关键不只是“创建更多线程”,还包括:

  • 任务粒度是否合适。
  • 工作负载是否均衡。
  • 任务之间的依赖如何表达。
  • 共享数据如何同步。
  • 是否会产生锁竞争和缓存抖动。
  • 主线程是否会等待后台任务。
  • 结果是否需要保持确定性。

因此,现代引擎的多线程架构通常需要把系统、数据和 Tick 一起设计。


7. Tool Layer:工具层

工具层是引擎面向创作者的一面,负责把底层能力转化为可操作的生产工具。

7.1 DCC 与编辑器

视频展示了两类工具:

  • DCC(Digital Content Creation):Maya、3ds Max、Blender、Houdini、FMOD 等外部内容制作工具。
  • Engine Editors:关卡、材质、蓝图 / 图表、动画、特效和其他引擎编辑器。

工具层的目标不是让所有人都直接阅读引擎源代码,而是让不同角色用各自熟悉的方式创作内容。

7.2 Asset Conditioning Pipeline

DCC 和引擎之间需要一个资产处理流程:

  • 识别和解析外部格式。
  • 检查资源合法性。
  • 转换坐标系、单位、材质或动画数据。
  • 生成引擎内部格式。
  • 压缩和优化资源。
  • 建立 GUID 和依赖关系。
  • 针对目标平台生成不同版本。
  • 在资源发生变化时重新处理相关内容。

这条 Pipeline 是“内容制作”和“运行时资源”之间的桥梁。

7.3 游戏内容与引擎的关系

视频中的结构示意可以概括为:

Game
  ↑ 使用
Game Engine
  ↑ 由工具和资源 Pipeline 服务
Editors + DCC + Asset Conditioning Pipeline

游戏项目不应该把引擎核心改得面目全非;更理想的方式是使用引擎提供的接口、资源格式、编辑器和扩展机制,构建自己的游戏内容。


8. Third-party Libraries:第三方库与中间件

游戏引擎可以复用成熟的第三方库,把有限的工程资源集中到自己的核心竞争力上。本讲展示的例子包括:

方向 例子
物理 Havok、PhysX
音频 Wwise、FMOD
天空与环境 TrueSky
植被 SpeedTree
光照或渲染辅助 Enlighten
几何处理 Simplygon

引入第三方库时,不能只看“功能能不能用”,还要考虑:

  • 数据格式是否兼容。
  • 内存分配是否统一。
  • 线程模型是否冲突。
  • 资源生命周期如何接入。
  • 平台是否都支持。
  • 调试、授权和版本升级如何管理。

第三方库是引擎生态的一部分,但整合责任仍然在引擎架构者手中。


9. 本讲最重要的结论

9.1 分层不是形式主义

分层的价值在于管理变化:平台和核心基础变化慢,功能和资源变化适中,工具和游戏内容变化快。把它们混在一起,会让任何修改都变得昂贵。

9.2 资源 Pipeline 是引擎的生命线

美术工具中的文件不能直接等同于运行时资源。导入、转换、压缩、依赖、GUID、缓存和释放,决定了内容能否稳定、高效地进入游戏。

9.3 数据布局和内存访问是性能问题

实时引擎的性能不仅由算法复杂度决定,也由数据是否连续、缓存是否命中、分配是否频繁、CPU / GPU 之间是否需要搬运等因素决定。

9.4 Tick 是世界时间的组织方式

虚拟世界不是一次性计算完成的,而是由许多时钟和 Tick 持续推进。Tick 的顺序、频率、依赖和线程安排,是功能层设计的基础。

9.5 多线程必须和数据依赖一起考虑

真正的并行化不是把函数随意放进线程,而是把工作拆成可调度任务,并明确数据所有权、依赖关系、同步方式和负载均衡。

9.6 工具层决定引擎能否被团队使用

一个引擎如果只有底层功能,没有好的编辑器、资源导入和协作流程,就很难转化为团队生产力。


10. 复习用速记

五层:Tool → Function → Resource → Core → Platform

下层:更稳定、更通用、更接近硬件
上层:更灵活、更面向内容、更容易定制

资源 Pipeline:
DCC / 源文件 → 导入处理 → 内部资源 → 运行时加载 → 缓存 / 释放

性能关键词:
数据局部性、缓存命中、内存池、资源生命周期、批处理、任务依赖

时间关键词:
Clock、Tick、更新顺序、更新频率、固定步长、同步

工具关键词:
Editor、DCC、Asset Conditioning Pipeline、GUID、Schema、调试

11. 自测题

  1. 为什么越底层的层通常越稳定,越上层的层通常越容易定制?
  2. Platform Layer 和 Core Layer 的边界分别是什么?
  3. 为什么不能让运行时直接读取所有 Maya、PSD 或其他 DCC 源文件?
  4. GUID 解决了单纯使用文件路径引用资源时的什么问题?
  5. 资源管理器为什么需要同时处理加载、缓存、依赖和释放?
  6. Tick 系统需要考虑哪些顺序、频率和依赖问题?
  7. 从主线程到线程池,再到 Job System,演进的主要动机是什么?
  8. 多线程为什么会引入锁竞争、数据竞争和确定性问题?
  9. 为什么引擎需要关注容器的数据连续性和缓存命中率?
  10. 第三方库接入引擎时,除了功能,还要检查哪些工程问题?
  11. DCC、Editor 和 Asset Conditioning Pipeline 分别扮演什么角色?
  12. 你在 Pilot Engine 中会按什么顺序寻找主循环和各层代码?

12. 术语表

术语 含义
Layered Architecture 分层架构,将系统按职责和依赖关系拆成多层
Pipeline 数据或工作经过多个阶段逐步处理的流程
Platform Layer 屏蔽操作系统和硬件差异的底层平台层
Core Layer 提供容器、内存、数学等公共基础的核心层
Resource Layer 负责资源导入、访问、缓存和生命周期的资源层
Function Layer 提供渲染、动画、物理和 Gameplay 等运行时功能的层
Tool Layer 面向内容生产、编辑和调试的工具层
DCC Digital Content Creation,数字内容创建工具
Asset Conditioning 将外部资产转换为引擎内部运行时格式的处理流程
GUID Global Unique Identifier,全局唯一资源标识
Tick 系统推进一小段时间并更新状态的过程
Job System 将工作拆成任务并按依赖关系调度到多核执行的系统
Cache Locality 数据访问的局部性,常用于提高 CPU 缓存命中率
Memory Pool 预先分配并重复利用的一组内存区域
PMR Polymorphic Memory Resource,多态内存资源机制
UMA Unified Memory Access,统一内存访问
Middleware 为引擎提供专门能力的第三方中间件或库

13. 参考资料