《游戏引擎实现原理》11.2 场景视口与渲染接入
编辑器视口要同时画场景、网格与选择描边,还要支持多相机切换。本节把卷二渲染器接进编辑器:用同一份实例数据分别喂给主相机与顶视相机、在片元里给选中实例描边、再单独叠一层世界网格,并在 headless Chromium 里回读像素逐条核对是否生效。
category
编辑器视口要同时画场景、网格与选择描边,还要支持多相机切换。本节把卷二渲染器接进编辑器:用同一份实例数据分别喂给主相机与顶视相机、在片元里给选中实例描边、再单独叠一层世界网格,并在 headless Chromium 里回读像素逐条核对是否生效。
视口要回答的是「鼠标点在画面上究竟选中了谁、拖动了多少」。本节把拾取射线与变换 gizmo 的几何抽成纯数学:像素→NDC→世界射线、射线与有限轴线的最短距离、轴手柄与面手柄判定、沿轴拖拽位移的求解,并在本机真跑 12 条 vitest 用例;真实鼠标拖拽属 L4,需在浏览器中手动验证。
同时选中多个对象时,属性面板该显示什么?本节实现多选编辑的纯数据部分:求公共属性、把值聚合成「全等」或「混合」两种状态、再生成只包含真正变化项的批量命令。公共属性、混合值判定与批量命令生成都在本机真跑并有 vitest 用例;面板渲染与交互属 L3/L4,需在浏览器中手动验证。
面板拿到字段后,该给它配哪种控件?本节写一张字段类型到控件种类的映射表:数值对应数字框、bool 对应勾选框、vec3 展开成三分量、句柄对应对象引用,再叠一个可覆写的控件注册表处理个别特例。映射与解析逻辑在本机真跑并有 vitest 用例;真实 DOM 控件的创建属 L3,需在浏览器中手动验证。
属性面板不该为每个组件手写 UI。本节把卷一第 16 章的反射描述符接进编辑器:从类型描述与实例展开出纯数据的「面板描述模型」——字段名、标签、类型、当前值、只读与枚举候选,不含任何 DOM。该数据模型在本机真跑并有 vitest 用例;DOM 渲染属 L3,需在浏览器中手动验证。
《游戏引擎实现原理》卷三《子系统与工具》导航页:在卷一《引擎内核》与卷二《渲染引擎》之上,用 TypeScript 从零实现让《微光岛》活起来的资源管线与流式、动画与蒙皮、物理集成、音频、输入、脚本绑定与热重载,以及编辑器四章(数据模型、命令栈、属性面板、视口与资产)。讲清本卷定位、与卷一卷二的关系、L1–L4 可运行性口径,按三个部分列出 33 节索引,另含阅读路径、周计划与常见问题速查。
跳跃输入总被『吃掉』?问题多半不在玩家手速,而在手感逻辑。本节实现跳跃缓冲、土狼时间与输入延迟补偿三件套,三者全部是纯时间逻辑,可在 Node 里逐帧喂入真跑,并说明真正的『手感』数值调参仍需在浏览器中手动验证、命令行不可复现,只有逻辑部分可自动化测试。
游戏逻辑不该认识 Space 或 Pad0:ButtonA。本节把动作名与物理控制解耦成一张数据绑定表,支持多设备并行绑定、运行时重绑定、同键冲突检测与版本化持久化,并用 vitest 在本机真跑重绑定、冲突检测与按键配置往返序列化的完整用例。
键盘、鼠标、手柄、触摸四种设备产生的原始事件形状完全不同,游戏逻辑不该为每种设备各写一遍分支。本节把四种设备归一化成统一的 InputEvent,实现帧内事件队列,并区分边沿与电平两种语义,最后用一组真值用例把归一化与队列逻辑在本机真跑一遍。
前两节的混音图与音效池最终要落到 Web Audio 上,而它有两条硬限制:自动播放策略会挂起 AudioContext,必须靠用户手势 resume;时间精度与延迟又受渲染量子与设备缓冲支配。自动播放解锁与真实设备只能在浏览器里手动验证,但秒帧换算、量子量化、过去时间钳位、延迟补偿这些算术可以抽成纯 TS 真跑。
混音图解决了「怎么求和」,这一节解决「同时能响几个」。用固定容量 voice 池做音效管理,配优先级抢占与线性淡入淡出包络避免爆音;BGM 则走流式:解码器按源采样率吐 PCM 分块,混音器按设备采样率拉帧,中间的线性插值重采样与欠载检测都抽成纯 TS 真跑并配 vitest。
把「音源 → 总线 → 主输出」建模成一张有向图:节点带线性增益,边是数据流,渲染一个量子时按拓扑序累加、逐节点乘增益。先用纯 TS 真跑分贝换算、等功率声像、混音求和与总线路由,再把同一张图放进 headless Chromium 的 OfflineAudioContext 复算,逐样本比对 Web Audio 与 L1 的输出。
边界上最后一块职责是回答引擎的提问:射线先打到谁、这个区域内有什么、A 和 B 是刚开始接触还是早就碰着。本节用位掩码做碰撞分层,实现 raycast 与 overlap,再把物理库给的接触列表差分成 enter / exit / stay 三态事件,全部可在 Node 直跑。
物理对步长极其敏感,所以引擎侧必须做三件事:用固定步长累加器推进逻辑,用上一态与当前态之间的插值喂给渲染,用扫掠 AABB 在位移超过自身体积时兜住高速穿透。本节复用内核的 FixedStepLoop,把这三件事写成可跑的 TypeScript,并用 vitest 真跑过。
本节不写求解器,只把引擎与物理库之间那条线画出来:用判别联合描述碰撞体形状,用一个 PhysicsBackend 接口把 Jolt、Rapier 或自研后端挡在下面,再用 ColliderSync 只把版本号变过的实体推给物理侧。全部纯 TypeScript,可在 Node 直跑并跑通 vitest。
把姿态接到骨骼上:由绑定逆矩阵算蒙皮矩阵,实现 CPU 线性混合蒙皮,再把这套数学搬进 WGSL 顶点着色器,在 headless Chromium 里与 CPU 逐顶点比对。最后本机实测蒙皮相对静态网格的代价——4 影响蒙皮约为静态变换的 6.3 倍。
状态机决定「现在放哪段动画」,混合树决定「两段动画之间怎么过渡」。从零实现参数驱动的状态转移(条件、exitTime、any-state、trigger 消费)、两段姿态的交叉淡化与一维混合树权重,全部 L1 纯 TypeScript 并带 10 条单测。
把一段动画拆成剪辑、轨道与关键帧,从零写出 helio 的关键帧求值器:时间轴钳制/循环/乒乓三种环绕、关键帧二分定位、step/linear/cubic 三种插值与旋转 slerp。全部为 L1 纯 TypeScript,可在 Node 直跑并带 14 条单测。
《微光岛》的岛不是一整块资源:本节用 TypeScript 把它切成 4x4 个区块,用相机距离排加载优先级、用每帧字节预算限流、用加载与卸载双半径做滞回防抖,写出一个可逐帧推进的流式调度器,并实测单块字节数、峰值驻留与单帧最大派发量,全部来自本机实跑。
构建系统凭什么知道该重导谁:本节用 TypeScript 实现内容哈希(FNV-1a 64)、导入器版本与依赖键沿 DAG 的传播,让增量构建只重导真正脏掉的子树;再加产物哈希校验与热重载级联派发,全部用真实构建日志与缓存字节数验证,无一估算。