现代游戏引擎导论
Class 1:什么是游戏引擎?
🎮 游戏引擎的定义
游戏引擎(Game Engine)是一套为游戏开发提供通用能力的软件系统。
它的核心目标是:
让开发者专注于“做游戏”,而不是重复造轮子。
🧩 游戏引擎包含什么?
一个现代游戏引擎通常包括:
- 🎨 渲染系统(Rendering)
- 🦴 动画系统(Animation)
- ⚙️ 物理系统(Physics)
- 📦 资源管理(Asset Management)
- 🧠 AI 系统
- 🖥️ UI 系统
- 📜 脚本系统(Lua / C# 等)
- 🛠️ 编辑器(Editor)
- 🌍 跨平台支持
❓ 为什么需要游戏引擎?
如果没有引擎,每个游戏都要重复实现:
- 渲染管线
- 输入系统
- 场景管理
- 资源加载
- 平台适配
👉 这几乎是不现实的。
✅ 游戏引擎的价值
- 提高开发效率
- 降低重复劳动
- 支持多人协作
- 支持复杂大型项目
🧠 游戏引擎的三种角色
1️⃣ 运行时系统(Runtime)
负责游戏运行时的一切:
- 游戏逻辑更新
- 动画更新
- 物理模拟
- 渲染输出
2️⃣ 内容生产工具(Toolchain)
提供开发工具:
- 场景编辑器
- 材质编辑器
- 动画编辑器
- 粒子编辑器
🎯 好的工具 = 更高的生产效率
3️⃣ 协作中枢
连接不同角色:
- 👨💻 程序
- 🎨 美术
- 🧩 策划
Class 2:引擎架构分层
🏗️ 引擎整体结构
一个典型游戏引擎可以分为五层:
Tool Layer (工具层)
Function Layer (功能层)
Resource Layer (资源层)
Core Layer (核心层)
Platform Layer (平台层)
🛠️ 一、工具层(Tool Layer)
定义
面向开发者,不面向玩家。
功能
- 编辑器(Editor)
- 调试工具
- 打包工具
- 性能分析工具
🎯 核心目标
Allow anyone to create game
让更多人参与游戏开发。
🌍 二、功能层(Function Layer)
定义
让游戏世界“活起来”。
核心模块
- 渲染(Rendering)
- 动画(Animation)
- 物理(Physics)
- AI
- UI
- 音频
- 脚本系统
⏱️ Tick机制(极其重要)
一帧的基本流程:
Input → Logic → Animation/Physics → Render Data → Render
🔄 两个核心阶段
tickLogic:更新世界状态tickRender:绘制当前画面
❗ 为什么要分离?
| 逻辑(Logic) | 渲染(Render) |
|---|---|
| 更新世界状态 | 展示结果 |
| CPU主导 | GPU主导 |
✅ 好处
- 架构清晰
- 易于调试
- 支持多线程
- 支持不同刷新率
🦴 动画系统流程
动画选择 → 骨骼计算 → 应用到模型 → 网格更新 → 渲染
👉 动画系统是连接“逻辑”和“表现”的桥梁。
⚠️ 功能层特点
Heavy-duty Hotchpotch(复杂混合体)
问题:
- 模块多
- 边界模糊
🧠 哪些是典型引擎能力?
- Rendering Pipeline
- Asset Management
🧵 多核设计思想
现代引擎必须支持:
- 多线程
- 任务系统
- 并行计算
可并行任务
- 动画计算
- 粒子系统
- 资源加载
- 可见性剔除
📦 三、资源层(Resource Layer)
定义
管理所有游戏资源。
🆚 Asset vs Resource
| 类型 | 示例 |
|---|---|
| Asset | PSD / FBX / PNG / MP3 |
| Resource | 引擎内部数据 |
🔄 转换流程
Asset → Import → Process → Resource
❓ 为什么要转换?
- 提高加载速度
- 统一格式
- 去除冗余数据
- 优化运行效率
职责
- 资源导入
- 依赖管理
- 异步加载
- 缓存管理
🧱 四、核心层(Core Layer)
定义
引擎的基础设施层。
包含内容
- 数学库(Vector / Matrix / Quaternion)
- 内存管理
- 数据结构
- 任务系统
- 文件系统
- 日志系统
➗ 数学基础
用于:
- Transform(位移/旋转/缩放)
- 摄像机
- 动画
⚡ SIMD
Single Instruction Multiple Data
作用:
- 一条指令处理多个数据
- 提升计算效率
🧠 内存管理
❗ 为什么重要?
- 游戏是实时系统
- 需要稳定帧率
🏊 内存池(Memory Pool)
预分配 → 自主管理 → 高效分配
🚀 性能关键原则
- 数据集中(Cache Friendly)
- 顺序访问
- 批量处理
🖥️ 五、平台层(Platform Layer)
定义
屏蔽平台差异。
处理内容
- 操作系统(Windows / Linux / iOS)
- 图形 API(DX / Vulkan / Metal)
- 输入设备
- 文件系统
🎯 目标
统一接口,隐藏差异
🧩 总体结构总结
Tool Layer
↑
Function Layer
↑
Resource Layer
↑
Core Layer
↑
Platform Layer
📌 核心思想总结
1️⃣ 引擎是系统工程
不是单一模块,而是完整体系。
2️⃣ 逻辑与渲染分离(最重要)
是实时系统设计的核心。
3️⃣ 性能本质 = 数据
- 内存访问 > 算法复杂度(很多时候)
- Cache 命中极其关键
4️⃣ 工具链决定生产力
一个“好用”的引擎,比一个“跑得快”的引擎更有价值。
Class 3:如何构建游戏世界?
本节核心:Game Object + Component + Tick + Event + Scene 管理
🎮 什么是“游戏世界”?
要构建游戏世界,首先需要理解:
一个游戏世界由什么组成?
🧩 游戏的基本构成
一个游戏可以拆解为:
- 可交互的动态物体(角色、车辆、敌人)
- 不可交互的静态物体(建筑、道具)
- 地形系统(Terrain / Environment)
- 天空盒(Skybox)
- 其他辅助物体:
- 空气墙
- 检测点
- 触发器(Trigger)
🎯 统一抽象:Game Object(GO)
Everything is a Game Object
游戏世界中的一切,都可以抽象为:
Game Object(GO)
🧠 如何描述一个 Game Object?
一个 GO 通常由两部分构成:
- 属性(Property)
- 行为(Behavior)
🏛️ 早期方案:面向对象(OOP)
传统方式:
- 使用类继承
- 构建父子关系
例如:
Vehicle
├── Car
├── Boat
❗ OOP 的问题
随着复杂度上升,会遇到问题:
👉 分类变得困难
例如:
- 水陆两栖车(是车?还是船?)
👉 多重继承复杂、混乱
🧩 解决方案:组件化(Component-Based)
核心思想
用组件组合,而不是用继承分类
🧱 组件的例子
例如一个“车”:
- 移动组件(Movement)
- 物理组件(Physics)
- 渲染组件(Render)
- 输入组件(Input)
🔄 灵活组合
像积木一样:
- 换“铲子” → 挖掘机
- 换“滚轮” → 压路机
- FPS 游戏 → 武器配件组合
🧠 核心结论
Game Object = 一组 Components 的组合
🧩 组件系统设计
基础结构
通常会设计:
ComponentBase(基类 / 接口)
↑
各种具体组件(Render / Physics / AI / ...)
🎯 设计原则
- 符合直觉(给设计师用)
- 可组合
- 可复用
- 低耦合
🌍 世界如何“活起来”?
核心问题:How to Make the World Alive?
⏱️ Tick机制(再次出现!)
定义
tick():每一帧 / 固定时间推进一次世界
例如:
- 1/30 秒更新一次
🧠 Object-based Tick
每一帧:
- 每个 Game Object 更新
- 每个 Component 更新
⚠️ 问题:效率低!
如果:
- 每个 GO 都单独更新
- 每个组件分散执行
👉 会导致:
- Cache 不友好
- CPU 利用率低
- 难以并行
🚀 优化方案:System-based Tick
核心思想
按“系统”更新,而不是按“对象”更新
示例
Render System → 一次处理所有 Render 组件
Physics System → 一次处理所有 Physics 组件
Animation System → 批量处理动画
✅ 优点
- 批处理(Batching)
- Cache 友好
- 易并行
- 更高性能
🔗 游戏对象如何交互?
❗ 问题
例如:
- 飞机 ✈️
- 驾驶员 👨✈️
它们是两个独立 GO,如何产生联系?
❌ 旧方式:Hardcode
直接写死逻辑:pilot.control(plane)
问题:
- 耦合严重
- 难扩展
- 难维护
✅ 解决方案:事件系统(Event System)
核心思想
用“事件”解耦对象之间的关系
示例
Pilot 发出事件 → "EnterPlane"
Plane 接收事件 → 执行逻辑
优点
- 解耦
- 灵活
- 可扩展
🗂️ 如何管理大量 Game Object?
👉 场景管理(Scene Management)
🌐 空间数据结构(关键)
为了高效管理 GO,需要空间划分:
🧱 方法 1:网格划分(Grid)
把世界切成格子
优点:
- 简单
- 易实现
缺点:
- 精度有限
🌲 方法 2:四叉树(Quadtree)
递归划分空间
特点:
- 层级结构
- 自适应密度
🌳 其他结构
- 二叉树(Binary Tree)
- 八叉树(Octree)
- BVH(Bounding Volume Hierarchy)
🎯 作用
用于:
- 碰撞检测
- 可见性剔除(Culling)
- 空间查询
⚠️ 关键工程问题
1️⃣ 组件依赖关系
例如:
- Render 依赖 Transform
- Physics 依赖 Collision
👉 必须管理依赖顺序
2️⃣ Tick 顺序
不同系统执行顺序:Input → Physics → Animation → Render
顺序错误会导致:
- 抖动
- 错误行为
3️⃣ 消息时序问题(非常重要)
相同输入必须产生相同结果(Determinism)
❗ 为什么重要?
- 网络同步(多人游戏)
- 回放系统
- Debug
🧩 总体结构总结
Game World
├── Game Object(GO)
│ └── Components
│
├── Systems(批量更新)
│
├── Event System(交互)
│
└── Scene Management(空间管理)
Class 4:游戏引擎的绘制(Rendering)
本节核心:Rendering Pipeline + GPU + 数据组织 + 性能优化
🎮 为什么渲染如此重要?
几乎没有游戏可以脱离渲染系统存在
渲染系统决定了:
- 玩家“看到什么”
- 游戏的视觉质量
- 性能上限
⚠️ 渲染的核心挑战
1️⃣ 场景复杂度极高
现代游戏包含:
- 云
- 光照
- 反射
- 毛发
- 大量角色
👉 元素多、交互复杂
2️⃣ 硬件限制
- 必须适配 GPU 架构
- 不同设备性能差异大
3️⃣ 帧率限制
在高分辨率下:
- 4K / 8K
- 每帧计算预算极其有限
4️⃣ CPU预算有限
CPU 不能全部用于渲染(通常 < 20%)
👉 必须把工作交给 GPU
🧠 渲染的基本流程(Pipeline)
从模型到屏幕:
顶点 → 三角形 → 投影 → 光栅化 → 像素 → 着色
详细流程
顶点(Vertex)
- 位置
- 法线
- UV
三角形组装(Triangle)
投影变换(Projection)
- 世界坐标 → 屏幕空间
光栅化(Rasterization)
- 转换为像素
片元处理(Fragment Shader)
- 计算颜色
🎨 纹理采样(Texture Sampling)
核心问题:走样(Aliasing)
当物体远离时,会出现:
- 锯齿
- 闪烁
解决方案:Mipmap + 插值
三步法:
- 选择两个最接近的 mipmap 层级
- 在每一层做双线性插值(Bilinear)
- 在两层之间做线性插值
⚡ 为什么需要 GPU?
CPU 不适合大规模并行计算
🧠 GPU 的核心特点
SIMD
Single Instruction Multiple Data
- 一条指令处理多个数据
- 适合矩阵/向量运算
SIMT
Single Instruction Multiple Threads
- SIMD + 多线程
- GPU 的核心执行模型
🖥️ GPU 结构(简化)
1 | GPU |
⚠️ CPU 与 GPU 通信成本
问题
- 数据传输开销大
- 同步困难
优化策略
- 尽量减少 CPU ↔ GPU 传输
- 尽量单向传输(CPU → GPU)
- 避免频繁同步
🧠 Cache 重要性
性能瓶颈往往在“数据访问”,而不是计算
关键概念
- Cache Hit:命中缓存(快)
- Cache Miss:未命中(慢)
优化原则
- 数据放在一起(连续内存)
- 顺序访问
- 批量处理
📦 渲染数据管理
🎯 Mesh Component
一个可渲染对象通常包含:
- Mesh(几何)
- Material(材质)
- Texture(纹理)
- Shader(着色器)
🧱 Mesh 数据结构
方法1(低效)
每个三角形存3个顶点
方法2(推荐)
顶点数组 + 索引数组
优点:
- 节省内存
- 提高缓存利用率
🎨 材质(Material)
常见模型:
- Phong
- PBR(Physically Based Rendering)
🧵 子网格(Submesh)
一个模型可以有多个材质:
1 | Mesh |
通过 offset 管理
🧠 Shader
Shader 本质是代码,但在引擎中作为资源管理
♻️ 资源优化
Resource Pool(资源池)
相同资源共享(实例化)
优点:
- 节省内存
- 减少重复加载
🎯 Batch Rendering(批处理)
按材质排序:
减少 GPU 状态切换
👉 极大提升性能
🗜️ 纹理压缩
为什么需要?
- 纹理占用内存极大
- 带宽有限
特点
- 支持随机访问
- 基于块(Block-based)
常见算法
- DXTC
- ETC
- BC7
👁️ 可见性裁剪(Culling)
不可见的物体,不要渲染
🧱 方法1:视锥裁剪(Frustum Culling)
判断物体是否在摄像机视野内
📦 包围体(Bounding Volume)
常见:
- Sphere
- AABB
- OBB
- Convex Hull
🌲 空间结构
- 四叉树(Quadtree)
- BVH
🏠 PVS(预计算可见性)
只渲染当前房间可见的内容
⚡ GPU裁剪
Early-Z
- 提前进行深度测试
- 不绘制被遮挡物体
🧰 建模工具
常见方式
- 多边形建模(Blender)
- 雕刻(ZBrush)
- 扫描(Scan)
- 程序生成(Procedural Modeling)
🚀 现代渲染管线
问题
- 场景越来越大
- 模型越来越复杂
新技术方向
Cluster-Based Mesh Pipeline
- 对模型进行分簇处理
Programmable Mesh Pipeline
- GPU 动态生成细节
Nanite(UE5)
- 自动 LOD
- 超大规模几何
🎯 核心思想
让计算机尽量“少做事”(Do Nothing)
📌 本节核心总结
1️⃣ 渲染是性能核心
决定画面与帧率
2️⃣ GPU 是主力
CPU 负责调度,GPU 负责计算
3️⃣ 数据结构决定性能
- 索引缓冲
- 批处理
- Cache 友好
4️⃣ 减少无效计算
- Culling
- Early-Z
- PVS
5️⃣ 资源管理非常关键
- Resource Pool
- Texture Compression
6️⃣ 现代趋势:更自动化
- Nanite
- 程序化生成
🧠 快速复习(面试版)
渲染的基本流程?
顶点 → 三角形 → 光栅化 → 像素 → 着色
为什么用 GPU?
适合大规模并行计算
SIMD vs SIMT?
- SIMD:单指令多数据
- SIMT:多线程并行执行
如何优化渲染性能?
- Batch Rendering
- 减少状态切换
- Cache优化
- Culling
什么是 Early-Z?
提前深度测试,避免无效绘制
为什么要纹理压缩?
节省内存和带宽
Nanite 是什么?
一种自动LOD的虚拟几何技术