← 全部标签
# GEA
74 篇页面
页面
图形学GPGPU 编程入门Introduction to GPGPU ProgrammingGEA 把 GPGPU 讲成「SIMD 向量化推到极致」:GPU 结合 SIMD(向量化 ALU)与 MIMD(一种抢占式多线程),即 NVIDIA 所说的 SIMT。适合 GPU 的任务必须是数据并行的——每个元素的计算不依赖其他元素的结果。写法与 CPU 向量化相反:不把循环硬编码成每次 4/8/16 个,而是保留单通道的循环体,抽成计算核函数 (compute kernel),由 GPU 替我们「跑循环」。一个「GPU 线程」其实就是一条 SIMD 通道。图形学图形 API 设备、管线状态与资源Graphics API Devices, Pipeline State & Resources所有图形 API(OpenGL、DirectX、Vulkan、Metal)都围绕同一组概念:代表 GPU 的设备、描述各阶段配置的管线状态、承载数据的资源及其视图。GEA 把 API 分成高层(OpenGL、D3D11 及更早)和底层(D3D12、Vulkan、Metal、PlayStation 的 Gnm),并介绍 D3D11 的特性级别、设备上下文与延迟上下文、D3D12 的命令列表、PSO 和根签名。本页按 GEA 的整理讲这些跨 API 的共同概念,不是任何一个 API 的教程。图形学图形栈与渲染引擎架构Graphics Stack & Rendering Engine Architecture渲染引擎要解决的"渲染问题"是:给定由场景元素、虚拟光源和虚拟相机组成的虚拟世界,生成相机看到的二维彩色图像。GEA 把它拆成可见性问题和光照问题,按解法把引擎分为光线追踪、深度缓冲光栅化、辐射度和混合四类;光栅化引擎通常采用立即模式的两层结构:下层是图形管线,上层是负责剔除、提交几何和设置着色器参数的应用层。本页讲这套整体架构,管线内部见 渲染管线。图形学渲染开发与 GPU 调试工具Rendering Development & GPU Debugging Tools渲染引擎的运行时部分只是拼图的一块,还需要一批离线工具来制作内容、优化性能、诊断问题。GEA 介绍了四类:数字内容创作 (DCC) 工具、材质编辑器、资产调理管线,以及 PIX、Razor GPU 这样的 GPU 帧捕获、检查和回放工具(GEA4 卷 II 12.9)。图形学着色方程与经典光照模型Shading Equation & Classic Lighting ModelsGEA 把渲染方程在两条简化假设下化成的求和式称为着色方程:只有理想化的点光源和方向光,间接光只用一个环境光项粗略近似。配上 Lambert、Phong、Blinn-Phong 这类简单 BRDF,它给不出照片级真实感,但能产生可信的画面,是早期游戏的全部光照,也是理解 BRDF 用法的最好入门(GEA4 卷 II 12.4、12.5.1)。图形学硬件光线追踪管线Hardware Ray Tracing Pipeline高端 GPU 提供硬件加速的光线追踪,它像 GPU 中独立于光栅管线和计算管线的另一条管线,但能与它们协调同步。场景几何要先组织成两层加速结构(TLAS 实例层与 BLAS 几何层),光线发射后由 any-hit、closest-hit、miss 等着色器处理结果。由于还不够快,GPU 光追通常与光栅化结合成混合渲染:先光栅化 G-buffer,再对镜面、反射和间接光发射光线。图形学网格着色器管线Mesh Shader Pipeline网格着色器管线(也叫网格几何管线)是 GPU 厂商为取代传统光栅管线前段而推出的新架构:光栅化器和像素着色器不变,网格着色器和可选的放大着色器(任务着色器)接管顶点、外壳、域和几何着色器的全部工作。它以类似计算着色器的线程组处理叫 meshlet 的小批图元,像启动计算着色器一样启动绘制,还能把部分应用层工作搬到 GPU 上。2019 年进入 DirectX 12,2022 年以 VKEXTmeshshader 扩展进入 Vulkan。图形学蒙特卡洛积分与重要性采样Monte Carlo Integration & Importance Sampling渲染方程里的半球积分没有闭式解,只能数值积分。蒙特卡洛积分在定义域里随机取样、按概率密度加权求平均,期望值正好等于积分真值,误差随样本数 N 按 1/\sqrt{N} 下降,而且与积分维数无关,所以成了光线追踪类渲染的数学基础。降低误差的主要手段是让采样分布接近被积函数的形状,也就是重要性采样(GEA4 卷 II 12.6.2、12.6.4)。图形学覆盖层与用户界面渲染Overlays & User Interface RenderingHUD、武器选择界面、各种菜单等图形用户界面,加上调试绘制,都在主 3D 场景渲染完之后才画,所以统称覆盖层 (overlay)。它们一般关闭深度测试、用屏幕空间四边形和正交投影绘制,由文字、位图、嵌入视频和矢量图形几类二维图元组成(GEA4 卷 II 12.8)。图形学随机光线追踪与路径追踪Stochastic Ray Tracing & Path Tracing光线追踪用投射光线同时解决可见性问题和光照问题。GEA 从最简单的"眼睛光线 + 着色方程"讲起,逐步加入阴影光线、透射光线、用蒙特卡洛估计渲染方程的随机光线追踪、递归间接光照,再到每次只走一条路径的路径追踪、双向路径追踪和 Metropolis 光传输,最后讨论降噪与光线投射效率(GEA4 卷 II 12.6.3–12.6.5)。游戏开发C++ 内存布局与对齐C++ Memory Layout & Alignment理解 C/C++ 程序在内存中的样子,是写高性能引擎代码的前提。主要内容包括:声明与定义、内部与外部链接;可执行映像的几个段(text、data、BSS、rodata)以及栈和堆;结构体成员的自然对齐和填充;继承和虚函数如何改变类的布局(vpointer 和 vtable)。GEA 也简要回顾了面向对象:多重继承容易产生致命菱形,所以通常只允许 mix-in;组合往往比继承更好。游戏开发SIMD 向量化SIMD VectorizationSIMD(单指令多数据)用一条机器指令并行处理多个数据。x86 上从 MMX 发展到 SSE(128 位,4 个 float)、AVX(256 位,8 个)和 AVX-512(512 位,16 个)。GEA 用 SSE 内建函数演示:数组相加、点积、向量×矩阵、矩阵×矩阵,强调两点——避免寄存器内的水平相加(先转置数据),以及把 SIMD 寄存器看成 N 条并行的「通道 (lane)」而不只是一个 4D 向量;条件分支则用向量谓词化 (vector predication / select) 代替。游戏开发三维音频渲染3D Audio Rendering3D 音频渲染引擎要尽可能可信地重现"玩家若真在游戏世界里会听到什么"。它的输入是世界里大量的 3D 声音,输出是少数几路扬声器声道;主要任务是合成、空间化(距离衰减 + 声像定位)、声学建模(混响、遮挡、衍射)、多普勒和混音。GEA 给出了恒功率声像律、聚焦角、混响区域交叉混合、遮挡/阻挡/排除的分析方法,以及顽皮狗《最后生还者》的随机传播建模和声音门户(GEA4 卷 II 15.4)。游戏开发世界块数据格式与生成器World Chunk Data Formats & Spawners世界块中的动态部分要为每个游戏对象记录两样东西:属性初始值和对象类型。GEA 比较了三种存储方式:二进制对象映像(生成最简单,但要处理指针、虚表和字节序,且对变化很脆弱)、序列化描述(XML/JSON/私有二进制格式,配合反射与类工厂),以及生成器 (spawner) + 类型模式 (type schema):生成器是只含类型 id 和键值对的轻量数据块,彻底切断世界编辑器与运行时代码的耦合;模式里的默认值配合"与默认值相同就省略键"可以让默认值的修改自动传播到旧实例(GEA4 卷 II 17.3)。游戏开发事件与消息传递Events & Message Passing游戏天然是事件驱动的:爆炸、玩家被敌人发现、血包被捡起都是事件,系统要通知感兴趣的对象并让它们以各自的方式处理。用虚函数 OnExplosion() 通知属于静态类型的后期绑定,迫使基类为所有可能的事件声明虚函数;GEA 主张把事件封装成对象(类型 + 参数),实现动态类型的后期绑定,从而获得单一处理函数、持久性和盲转发。GEA 依次讨论事件类型(枚举 vs 字符串/字符串 id)、参数(派生类、Variant 数组、键值对)、处理器、职责链、兴趣注册、排队与否的利弊,最后是数据驱动的事件系统和以数据通路连接端口的图形化系统(如 UE 蓝图)(GEA4 卷 II 17.8)。游戏开发人机接口设备Human Interface Devices人机接口设备 (HID) 是玩家向游戏提供输入、游戏向玩家提供反馈的硬件:手柄、键鼠、摇杆、Wii 遥控器、方向盘、跳舞毯、吉他等。GEA 从三个层面介绍它们:游戏如何与设备通信(轮询、中断、无线协议),设备提供哪几类输入,以及设备能输出哪些反馈。引擎如何加工这些原始输入见 引擎输入系统。(卷 I 9.1–9.4 节)游戏开发作业系统Job System多核硬件上,游戏循环的工作要分解成可并行执行的子任务。GEA 依次比较「每子系统一个线程」「分散/汇集 (scatter/gather)」和通用的作业系统 (job system),并给出典型作业 API、基于线程池的简单实现及其局限、用协程或纤程让作业可以睡眠、作业计数器、作业同步原语与可视化剖析工具,最后介绍 Naughty Dog 基于纤程的作业系统。(卷 I 8.6 节)游戏开发刚体动力学Rigid Body Dynamics游戏物理引擎关心的是动力学:力如何影响运动。它做了两个简化:经典(牛顿)力学(无量子、无相对论效应)和刚体(形状不变)。无约束刚体有 6 个自由度,其运动可以分成互相独立的线性动力学(质心像质点一样平动)和角动力学(绕质心转动)。线性部分是 F=ma 的常微分方程;三维角动力学则以角动量为主变量,并用一个四元数形式的微分方程更新朝向(GEA4 卷 II 14.4)。游戏开发动画压缩Animation Compression未压缩的动画每关节每样本最多 10 个 32 位浮点通道,数据量巨大。GEA 列出的压缩手段按力度递增:省略无用通道、量化(如 16 位 RL 编码)、降低采样率或省略近似线性的关键帧、用 B 样条拟合曲线、小波压缩,以及最根本的:只把需要的片段放进内存(选择性加载与流式加载)(GEA4 卷 II 13.8)。游戏开发动画混合Animation Blending动画混合把两个或多个输入姿势合成一个输出姿势。基本工具是对每个关节的局部 SRT 分别做 lerp(旋转用 nlerp 或 slerp);在此之上有时间插值、交叉淡入淡出、方向移动、一维/二维混合空间、部分骨骼混合和叠加混合(差分片段)。前几种都是加权平均,叠加混合则是"加上一段变化"(GEA4 卷 II 13.6)。游戏开发动画管线与动作状态机Animation Pipeline & Action State Machines低层动画管线把片段和混合规格变成局部姿势、全局姿势和蒙皮矩阵调色板;其上的动作状态机 (ASM) 给游戏代码提供"按状态驱动动画"的接口。每个状态是任意复杂的片段混合,跟踪当前所有片段的方式有扁平加权平均和混合树两种;过渡用数据驱动的过渡矩阵描述,控制参数通过节点搜索、命名变量或控制结构体暴露给代码(GEA4 卷 II 13.9–13.10)。游戏开发动画约束与后处理Animation Constraints & Post-Processing片段混合之后、渲染之前,动画系统常常还要修改姿势,这叫动画后处理:程序化动画、逆向运动学 (IK)、布娃娃。另一大类需求是约束:把武器挂在手上(挂接点)、让多个角色对齐演出(参考定位器)、手抓物体(手部 IK)、脚不滑步(根运动提取与脚部 IK),以及注视、掩体、穿越辅助等(GEA4 卷 II 13.7、13.11)。游戏开发基于锁的并发问题Problems with Lock-Based Concurrency即使所有共享数据操作都用锁保护好了,基于锁的并发仍有四类问题:死锁 (Deadlock)、活锁 (Livelock)、饥饿 (Starvation)、优先级反转 (Priority Inversion)。哲学家就餐问题集中体现了它们。GEA 由此总结出经验法则:全局排序、事务式算法、尽量减少争用;并反对把所有接口都做成「100% 线程安全」,主张正视并发、少用锁。游戏开发声音的物理与感知Physics & Perception of Sound声音是在空气等可压缩介质中传播的压缩波,用声压随时间变化的信号 p(t) 描述。游戏音频要模拟的物理与感知现象包括:用分贝度量的响度、人耳对频率不均匀的灵敏度、随距离和大气吸收的衰减、干涉、早期反射与晚期混响、多普勒频移,以及人耳判断声源方位的线索(ITD、IID、HRTF)。GEA 把它们作为 3D 音频渲染的理论基础(GEA4 卷 II 15.1)。游戏开发子系统启动与关闭Subsystem Start-Up & Shut-Down游戏引擎由许多相互依赖的子系统组成,启动时必须按依赖顺序初始化(B 依赖 A,则先启动 A),关闭时通常反序。C++ 全局/静态对象的构造与析构顺序不可预测,「首次调用时构造」的函数静态单例也无法控制析构顺序且时机不可控。GEA 推荐最朴素的方案:单例管理器的构造/析构函数什么也不做,提供显式的 startUp() / shutDown(),在 main() 里按顺序手动调用。游戏开发字符串与唯一标识符Strings & Unique Identifiers字符串在 C/C++ 中只是字符数组,变长意味着要么硬编码长度上限、要么动态分配;比较和复制都是 O(n)。引擎内部大量用字符串做资源名和对象 ID,处理不当会严重拖慢游戏。GEA 的建议:运行时代码尽量避免字符串类,若用则了解其成本并按引用传递;路径类是值得专门做的例外;对象与资产的唯一标识用哈希字符串 ID(string id),兼得字符串的可读性与整数的比较速度。游戏开发容器与哈希表Containers & Hash Tables容器的职责都是容纳和管理零个或多个元素,差别在于实现方式与取舍。GEA 概览了常见容器与操作、迭代器、大 O 复杂度与内存布局的综合考量,讨论引擎为何常自研容器、C++ 标准库 / Boost / Folly / Loki 的利弊,并重点讲了动态数组的增长策略与哈希表(开放哈希与封闭哈希、哈希函数、线性/二次探测、Robin Hood 哈希)。游戏开发并发与并行基础Concurrency & Parallelism FundamentalsGEA 区分两个正交的概念:并发 (Concurrency) 是软件层面多个控制流读写共享数据,核心难题是数据竞争;并行 (Parallelism) 是硬件层面两个以上部件同时工作。硬件并行又分为提升单线程速度的隐式并行(流水线、超标量、VLIW,即指令级并行 ILP)和为同时运行多条指令流而设计的显式并行(超线程、多核、多处理器、集群/云)。游戏开发引擎文件系统与异步文件读写Engine File System & Asynchronous File IO资源管理器大量依赖文件系统。引擎通常把操作系统的文件 API 包装成自己的 API:一是跨平台、屏蔽硬件差异;二是操作系统缺少引擎需要的功能(如流式加载),主机还要统一访问记忆棒、硬盘、蓝光盘、网络文件系统等介质。引擎文件系统 API 通常涵盖:文件名与路径操作、打开/关闭/读写文件、扫描目录、处理异步 I/O 请求(流式加载)。游戏开发引擎输入系统Game Engine HID System多数引擎不直接使用「原始」HID 输入,而是先加工(死区、滤波)使游戏内行为平滑直观,并在 HID 与游戏之间加至少一层间接(如按键映射表把原始按钮翻译成逻辑游戏动作)。GEA 列出引擎输入系统的典型需求并逐一讲解实现:死区、模拟信号滤波、按下/松开事件、组合键与序列手势、多设备多玩家、跨平台抽象、输入重映射、上下文相关控制与输入禁用。(卷 I 9.5–9.6 节)游戏开发引擎配置系统Engine Configuration游戏引擎总会积累大量可配置选项:有的通过选项菜单暴露给玩家(画质、音量、手柄设置),有的只供开发团队使用、发售前隐藏或去除(如玩家最大行走速度在开发期可调、发售前改成硬编码)。GEA 讨论选项的存取方式(文本文件、压缩二进制、注册表、命令行、环境变量、在线档案)、全局与每用户选项,以及 Quake cvar、OGRE、Naughty Dog 三个真实例子。游戏开发数字音频技术Digital Audio Technology游戏音频程序员需要了解从麦克风到扬声器的整条硬件链路:模拟端的换能器、扬声器布局、电平、放大器和音量控制;数字端的脉冲编码调制 (PCM) 采样、香农-奈奎斯特采样定理、位深与量化误差、各种音频格式与编解码器、多声道数据的并行与交错布局,以及数字接口。GEA 用它们说明音频引擎的输入输出到底是什么(GEA4 卷 II 15.3)。游戏开发数据驱动与游戏世界编辑器Data-Driven Engines & Game World Editors当游戏行为可以部分或全部由美术和策划提供的数据控制,而不只由程序员写的代码控制时,引擎就是数据驱动的。它能提高团队效率、缩短迭代时间,但工具、健壮的运行时和预览手段都要花大量成本,过度设计反而降低生产力,所以要权衡并坚持 KISS。玩法领域里对应 DCC 工具的是游戏世界编辑器:创建世界块并放入静态和动态元素、设定对象初始状态和行为。GEA 列出了它的典型功能,并比较了不同引擎在何时做平台相关优化上的取舍(GEA4 卷 II 16.3–16.4)。游戏开发无锁编程与内存序Lock-Free Programming & Memory Ordering「无锁 (lock-free)」的真正含义不是不用互斥锁,而是从不让线程阻塞(作者说叫 block-free 更贴切)。数据竞争有三个来源:关键操作被打断、编译器与 CPU 的指令重排、缓存一致性协议优化造成的内存序问题。对应的工具是原子指令(TAS、XCHG、CAS、LL/SC)和内存屏障/栅栏(acquire、release、full fence),C++11 用 std::atomic 与 std::memoryorder 把它们封装成可移植接口。在此基础上可以写出自旋锁、可重入锁、读写锁,以及基于「本地准备 + 一次原子提交」事务的无锁数据结构。游戏开发日志与调试绘制Logging & Debug Drawing几乎所有专业团队都会为引擎打造一套调试与开发工具。本页记录其中最基础的两类:日志与追踪(格式化调试输出、详细级别、通道、镜像到文件、崩溃报告)和调试绘制(在游戏世界或屏幕上画线、形状和文字以可视化数学计算)。(卷 I 第 10 章 引言、10.1–10.2 节)游戏开发构建配置与编译优化Build Configurations & Compiler OptimizationC++ 游戏代码从源文件到可执行文件要经过预处理、编译、链接。游戏项目通常不止 Debug/Release 两种构建配置 (build configuration),而是 Debug、Development、Ship,有时再加混合构建;配置越多测试越难。编译器优化分为局部优化和全局优化(链接时优化 LTO、剖析引导优化 PGO),它们让程序更快,但也让调试器“说谎”。所以要学会调试优化过的构建,并用统计式或插桩式性能分析器 (profiler) 找出最耗时的代码。游戏开发游戏 AI 决策管线Game AI Sense-Decide-Act Pipeline游戏 AI(与生成式 DNN 不同)通常按 感知 → 决策 → 执行 分层:底层是寻路、感知与记忆;中层是角色控制(对接动画/物理);上层是目标与决策(行为树、效用、规划等)。GEA 强调「perception is reality」——玩家看不见的内部聪明不算数。游戏开发游戏世界与游戏对象Game World & Game Objects游戏由玩法 (gameplay) 而不是技术定义;游戏机制 (game mechanics) 是支配游戏中各实体交互的规则,以及目标、成败标准、玩家能力、非玩家实体和整体流程(GEA 引用的论文称实现玩法的软件系统为游戏的 G 因子)。大多数游戏世界由静态元素和动态元素组成,划分为若干世界块,并由高层游戏流程组织玩家目标;动态元素通常实现为游戏对象 (GO),引擎提供的建模设施称为游戏对象模型,且分为工具侧和运行时两套(GEA4 卷 II 16、16.1–16.2)。游戏开发游戏世界的加载与流送Loading & Streaming Game Worlds世界加载系统负责两件事:把世界块和所需资源从磁盘读进内存的文件 I/O,以及这些资源的内存分配与释放;另外还要管理游戏对象的生成与销毁。GEA 按演进顺序介绍:一次只载一个关卡的栈式加载(LSR 资源压栈底),用小块气闸 (air lock) 区域实现基本无缝的过渡,再到把所有资产切成等大块、用块池分配器按关卡加载区域流送的细粒度方案(Naughty Dog);对象生成的内存问题有"不许运行时分配"、按尺寸分池、小内存分配器和内存重定位四种对策;存档则分检查点和随处存档(GEA4 卷 II 17.4)。游戏开发游戏中的物理与中间件Physics in Games & Middleware今天游戏引擎所说的"物理",准确说是经典刚体动力学模拟:理想化的、无限坚硬不可形变的物体在力的作用下如何运动和相互作用。它高度依赖碰撞检测系统;反过来,碰撞检测可以独立存在,几乎所有有移动物体的游戏都有某种碰撞检测,但不一定有物理。GEA 先讨论要不要物理、物理是否好玩、它对项目各部门的影响,再列出主流中间件(GEA4 卷 II 第 14 章引言、14.1–14.2)。游戏开发游戏内性能与内存剖析In-Game Profiling & Memory Tracking游戏是实时系统,要保持 30 或 60 FPS,同时受目标硬件内存限制。第三方剖析工具有局限、在主机上未必可用,所以许多引擎自带游戏内剖析器(手动插桩的代码块计时、层级关系、调用次数、导出 CSV)和内存统计与泄漏检测工具。GEA 讲解它们的实现要点和常见限制。(卷 I 10.8–10.9 节)游戏开发游戏内调试工具In-Game Debugging Tools各子系统(渲染、动画、碰撞、物理、音频、网络、玩家机制、AI……)都有大量配置项,能在游戏运行时调整而无需改代码、重新编译链接再运行,可大幅节省调试和搭建关卡/玩法的时间。GEA 介绍支撑这一点的几类游戏内工具:游戏内菜单(及 Dear ImGui 等第三方 GUI)、游戏内控制台、调试摄像机与暂停/慢动作、作弊功能、截图与录像。(卷 I 10.3–10.7 节)游戏开发游戏对象并发更新Concurrent Game Object Updates游戏对象模型要和几乎必然是多线程的底层子系统打交道,所以对象代码必须异步思考:耗时操作用非阻塞请求尽早发起、尽晚取结果,必要时容忍一帧延迟。作业之间的依赖决定了并行度 (DOP),每个依赖都引入同步点,"优化并发设计的秘诀是延迟"。对象模型本身也能并行:Naughty Dog 在把引擎从 PS3 移植到 PS4(《最后生还者 重制版》)时,让每个桶内的对象更新成为作业;桶间访问无需加锁,桶内读取用对象快照,桶内修改尽量避免,或加锁、或放进请求队列、或交给下一桶中的同步作业(GEA4 卷 II 17.7)。游戏开发游戏对象引用与查询Game Object References & Queries游戏对象需要唯一 id 来区分、查找和充当通信目标;找到对象后还要能引用它,引用的寿命可以从一次函数调用到许多分钟。GEA 比较三种实现:裸指针最快最简单,但有孤儿对象、悬垂指针、非法指针三类问题,且不能配合内存重定位;智能指针可以检测对象已删除、做引用计数,但很难写对,GEA 个人建议远离它,非用不可就用 C++11 标准库或 Boost 的成熟实现;句柄是全局句柄表的整数索引,加上唯一 id 防止槽位复用后指向错误对象,简单而安全。对象查询则不追求通用数据库,而是为最常用的查询建专门的数据结构(GEA4 卷 II 17.5)。游戏开发游戏对象的实时更新Updating Game Objects in Real Time游戏对象更新就是根据上一时刻状态 Si(t − Δt) 求出当前状态 Si(t)。"每帧对每个对象调一次虚函数 Update(),并在里面顺带更新它的动画、物理、渲染组件"的简单做法在商业引擎里行不通:底层子系统需要批量更新才能高效,而对象之间、子系统之间又有依赖顺序。GEA 介绍的对策依次是:子系统由游戏循环直接批量更新、对象只操纵子系统中的"代理状态";在帧内多个阶段挂回调;按依赖森林把对象分桶,逐桶运行完整的分阶段更新(Naughty Dog 的做法);并用状态缓存和时间戳对付更新过程中的状态不一致和一帧延迟(GEA4 卷 II 17.6)。游戏开发游戏时间线与时钟Game Timelines & Clocks游戏引擎要同时处理多种「时间」:真实时间、游戏时间、动画片段的局部时间、CPU 周期等。GEA 用抽象时间线 (abstract timeline) 统一看待它们,并讨论帧时间 \Delta t 的测量方法、高精度计时器、时间单位与时钟变量类型的取舍,以及断点恢复后的异常 \Delta t 处理。(卷 I 第 8 章 引言、8.4–8.5 节)游戏开发游戏本地化与 UnicodeGame Localization & Unicode本地化(国际化,I18N)是把软件改编为其他语言版本,最好从第一天起规划并贯穿每个开发步骤。GEA 介绍了 Unicode 与 UTF-32/UTF-8/UTF-16/UCS-2 编码、char 与 wchart、Windows 的字符串 API 约定、主机上的选择,字符串之外的本地化问题,以及核心基础设施——字符串数据库 + 按 ID 查找当前语言字符串的运行时函数,并以 Naughty Dog 的本地化工具为例。游戏开发游戏脚本系统Game Scripting脚本语言是主要用来让用户控制和定制软件行为的编程语言;在游戏引擎中,它是高层、相对易用、能方便访问引擎大多数常用功能的语言,程序员和非程序员都能用它开发新游戏或给现有游戏做 mod。GEA 先区分数据定义语言和运行时脚本语言,梳理语言分类与游戏脚本的典型特征(解释执行、轻量、快速迭代、方便),介绍 QuakeC、UnrealScript、Lua、Python、Pawn,列出从"脚本回调"到"脚本驱动整个游戏"的六种架构,再以 Naughty Dog 的 DCScript 为例讲虚拟机与原生函数接口,最后是对象引用、事件收发、面向对象脚本、脚本状态机和协作式多线程脚本(GEA4 卷 II 17.游戏开发游戏音频功能与角色对话Game-Specific Audio Features & Character Dialog在 3D 音频渲染管线之上,游戏还要实现分屏、物理驱动音频、动态音乐、角色对话、声音合成和人群声音等专门功能。GEA 重点讲了分屏的多听者问题、顽皮狗《最后生还者》的角色对话系统(逻辑台词、随机不重复、优先级与打断、把对话作为一等实体、排他性、基于规则和事实字典的分支对话、按区域标签的情境台词、对话手势)以及音乐系统(播放列表、紧张度、插入乐、过渡)(GEA4 卷 II 15.6)。游戏开发物理引擎与游戏的集成Integrating a Physics Engine into a Game碰撞/物理世界里的刚体只是抽象的数学描述,要通过游戏对象这个中枢,间接连到屏幕上的视觉表现。刚体按谁驱动分三类:物理驱动、游戏驱动(Havok 称 key framed)、固定;Havok 用可在运行时切换的"运动类型"统一表示。每帧更新有固定顺序:先更新游戏驱动刚体、幻影、力、冲量和约束,再步进模拟,然后回读物理驱动对象、查询幻影、执行投射查询。物理宜用恒定的 1/30 或 1/60 秒时间步。GEA 还逐一讨论了子弹、手雷、爆炸、可破坏物、角色移动、相机碰撞和布娃娃的实际做法(GEA4 卷 II 14.5–14.6)。游戏开发碰撞响应与约束求解Collision Response & Constraint Solving刚体碰撞后,模拟必须让它们做出逼真的反应,并保证一步结束时不再互相穿透,这就是碰撞响应。主流方法有三种:基于牛顿恢复定律的冲量法、像硬阻尼弹簧一样的惩罚力、以及把碰撞当作约束交给通用约束求解器。此外还要处理摩擦、多边形汤上的伪接触(焊接),以及让物体安静下来(休眠与模拟岛)。约束(点对点、铰链、棱柱、布娃娃、动力约束等)限制刚体的自由度;每帧的物理步骤是"积分 → 碰撞检测 → 碰撞解决 → 约束求解"的迭代误差最小化(GEA4 卷 II 14.4.7–14.4.10)。游戏开发碰撞查询Collision Queries碰撞系统除了检测真实物体的接触,还要回答假设性问题:子弹从枪口朝某方向飞,先打中什么?车能否从 A 开到 B 不碰到东西?某半径内有哪些敌人?这类操作称为碰撞查询。最常见的是投射 (cast),也叫 trace 或 probe:沿射线或线段移动一个假想物体,看它会碰到什么;被投射的东西并不真正进入碰撞世界,不会影响其他物体(GEA4 卷 II 14.3.7)。游戏开发碰撞检测系统Collision Detection System碰撞检测系统判断游戏世界里的物体是否接触,本质上是一个"高级几何相交测试器",同时返回接触的详细信息。每个可碰撞对象由形状 + 变换描述,全部存放在独立的碰撞世界里;形状尽量用球、胶囊、盒、凸体等简单图元。为了在大量物体中快速找到真正相交的对,引擎使用时间相干性、空间划分,以及宽相、中相、窄相三级筛选(GEA4 卷 II 14.3)。游戏开发线程同步原语Thread Synchronization Primitives并发编程的中心问题是数据竞争 (Data Race):多个控制流交错地读改写同一共享对象。解决思路是让同一对象上的关键操作具备原子性(看起来瞬间完成,从而被串行化)。最简单可靠的工具是操作系统提供的线程同步原语:互斥锁、临界区、条件变量、信号量、Windows 事件。它们好用但昂贵(需要内核调用,上下文切换可超过 1000 个时钟周期)。游戏开发自定义内存分配器Custom Memory Allocators内存从两方面影响性能:动态分配(malloc / 全局 new)很慢,以及访问模式主导现代 CPU 的性能。GEA 的经验法则是「尽量少做堆分配,绝不在紧密循环里做堆分配」,并介绍游戏引擎常用的自定义分配器:栈分配器(含双端栈)、池分配器、对齐分配、单帧与双缓冲分配器;以及如何用栈/池避免碎片,或用碎片整理加指针重定位(智能指针、句柄)解决碎片。游戏开发计算机硬件与内存层次Computer Hardware & Memory Hierarchy从冯·诺依曼结构讲起,依次是 CPU 的组成(ALU、VPU、寄存器、控制单元、时钟)、总线与字长、机器语言、汇编和寻址方式,再到内存体系:内存映射、虚拟内存和 TLB,以及为了缩小“内存鸿沟”而设计的寄存器文件、多级缓存(缓存行、标签、组相联、替换和写策略、一致性协议)和 NUMA(PS3 的 SPU 本地存储、PS2 的 scratchpad)。对游戏程序员最重要的结论是:数据要连续、紧凑、顺序访问;热循环的代码要小。游戏开发资产管线与工具链Asset Conditioning Pipeline & Tools游戏引擎的输入数据来自美术使用的数字内容创作 (DCC) 工具,但 DCC 格式几乎从不直接用于游戏,必须经过资产调理管线 (Asset Conditioning Pipeline, ACP) 导出、处理并按平台打包。围绕它还有世界编辑器、记录资产元数据的资源数据库,以及几种工具架构(独立工具、与引擎共享底层、编辑器内嵌于引擎、Web 界面),各有取舍。游戏开发资源交叉引用与加载后初始化Resource Cross-References & Post-Load Initialization资源之间以任意方式相互引用(网格引用材质,材质引用纹理),整个资源数据库是一张有向依赖图。内存中的指针离开运行中的程序就失去意义,所以存盘时要把引用改写为 GUID 或文件内偏移,加载后再修正 (fix up) 回指针;含非 POD 的 C++ 对象还要用 placement new 调用构造函数;跨文件的外部引用要先加载全部相关文件。加载后通常还需要按资源类型做加载后初始化(以及释放前的拆除)。游戏开发资源管理器Resource Manager游戏由网格、材质、纹理、着色器、动画、音频、关卡布局、碰撞与物理参数等大量资源(也叫资产、媒体)构成。内存有限,所以引擎要保证每个资源在内存中只有一份(五个网格共用一张纹理时只载入一份)。每个引擎都有某种资源管理器:有的是统一集中的子系统,有的散布在多年间由不同人写的各个子系统中,但职责和要解决的问题是一样的。本页讲运行时部分:职责、文件组织与归档、文件格式、资源 GUID、资源注册表、生命周期(引用计数)和资源的内存管理;交叉引用与加载后初始化见 资源交叉引用与加载后初始化,离线工具链见 资产管线与工具链。游戏开发运行时对象模型架构Runtime Object Model Architectures玩法基础系统 (gameplay foundation system) 是位于"引擎与游戏分界线"附近、供构建游戏规则和动态元素的一组运行时组件,其中最复杂的是运行时对象模型:它在运行时实现世界编辑器展示给策划的抽象对象模型。GEA 把架构分为两大类:以对象为中心(每个工具侧对象对应一个或一小组类实例)和以属性为中心(对象只是一个 id,属性分散在按属性类型划分、以 id 为键的表里)。前者从单体类层次走向组合与组件,后者更省内存、更缓存友好、更易数据驱动,但难以表达属性间关系、也更难调试(GEA4 卷 II 17.1–17.2)。游戏开发运行时引擎分层架构Runtime Engine ArchitectureGregory 把游戏视为“软实时、交互式、基于代理的计算机模拟”,把游戏引擎定义为数据驱动、可复用来做多款游戏的软件。运行时引擎按层组织:从目标硬件、驱动、操作系统、第三方 SDK、平台无关层、核心系统、资源管理器,到渲染、物理、动画、音频、网络等子系统,再到游戏性基础层和游戏专属子系统;上层依赖下层,避免循环依赖。游戏开发进程、线程与纤程Processes, Threads & Fibers并发编程依赖操作系统提供的服务:内核态与用户态、中断、系统调用、抢占式多任务。进程只是为线程提供运行环境(虚拟地址空间与资源),线程才是操作系统的基本执行单位(调用栈 + 寄存器)。线程由内核抢占调度;纤程 (Fiber) 由程序协作调度,适合实现引擎任务系统;用户级线程与协程完全在用户空间切换上下文,更轻量。游戏开发错误处理与断言Error Handling & AssertionsGEA 把错误分为用户错误和程序员错误,两者处理方式相反:用户错误要尽量平稳地告知并继续,程序员错误应立即停机并给出详细的底层信息。“用户”又分玩家和制作游戏的开发者。实现手段有错误返回码、异常和断言。Naughty Dog、EA、Midway、Insomniac 等在运行时代码中通常关闭 C++ 异常,靠断言和静态断言 (staticassert) 捕获 bug,并用 RAII(Naughty Dog 称为 janitor)管理资源。游戏开发音频引擎架构Audio Engine Architecture音频渲染软件和多数系统一样是分层的"栈":硬件 → 驱动 → 底层 API → 3D 音频引擎,功能集通常模仿录音棚的多通道调音台。每个 2D/3D 声音是一条语音 (voice),沿语音总线经过编解码、增益、预发送滤波、辅助发送分出干湿两路、混响、后发送滤波和声像,然后由主混音器合成多声道信号,再经主输出总线(预放、LFE 生成、EQ、压缩、主音量)送到扬声器。资产上分片段、音效提示 (cue)、声音库和流式声音;混音靠分组、闪避、总线预设与混音快照、实例限制和语音抢占(GEA4 卷 II 15.5)。游戏开发骨骼、姿势与动画片段Skeletons, Poses & Animation Clips角色动画系统的数据核心是三样东西:骨骼(关节层级)、姿势(每个关节的 SRT 变换)和动画片段(姿势随局部时间变化的采样)。GEA 把各种动画方法看成对"逐点控制表面"这一理想的不同压缩方式;骨骼蒙皮用少量关节驱动大量顶点,是今天最主流的方案(GEA4 卷 II 13.1–13.4)。游戏数学伪随机数生成Pseudorandom Number Generation游戏引擎到处要用随机数,但随机数生成器 (RNG) 并不真正随机:它只是给出一个复杂但完全确定、预先定义的序列,因此严格说应叫伪随机数生成器 (PRNG)。好坏取决于周期(多久重复)和能否通过各种公认的随机性测试。GEA 介绍了线性同余、梅森旋转、Marsaglia 的 Mother-of-All / Xorshift / KISS99,以及 PCG。游戏数学信号与线性时不变系统Signals & LTI Systems信号与系统理论是几乎所有数字音频技术的数学基础,也用于图像处理、电子、流体等领域。核心结论是:线性时不变 (LTI) 系统完全由它的冲激响应 h 描述,任意输入的输出等于输入与 h 的卷积;借助傅里叶变换,时域卷积等于频域乘法,滤波器就是在频域里按频率响应 H(\omega) 衰减部分频率(GEA4 卷 II 15.2)。游戏数学几何图元的表示Geometric Primitive Representations除点、向量、矩阵、四元数外,游戏引擎还天天用到直线/射线/线段、球、平面、AABB、OBB、视锥和凸多面体区域。本页按 GEA 整理它们在内存中如何表示以及最基本的「点在内部吗」测试;各种求交与重叠算法见 RTR4 来源的相交测试系列页面。游戏数学数值积分Numerical Integration游戏中的力是交互的、无法事先预测的,运动方程(常微分方程)几乎没有解析解,所以物理引擎采用数值积分:以大致恒定的时间步长 \Delta t 逐步推进,由当前时刻 t1 的 \mathbf r(t1)、\mathbf v(t1) 和力函数 \mathbf F(t,\mathbf r,\mathbf v) 求出 t2=t1+\Delta t 时的位置与速度。衡量方法好坏看收敛性、阶、稳定性三项。显式欧拉最简单但误差大、不稳定;GEA 认为交互游戏中最常用的大概是 Verlet,尤其是速度 Verlet(GEA4 卷 II 14.4.4)。游戏数学数值表示与浮点数Numeric Representation & Floating Point游戏引擎里到处是数值,所以要清楚它们在机器中如何存储:二进制和十六进制、补码整数、定点数、IEEE-754 浮点数(量级与精度此消彼长、次正规数、机器 epsilon、ULP),以及多字节值的大小端问题和安全的类型双关 (type punning) 方法。书中一个直观的例子是:用 32 位浮点数记录以秒为单位的游戏时钟,大约 12.14 天后,加 1/30 秒就不再改变它的值。游戏数学旋转表示的比较Comparison of Rotational Representations旋转可以用欧拉角、3×3 矩阵、轴角、四元数表示,再加上平移与缩放就有 4×4(或 4×3)矩阵与 SRT(SQT)变换;更进一步还有对偶四元数。没有一种在所有场合都最好。GEA 按大小、直观性、能否直接作用于向量、能否插值、有无万向节锁比较它们,并用「参数个数 − 约束个数 = 3 个自由度」统一解释为什么它们用的浮点数不同。游戏数学点与向量运算Points & Vector Operations游戏程序员最常用的数学是三维向量与矩阵(三维线性代数)。本页按 GEA 第 5 章整理点与向量的基础:坐标系与手性、点与方向的区别、标量乘与 Hadamard 积、加减、模长与归一化、点积与投影、叉积、伪向量与外代数,以及线性插值 (lerp)。贯穿全章的两条经验是:能在二维里想清楚的问题先在二维里想,以及能不开方就不开方。综合引擎运行时架构总览Engine Runtime Architecture Overview本页把 wiki 中分散在 GEA 第 1、6、8、17 章及 GPP、UE5 相关页面里的内容串成一条线,回答"一个游戏引擎在运行时是怎样组织起来的":静态结构上是自下而上的分层,上层依赖下层;启动关闭按依赖顺序显式进行;每一帧由主循环驱动各子系统批量更新,游戏对象的逻辑被安排在子系统阶段之间,并按依赖分桶或设先后关系;多核上一帧被拆成成百上千个作业;玩法基础系统(对象模型、事件、脚本、世界加载流送、高层流程)位于引擎与游戏的分界线附近。各结论的细节与出处见所链接的概念页。综合游戏中的内存管理Memory Management in Games本页把 wiki 中关于内存的内容串成一条线:内存从两方面影响游戏,一是访问模式(缓存未命中远比计算昂贵),二是分配本身(通用堆分配慢且产生碎片,而主机上没有自动换页的虚拟内存兜底)。对策按生命周期组织:常驻与关卡数据用栈、每帧临时数据用单帧/双缓冲分配器、同尺寸小对象用池或小内存分配器、大小不一的动态对象用堆加碎片整理与句柄;资源按引用计数和分块加载管理,流送系统把世界切成等大的块;UE 则在 UObject 层用垃圾回收管理对象生命周期。最后,没有准确的追踪工具,以上一切都无从验证。各结论的细节与出处见所链接的概念页。