1.2 一帧的旅程:从场景图到像素


1.2 一帧的旅程:从场景图到像素

本节摘要:一帧是引擎的一次完整生产周期,从遍历场景图开始,到像素写入画布结束。本节把这条周期切成八段站点,给出全册通用的帧流水线图;后续每一章的组件都能在这张图上找到自己的岗位,性能问题也能按站点归账。读懂这张图,等于拿到了全册的地图。

一、把 16.7 毫秒切开

60 帧每秒意味着每帧 16.7 毫秒左右,从浏览器发出刷新信号到像素可见,这段时间被引擎排成一条单向流水线。单向,是因为后一段只消费前一段的产出:矩阵没算完就不能剔除,剔除没做完就不该提交绘制。理解任何一个组件,先在下面这张图上找到它的位置。

图 1-1 一帧流水线的八段站点总览

图 1-1 一帧流水线的八段站点总览

二、八段站点的职责与交接

逐段过一遍交接物。第1站动画推进把节点变换写新;第2站场景图遍历顺着父子链把局部变换累乘成世界矩阵——这是第2章的主题;第3站剔除拿世界矩阵算出的包围盒对照相机视锥,把看不见的网格请出名单,省下的是第6站最贵的绘制调用;第4站相机与灯光把这一帧的观察方式和照明方式打包成两组数据,供第7站消费。

第5站是几何与材质的绑定:每个网格的顶点缓冲、索引、材质在此各就各位;第6站引擎按优化过的顺序逐网格发出绘制调用,一次调用就是 CPU 给 GPU 的一张工单;第7站 GPU 执行工单,对着每个像素跑一遍着色程序,颜色在这一步才真正算出来;第8站呈现,把整帧画面交给画布,然后等待下一个刷新信号。

用代码验证站点顺序最直接的方式是观测回调。引擎在关键站点之间留了挂钩,往里塞打印就能看到一帧内的执行顺序:

// 观测一帧内部的执行顺序:把钩子挂到流水线的不同站点 scene.onBeforeAnimationsObservable.add(() => { console.log("站点1之前:动画即将推进"); }); scene.onBeforeRenderObservable.add(() => { console.log("渲染主体之前:矩阵与剔除即将开始"); }); scene.onAfterRenderObservable.add(() => { console.log("本帧渲染完成:像素已就绪"); }); // 连续观察若干帧,控制台会按固定顺序循环打印三个钩子 // 这个顺序就是八段图的可执行版本

站点顺序还能解释一个常见疑惑——为什么改了位置属性,下一帧才生效:

// 属性修改只是记账,真正的矩阵更新发生在下一帧的第 2 站 box.position.x = 10; console.log(box.absolutePosition.x); // 仍是旧值:世界矩阵还没重算 box.computeWorldMatrix(true); // 强制立即重算,脱离帧节拍 console.log(box.absolutePosition.x); // 现在是 10

钩子还能当秒表用。相邻两个钩子之间的耗时,就是一段流水线的实测成本,自己动手就能做出第一张站点耗时表:

// 用一对钩子量出"渲染主体"这段的耗时:每秒报一次,别刷屏 let t0 = 0, frameCount = 0; scene.onBeforeRenderObservable.add(() => { t0 = performance.now(); }); scene.onAfterRenderObservable.add(() => { frameCount++; if (frameCount % 60 === 0) { console.log(`渲染主体耗时 ${(performance.now() - t0).toFixed(2)} 毫秒`); } });

这份自制秒表量的是 CPU 侧的准备与提交,GPU 的异步执行不在读数里——CPU 与 GPU 是隔着提交边界的两本账,1.3 节展开。

三、用站点图读后续章节

这张图的用法贯穿全册:读到任何组件,先问它驻扎在第几站、产出什么交接给谁。相机驻第4站,交接视图投影矩阵;材质驻第5到7站,交接着色程序与纹理;动画驻第1站,交接写新的变换;后期处理是第7站之后加出来的"第8.5站",对整帧像素统一加工。性能调优则是给站点归账:绘制调用多归第6站、像素超支归第7站、纹理带宽归第5到7站。

把"症状到站点"的对应关系先立一张速查表,后面五章会反复引用它:

症状 第一嫌疑站点 首查动作
物体凭空消失 第3站剔除 查包围盒与相机视锥、查 setEnabled
拖影或残影 第3站与第8站 查自动清除缓冲设置、查相机近远裁剪面
颜色整体发暗 第4站灯光数据 查灯光存在与强度,再查材质通道
帧率随镜头转向波动 第2、3、6站 数可见网格与绘制调用,最坏朝向读数
点击无响应 输入与拾取环节 查拾取开关与事件绑定(第4章展开)

这张图会不会过时

读者可能担心:引擎版本年年更新,八段图会不会很快失效。我的判断是不会。图形接口从 WebGL 换到 WebGPU(6.4 节),变的是每一段的执行效率与实现方式;场景要先遍历、可见要先筛、颜色要逐像素算——这个因果顺序由问题本身决定,不由引擎决定。学 API 会过时,学这张图不会,这也是本教程拿它当主线的理由。

⚠️ 常见坑:把"帧率高"当成"不卡"的唯一指标。站点 6 的绘制调用暴增时帧率先稳后崩,中间有一段"帧率正常但操作发涩"的区间,因为输入响应也排在帧内处理。观测时要同时看帧率与帧耗时两个指标。

💡 关键直觉:流水线是单向的,所以优化也单向归因。画面不对先找上游:看不到先查剔除与相机,颜色不对再查着色与灯光。从下游像素倒推,十次有八次绕远路。

本节要点回顾

  • 一帧是一条单向流水线:八段站点,后段只消费前段产出
  • 前四站在 CPU 侧备料:动画、遍历、剔除、相机灯光数据
  • 后四站花钱出画:绑定、绘制调用、着色执行、呈现
  • 三本账:CPU 账看调用数、GPU 账看像素与着色、带宽账看数据搬运
  • 回调钩子可观测帧内顺序:这是全册做实验的通用手段
  • 属性修改延迟到下一帧生效:矩阵更新有自己的站点节拍

有了整帧地图,还差一个节拍器:这一帧到底何时开始、多久算超时?下一节给流水线装上秒表,讲清帧率的来龙去脉与掉帧的三种典型面孔。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U