干游戏引擎这一行有个很有意思的现象很多刚入行的同学拿到大厂引擎源码或者项目工程时往往先扑向渲染代码、物理系统这些看得见摸得着的模块结果读不了几天就一头雾水——因为牵引这些表面的东西其实是背后那套看不见的底层架构而承载这套架构的恰恰又是团队的协作方式。这篇文章想聊的就是“游戏引擎架构 001”这个概念。它不完全是一堂代码课更像是我个人在引擎团队里的复盘。我会先从团队分工入手讲清楚人的组织边界怎么决定了引擎模块的边界再往下拆解内存管理、框架循环、渲染架构、资源生命周期这些底层架构的关键环节。如果你是想入行引擎岗位的新人、正在从业务逻辑转引擎方向的客户端同学或者打算自己从零攒一个轻量引擎的独立开发者这篇内容应该能帮你把脑子里零散的知识点串成一条线。1. 团队分工如何塑造引擎模块边界1.1 康威定律在引擎项目里的真实投影第一次听到“康威定律”是在一个老前辈的分享上他说设计系统的组织其产生的设计等同于组织内部的沟通结构。当时不以为意觉得这就是一句管理学的鸡汤。直到我真正在引擎团队里待过两年才明白这句话在游戏引擎的底层架构里简直是在每个角落被验证的。想想看引擎团队的常规配置是什么大致是客户端逻辑组、渲染组、工具链组、音频组、物理组、动画组再加一个公共基础组。有意思的是你去翻任何一款商业引擎的源码目录模块划分几乎和这个团队编制一模一样。客户端组写 GameObject、组件系统渲染组管 Renderer、Material工具组做编辑器面板和资源导入器。模块之间的接口设计本质上是团队之间的沟通协议。这个认知对架构设计有个实打实的影响如果你正在设计引擎划分模块的边界先别急着画类图先看看团队的沟通成本集中在哪。两个团队如果要频繁沟通那么他们的代码耦合一定会偏高这时候与其强行做抽象把共享代码抽出来反复横跳不如把他们负责的模块合并成一个拥有清晰对外接口的完整子系统。我在项目里见过最惨烈的例子是渲染团队和地形团队分得特别开结果地形数据的加载和网格提交逻辑被拆到了两个模块两边天天为该在哪个模块里保存贴图引用而吵架最后干脆让地形模块直接依赖渲染私有头文件。团队分工一旦不匹配技术边界架构立刻开始腐化。1.2 一线引擎团队的典型分工版图从团队分工来理解引擎底层架构会更直观。我按自己在实际项目中接触到的编制整理了一张典型引擎团队的模块分工表它基本映射了引擎各子系统的边界团队/小组负责模块对外提供的主要接口公共基础组内存分配、容器、字符串、数学库、断言与调试Core 静态库几乎所有模块直接依赖框架层引擎生命周期、模块注册、Update 调度Application / Engine 启动序列渲染组RHI 抽象、渲染线程、资源屏障、Shader 编译RenderScene、MeshBatch、PipelineState工具链组资源导入、序列化、编辑器窗口、Cook 流程AssetDB、Importer、Inspector 扩展玩法逻辑组GameObject、Component、事件系统、网络同步底层World、Entity、ComponentRegistry音频/物理/动画组各自运行时与外部 SDK 桥接各子系统透明的 Service 接口这张表看起来简单但里面藏着几个很关键的设计原则。第一个原则是依赖方向只能向内收敛公共基础组在最底层全局业务模块在最外围渲染组和逻辑组之间绝对不允许互相 include 对方的私有头文件只能通过公共抽象层交互。第二个原则是每个组负责的模块要具备独立测试和独立运行的边界尤其在多人配合时谁破坏了依赖闭环代码评审阶段就该被打回。从团队分工出发看底层架构还有个额外的好处代码的“拥有者”会变得非常清晰。一旦模块出现崩溃或性能瓶颈你能快速通过目录归属定位到对应的团队和负责人而不是整个项目所有人一起陷入排查混乱。这一点在后续诊断引擎问题时体会会越来越深。2. 底层架构的基石内存、容器与崩溃处理2.1 内存分配器为什么是引擎的“地基”很多人在开始接触引擎底层架构时会惊讶游戏引擎最核心的模块居然不是渲染而是内存管理。道理并不复杂商业游戏追求的是稳定帧率而内存分配恰恰是最容易引发性能毛刺的行为之一。一般情况下调用 malloc 只要频繁触发系统调用和堆锁竞争帧率就会出现肉眼可见的抖动。引擎里对帧率敏感的操作比如每帧创建新网格批次、粒子缓冲、动画骨骼矩阵都需要走引擎自己的内存池或栈式分配器。我自己较真过一次用简单基准测试对比了 malloc 和引擎内存池在多次小体积分配下的耗时差异。同样的 10 万次小于 64 字节的分配malloc 平均耗时比引擎内存池高出接近一个数量级而且 malloc 的延迟方差很大偶尔还会出现一次特别离谱的峰值。这正是做底层架构的人坚决不用裸 malloc 做高频分配的根本原因——稳定性问题比吞吐量问题更致命。实操角度看自己写内存分配器建议先做固定大小块的 free list 分配器。它的思路很简单预申请一块大内存按固定大小切成 N 个槽用空闲链表串起来分配时取链表头释放时把节点重新挂回链表。所有分配释放操作都是 O(1) 的指针移动没有锁竞争也不产生堆碎片。等这个基础版本跑通之后再去考虑不同尺寸分类的自由桶分配器、跨线程无锁分配器、以及带帧索引的栈分配器。后两者对并发和内存生命周期的理解要求显著高出几个级别盲目追求复杂分配器不是好事。2.2 自定义容器库背后的取舍逻辑一个合格引擎会在标准库之外维护自己的容器实现比如 TArray、TMap、TSet 这类命名看起来像旧式风格的自研容器。为什么不直接用 std::vector、std::unordered_map核心原因是可控性和内存布局调优。标准库为了提高通用性很多行为引擎团队无法干预比如 std::vector 的扩容策略在不同标准库实现下并不一致。引擎需要的是明确的扩容系数、明确的对齐方式、以及所有容器可以接入自定义分配器的能力。另外还有个工程现实SDK 和第三方库混编时不同模块的 CRT 版本不一致会导致跨 DLL 进行内存释放时直接崩溃。如果引擎容器强制使用自身内存分配器来管理其内部缓冲区就天然规避了这种跨模块释放问题。我自己比较推荐的容器约束习惯是凡是引擎内部数据一律使用引擎容器只在解释和序列化外部 SDK 数据时才在边界处转换到 std 容器。平时维护代码时还得注意一点TArray 的的容量初始化和缩容规则需要和业务场景匹配比如每帧往上追加元素的数组最好预分配避免反复扩容把堆挤成碎片。2.3 断言、调试宏与崩溃日志架构里的安全带底层架构里最容易让人忽视却又价值极高的部分是断言和调试基础设施。很多引擎空有庞大业务功能一旦真机崩溃就完全失控就是因为基础层没有建立规范的断言体系。断言和普通运行时错误检查最大的区别在于断言是开发者预期内的条件检查它应当只在 Debug/Development 配置下开启在 Release 版本里被完全编译移除避免对玩家构建产生额外分支判断开销。引擎里常见的做法是提供一组带格式输出的断言宏比如CHECK(condition)和CHECK_MSG(condition, format, args...)在构建平台差异层分别映射到 breakpoint 或平台调试输出。有一点经验值得强调不要在断言里写带有副作用的表达式比如CHECK(ptr GetPtr())因为 Release 下断言被移除复制操作也随之消失逻辑就会和 Debug 版本不一致。这种问题是底层架构里最隐晦也最容易造成线上事故的。崩溃处理还应配套捕获调用栈的实现。跨平台落地的话Windows 上可以用 CaptureStackBackTrace 抓栈并配合符号文件转成函数名移动端就稍微麻烦一点需要用信号处理器在 SIGABRT/SIGSEGV 时主动落日志。做引擎底层架构顺带把这些基础设施搭好业务层和上层模块排起错来会省力得多。很多新手只觉得崩溃了“重启一下就好”等发现问题根因在内存越界已经写坏邻接对象时才理解崩溃栈和内存调试接口有多贵。3. 引擎框架层生命周期、模块注册与主循环调度3.1 初始化序列为什么要设计成多阶段引擎启动时会执行一系列逻辑但真正底层架构的微妙之处在于初始化顺序的设计。PlayStation、Xbox 这种主机平台对内存分配阶段非常敏感而我们平时写 PC 项目也常常会遇到“这个模块依赖那个模块先启动”的循环依赖问题。所以引擎的初始化会拆成多个阶段PreInit 阶段完成基础日志、内存分配器、平台抽象层的最小环境搭建Init 阶段各模块正式申请资源并注册服务PostInit 阶段则处理模块之间的三级依赖比如渲染模块需要物理模块的碰撞结果来剔除而物理模块自己也需要渲染模块提供线框调试接口。拆成多阶段的根本原因是让模块间不再以任意顺序互相构造。只要约束每个阶段内模块彼此不直接依赖或者依赖关系在做注册时显式声明就能在启动期保证拓扑序稳定。我实际维护过的引擎采用过一个很朴素的注册表每个模块把自己需要的一批前置模块名单打到注册表里引擎启动时做一次拓扑排序把模块初始化顺序算出来有环就会启动失败并输出循环依赖的模块名单。这种方案的优点是直观、容错性强缺点是模块数量到 100 个以上时 Topo 顺序的人工理解开始困难但做底层架构的人一般还是会和这种注册表长期共存。3.2 主循环调度与 Tick 系统的数据一致性主循环是所有引擎活动的心脏。游戏引擎的标准循环是 while 不断运行三件事处理平台消息、更新游戏世界Tick/Update、渲染整帧。但这里有个普通业务逻辑开发者容易忽略的关键点引擎的 Tick 和渲染帧之间并不是严格的一对一关系。比较常见的做法是让“游戏线程Tick”和“渲染线程提交帧数据”异步运行。游戏线程在一个固定步长下推进比如固定 50Hz 或 60Hz有时甚至允许追赶渲染线程则尽量以当前显示器刷新率为目标比如 120Hz。这样设计的好处是当主逻辑产生尖峰时渲染线程还能按自己的节奏把上一帧数据渲染完避免整个游戏世界被物理计算拖垮。Tick 调度底层架构还需要处理一个关键的数据一致性问题渲染线程使用游戏世界里场景数据时游戏线程可能同时在更新。没有锁定机制直接访问就会产生严重的竞态。业界常用的方案是双缓冲场景描述游戏线程更新到一个后台副本完成后通过原子指针交换或者命令缓冲通知渲染线程切换读取。底层引擎通常还会加辅助约束渲染线程只读游戏线程发布的只读场景快照任何写操作都必须通过队列发到渲染线程执行。限定了这个规则后跨线程共享数据的安全边界就有了明确答案。4. 渲染架构线程模型、RHI 抽象与帧流程4.1 渲染线程与游戏线程如何协同渲染架构是引擎里最复杂的一块团队架构演化过程中它也是争夺最激烈的领域之一。早期引擎多半是单线程渲染游戏更新完立刻拿现成数据做 draw call直观且没什么并发问题。但到了高多边形场景、复杂光照烘焙、或者屏幕空间特效堆在一起时单线程渲染很快就会变成瓶颈。于是一代引擎从单线程渲染演进到“游戏线程 渲染线程”双线程并发底层架构就需要引入“帧延迟”的概念。在这种架构下游戏线程在帧 N 收集并提交渲染命令到命令缓冲渲染线程同时在消费上一帧帧 N-1的命令缓冲并执行 GPU 提交。所以看到的效果是渲染结果通常比游戏线程实际逻辑落后一帧。对绝大多数交互体验来说一帧延迟完全察觉不到但它确实存在。如果需要做帧同步类玩法比如快速操作与画面反馈需要严格对齐就得再配合延迟调整和输入采集时机来补偿。引擎里渲染命令的形态一般是“状态变化 绘制命令”的线性列表。游戏线程一侧构造 RenderCommand 并推入线程安全的队列渲染线程侧消费时逐条解析设置管线状态、上传常量缓冲、绑定顶点缓冲和索引缓冲最后提交绘制。这里的一个核心优化点就是减少状态切换次数纹理绑定和 Shader 切换这种重型状态变化一定要合并排序否则即便双线程渲染GPU 流水线也会频繁 stall性能爬不上去。4.2 RHI 抽象的层级设计写一次渲染代码跑通多个图形 API 是引擎底层架构里绕不开的课题尤其在 PC 平台同时覆盖 DX11、DX12、Vulkan 时RHI渲染硬件接口抽象层的设计就直接决定了引擎的适配成本。RHI 层的核心原则是“把 GPU 看得见的资源抽象成引擎可管理的资源对象把平台特性的差异压到最低层”。这里有两条路线。路线一是低抽象层路线RHI 尽量暴露接近 Vulkan/DX12 的原生语义比如显式屏障、显式命令队列、显式资源状态管理优点是性能和灵活性最好缺点是上层代码很容易写出平台相关的分支路线二是高抽象层路线引擎替用户管理资源状态和屏障用户在更高层只描述“这一帧我要做这些 pass”由引擎自动插入屏障和状态转换胜在易用和稳定代价是损失少量性能下限。就实际项目的经验来看架构初期更适合从高抽象 RHI 入手先把所有平台的通用绘制语义跑通资源创建、Shader 编译、Pipeline 构建、Draw 提交等你们对每个平台的行为差异有了充足数据再逐步往下渗透底层细节。千万不要在架构早期就拥抱 DX12 的精细同步机制那只会让全组人都被没完没了的屏障错误淹没。4.3 一个典型帧的数据流和屏障节奏拿一个典型的延迟渲染帧来串一遍你会更清楚引擎渲染架构实际在忙什么。首先游戏线程收集可见性结果生成一份包含网格、材质、光照标记的渲染场景描述提交到渲染线程。渲染线程拿到这帧数据后开始组织 GPU 命令深度预通道Depth Prepass写深度缓冲然后 G-Buffer 通道把位置、法线、反照率、粗糙度分别渲染到多张渲染目标随后是光照通道从光源列表对 G-Buffer 进行光照计算最后是天空盒、透明物体和后处理合成。这个过程里最考验底层架构功底的就是资源屏障管理。GPU 的缓冲和纹理在不同 Pass 之间会有读写冲突比如在 G-Buffer 写入法线下一阶段光照采样法线就必须在两者之间插入屏障确保上一操作完成且资源状态从 RenderTarget 切换到 ShaderRead。引擎架构里这一块通常用 RenderGraph 来规划整帧的资源依赖自动排序 Pass 并插入最少的屏障这也是现在较新引擎底层普遍走的方向。我自己看帧流程是否设计优秀通常还会看两个细节第一是否每帧都有不必要的全屏清除操作比如在延迟渲染 G-Buffer 中不需要清除 Diffuse 纯色第二是否大量出现 CPU 等待 GPU 的空闲间隙这种间隙多数是因为没有做合适的帧内异步提交和处理管线交叉。把这个节奏调顺了帧率才能稳下来。5. 资源生命周期和数据驱动设计5.1 资源的 Handle 与引用计数管理引擎的底层架构除了要处理 CPU 内存还要处理海量“资产”贴图、网格、音频、动画、材质。这些资源有一个共同特点体量很大、生命周期长、被多个模块共享。如果引擎每次使用资源都按路径加载一份独立拷贝内存很快就会被撑爆。所以资源管理必须走基于 Handle 和引用计数的方案。Handle 本质上是资源表中的一个索引配合一代代递增的代次记录generation counter能有效避免悬垂指针当你持有一个旧 Handle 去访问已被释放的资源如果代次不匹配引擎就能立刻判定访问失败而不是直接读到野数据。这个设计在当前所有主流引擎底层几乎都是标配它也让我们在资源加载时可以做统一路径全部资源通过资源表查询Handle 有效性和类型检查放在第一道防线。引用计数策略上我比较推荐的思路是“强引用 弱引用”并存正在被渲染场景引用的资源必须是强引用保证加载和持久存在编辑器预览、异步加载中间态这类不保证资源存活的场景用弱引用。注意循环引用问题在资源图上很常见比如材质引用了贴图渲染场景又通过材质列表引用了材质如果不注意释放语义资源就会一直无法回收。底层架构在最初设计资源注册表时就把循环引用检测做进去比最后靠业务层手工修复爽太多了。5.2 异步加载与热重载的处理节奏游戏运行时资源加载有一个核心矛盾文件 IO 和反序列化的耗时完全不可控不能阻塞主逻辑。所以资源系统通常会做异步加载主逻辑发出加载请求资源系统在后台线程读取文件、解析数据、创建 GPU 资源完成后通过回调或者事件通知业务层。这个流程看似简单真正难的是处理“加载完成前资源被请求释放”“加载过程中资源描述已变更”这类竞态条件。我见过很多引擎在资源异步加载时爆出诡异的崩溃原因基本都指向同一处资源对象在后台加载线程使用了主线程的数据结构或者回调里操作了正在被卸载的同名资源。安全的做法是异步加载线程只保留纯粹的加载数据和反序列化对象不碰引擎场景对象加载完成后再由主线程的“资源发布队列”把新资源对接到场景数据中这样主线程和加载线程之间的共享状态就极其有限。热重载是这套流程的进化版。不用重启游戏就能让美术看到新贴图或新材质效果本质上就是资源在运行时被检测到文件变更后自动走一遍“重新加载-发布-替换”的链路。做热重载时有个操作顺序问题非常重要先创建新资源再原子替换 Handle 指向的对象最后释放旧资源绝对不能反过来。顺序一颠倒凡是拿着旧 Handle 的模块都会立刻访问到半初始化对象线上直接白屏。很多引擎项目里美术同事烦透了的“改个颜色要等两分钟重新进 PIE”根因往往就在热重载链路的替换顺序没做对。5.3 数据驱动让玩法真正动态起来资源系统还承担着引擎数据驱动设计的重要使命。一个大型游戏团队里玩法策划、关卡设计师、美术同学每天会生成大量配置。这些配置不可能靠写代码去硬编码而是通过数据文件描述后被引擎解释执行。最典型的例子是实体预制体和组件配置一个场景里的敌人 NPC 由哪些组件组成每个组件的参数是什么全部用数据描述引擎启动时把数据加载出来实例化为对应对象。从底层架构角度看数据驱动最大的价值是把“可配置性”和“代码变更”解耦。策划调数值不需要改代码美术调材质不需要碰渲染层程序只需要维护好数据出入口的 Schema 兼容。但数据驱动也会带来新的灾难当配置数据膨胀到几千个文件字段重命名、类型变更、值合法性校验都成了底层架构需要解决的问题。所以我在项目里一直坚持两条约束所有配置必须有默认值和类型检查所有配置变更必须有 Diff/合并流程。有了这些兜底数据驱动的自由度才敢放开。6. 模块依赖与分层如何让引擎保持可扩展6.1 三层引擎架构的清晰依赖底层架构在宏观上普遍采用“平台层 - 核心层 - 引擎层 - 工具层”的分层模型。我这里把最典型的依赖画成结构化描述平台层封装操作系统和 SDK 差异提供窗口、输入、文件 IO、线程同步原语核心层包含上一篇详细展开过的内存分配器、容器、数学库、断言体系引擎层提供渲染、物理、音频、动画、场景管理、资源管理等业务运行时模块工具层则包括编辑器、资源导入器、调试面板和 Cook 工具。分层架构因为依赖方向只有一条向下传递的线让代码的可测试性和扩展性都有了教科书级别的保证。比如你想给引擎换一个输入后端从 Win32 切到 SDL只管改平台层接口适配就好核心层和引擎层完全无感知。而可怕的反向依赖比如核心层引用了渲染模块的头文件会导致整个项目无法做模块级测试任何改动都可能连锁引发一堆模块编译失败。我做底层架构评审时有个习惯性动作翻到头文件 include 第三级如果发现引擎层和核心层互相包含直接打回。判断代码的健康程度包括头文件依赖图比看功能是否完整靠谱得多。编译时间只是痛苦的表面隐藏很深的设计腐化基本都是从这个方向蔓延开的。6.2 依赖注入与模块解耦的实践心得底层模块解耦的一个常用手段是依赖注入但这里的“注入”并不是互联网后端领域那个容器而是引擎内更朴素的接口注册模式。比如渲染模块不想直接依赖特定物理引擎于是引擎提供一套“物理场景查询”接口物理模块在启动时把实现注册进全局接口表渲染模块运行时只通过接口名查询并调用对具体物理实现一无所知。这样一来即使之后你想把 PhysX 换成别的物理 SDK渲染模块一行代码都不用改。依赖注入最大的风险在于接口设计会把大量真相隐藏掉。比如接口里出现“传入场景指针、输出碰撞结果”这种语义宽泛的方法调用方完全不知道数据归属、生命周期和线程限制出错排查会非常痛苦。所以我的实践心得是接口方法必须带明确注释说明调用线程、参数所有权、返回数据的有效期能返回结构体就不要返回智能指针能只读就不要传可变引用。这些老生常谈的约束在引擎底层架构的实现细节中每一条都在救命。6.3 模块迭代时如何保证兼容性游戏引擎生命周期非常长一个版本会用上三五年团队也会一代代更替。所以底层架构设计时就得考虑兼容性。模块依赖变化之后旧接口不能删除或者随意改名核心接口的二进制布局更不要轻易调整否则第三方商用的插件或者历史项目数据全都会失效。最好的做法是保留旧的公开接口内部转调新的逻辑并在代码里留下标记以便后期统一清理。实际操作中可以根据影响范围把接口变更分成三类私有接口调整内部自由改、保护接口影响插件侧、公开接口影响历史项目资源针对不同级别走不同的决策流程。这样底层架构就不会经常陷入“我改一个字段导致全组资源重做”的灾难。代码里那些“TODO: 删除老接口”“Deprecated”标记看着烦实际上是版本演进的保险绳。7. 常见问题与排查技巧实录7.1 反复崩溃与性能抖动如何定位底层架构最常见的两类问题一是崩溃找不出根因二是性能抖动无从下手。我先说崩溃。遇到随机的非法访问崩溃比如访问空指针或者越界可以直接怀疑三种情况对象已被释放但引用未断、内存被越界写坏、线程之间的共享数据未同步。排查思路建议先看崩溃栈是否稳定如果每次栈不固定先启用内存调试分配器检测 heap 越界如果崩溃行为在同一个位置反复出现则重点检查生命周期管理。关于性能抖动优先怀疑点是内存分配、渲染状态高频切换、以及线程等待。先用 profiler 抓出耗时毛刺最明显的线程再看那个线程在毛刺区间究竟在等什么。我自己用的流程是抓帧、过滤主线程超长函数、定位可疑分配点、临时用固定内存池替换并对比曲线。这个方法看起来土但比任何高级分析工具都直观。性能问题排查的精髓是“先用统计缩小范围再针对热点做实验验证”而不是上来就改代码。7.2 跨模块引用与依赖循环的解法依赖循环在引擎工程里几乎是不可避免的。没有良好的接口抽象A 模块调 BB 又调 A编译器不会报错但运行时初始化顺序已经乱了经常出现“模块未初始化却开始使用”的隐性崩溃。解决思路可以分几层递进第一步把循环依赖拆分出来提取公共低层模块例如让两个模块都依赖同一份“基础配置”数据而不是互相调用对方的初始化函数第二步如果确实无法拆分则考虑引入接口和回调注册让调用关系只在初始化期建立第三步实在无法解开的临时方案是允许延迟绑定但要给接口访问加运行期保护。从工程管理角度来看依赖循环还得靠工具辅助。团队里可以做一个简单的头文件依赖检测脚本定期跑一遍自动把新引入的循环依赖识别出来并打出来交给代码评审去问责。我在项目里见过最严重的依赖循环是渲染模块引用了 UI 模块的窗口句柄UI 模块又引用了渲染桥接的静态函数双方都在自己的初始化里读取对方。这种问题如果不靠工具主动发现光靠人肉评审迟早会在某个奇怪的真机平台上暴露成灾难。8. 一点个人经验架构是“长”出来的不是“设计”出来的做了几年引擎相关的工作我的体会是底层架构这个词听起来很高大上好像是一群聪明人坐在白板前画几天类图就能定下来的东西。但真实的引擎架构更多是在团队构成、业务需求、硬件性能、第三方 SDK 边界这些因素反复博弈中一点一点“长”出来的。设计时要敬畏约束实现时要敬畏细节就好比内存分配器多写一个字节的对齐宏可能就决定了底层架构在某个主机平台上跑不跑得稳。如果你正在规划自己的引擎我的建议是先接受它的不完美初期用单一大模块跑通完整渲染流程等真正遇到性能瓶颈或多人协作痛点时再考虑拆分模块和引入复杂线程模型。架构重构需要业务压力驱动而不是为了所谓“优雅”自嗨。顺着这个思路慢慢往前拱每一层都为上一层服务底层架构自然会越来越扎实。这篇“001”先讲清楚基础框架之后我们可以继续从渲染管线、资源系统、物理与动画桥接这些方向展开聊。