这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
《游戏引擎实现原理》17.3 与 Job System 的对接
命令缓冲通道有了、独立编码器拆分也验证了,但谁来切块、谁来合并还没着落。本节把「分块 → 并行录 → 合并」接到卷一那套 Job System 上:用确定性调度模型算 makespan,再用 worker_threads 真跑,实测 4 worker 把 4096 个绘制项的录制墙钟压到约 2.7 分之一,且校验和与串行完全一致。
posts
这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
命令缓冲通道有了、独立编码器拆分也验证了,但谁来切块、谁来合并还没着落。本节把「分块 → 并行录 → 合并」接到卷一那套 Job System 上:用确定性调度模型算 makespan,再用 worker_threads 真跑,实测 4 worker 把 4096 个绘制项的录制墙钟压到约 2.7 分之一,且校验和与串行完全一致。
上一节把命令缓冲做成了一条跨线程通道,但录制本身还是串行的。本节用 OffscreenCanvas 把渲染表面搬离主线程,再把一帧的命令拆成多条独立编码器分别录制、按提交顺序合并,在真实 WebGPU 设备上验证「两条命令缓冲」与「单编码器两趟」逐字节一致。
主线程一边跑游戏逻辑一边录渲染命令,逻辑一卡帧就掉。本节把录制与提交拆到渲染线程上:主线程只往命令缓冲里塞命令,渲染线程按帧取走执行,用一个双缓冲的生产消费环把「还没消费完就覆写」变成一条确定性异常,并跑出背压的真实时间线,看清三缓冲比双缓冲多出的那一份缓冲垫换来了什么。
精灵和矢量图形都能画了,但它们和 3D 世界还没有规矩:谁先画、谁压在谁上面、半透明怎么混。本节把一帧的绘制定成三段——3D 不透明近到远、3D 透明远到近、UI 永远在最上——再在同一个 render pass 里先画 3D 绿底、再用 alpha 混合叠上左半屏的红色 UI,回读出 0.5 红加 0.5 绿的真实像素。
文字和矢量图形由曲线定义,位图放大会糊、图集也装不下任意字号。本节用有向距离场(SDF)把形状变成一段距离函数:距离直接转成抗锯齿覆盖率,一个全屏三角形就能画出边缘半透明的圆角矩形,任意放大都不糊。CPU 参考与 WGSL 逐字对齐,跑出中心全白、角外全黑、边界 128 的真实像素。
2D 与 UI 渲染的第一个问题是「一张界面里成百上千个精灵怎么画」。本节先把小图打进一张 2 次幂图集(货架装箱),再用一条精灵批次键把同层同深度的同页精灵并成一次绘制:10 万个精灵在单页图集下只剩 16 次绘制,最后在真实 WebGPU 设备上跑出一次 draw(6,2) 从图集采出红蓝两个精灵。
后处理链的代价可以提前算出来:每个全屏 pass 都要把纹理读一遍、写一遍,这就是带宽。本节在 helio 里实现一份带宽账本,把 pass 的读写字节按格式与分辨率算清楚,再在真实 WebGPU 设备上实测 1/5/13 抽样三个全屏 pass 的每帧耗时,看成本到底由「固定开销」还是「像素数」主导。
场景 pass 输出的是线性 HDR,显示器要的是 [0,1] 的 LDR,中间隔着色调映射;锯齿则要在边缘上做文章。本节在 helio 里把 Reinhard、ACES、filmic 三个算子与一次 5 邻域 FXAA 接成后处理链上的两个 pass,并在真实 WebGPU 设备上验证 GPU 结果与 CPU 算子逐像素一致。
后处理是一串全屏 pass:每个 pass 读若干张纹理、写一张纹理。本节在 helio 上从零搭出全屏 Pass 链的数据模型——覆盖整个视口的全屏三角形、pass 描述符、链合法性校验,以及用「最后一次使用」回收中间纹理的物理纹理规划,再在真实 WebGPU 设备上跑一条两段链、回读像素逐通道验证。
帧图编译成执行计划只有三步:拓扑排序定顺序、给每个资源分配槽位、把 Pass 的读写原样带上。本节在 helio 里把这三步写成纯 CPU 的编译器,再让浏览器侧的执行器只按计划录命令,把一条乱序声明的三 Pass 帧图跑出正确像素,验证「声明序」与「执行序」彻底解耦。
Vulkan 类 API 的帧图要替开发者插屏障、把瞬态资源别名进共享显存;WebGPU 把这两件事整个从 API 里拿掉了——同步在 pass 边界隐式完成,纹理也没有别名入口。本节在 helio 里把这个约束差异量化:同一批瞬态资源,别名规划器给出两种显存占用,再在真实设备上跑通一条三 Pass 链。
帧图不是一张画出来的图,而是一组 Pass 与它们读写资源的声明。本节在 helio 里从零实现帧图的 IR:把「谁读什么、谁写什么」翻译成数据流依赖与三类 hazard,跑拓扑排序排出执行序,再算出每个资源的生命周期区间,为后面的内存规划与执行打底。
阴影的两大旋钮——每像素采样数与深度偏置——分别换质量和换成本。PCF 半径从 1 到 3 让采样数按平方增长,把硬边软化成柔化像素;偏置从 0 往上加能压住自遮挡粉刺,但加过头阴影就脱离接触点。本节在 helio 里实现采样核与斜率缩放偏置,并在 GPU 上量化这两条权衡曲线。
一张固定分辨率的阴影贴图罩住整个视锥,近处必然糊、远处必然浪费。级联阴影把视锥沿深度切成几级,每级单独拟合一个光源空间正交盒,并在相机移动时把正交盒对齐到 texel 网格。本节在 helio 里实现切分、角点提取与拟合,再在 GPU 上复算逐级联的包围盒。
光照决定物体长什么样,阴影决定它站在哪。本节在 helio 上从零实现阴影贴图:先从光源视角把遮挡物渲染成一张光空间深度图,再把相机视角的地面点投影回光源空间做深度比较,最后在真实 WebGPU 设备上回读像素,与 CPU 解析参考逐点比对。
上一节把单个光源的衰减算清楚,但场景里往往有上百个光源,逐物体遍历全部光源太贵。集群光照把视图空间切成三维网格,每个集群预存一份命中的光源列表。本节在 helio 里实现集群网格与光源装配的 CPU 参考,再用 compute 着色器在真实 WebGPU 设备上复算并逐集群比对。
上一节把着色路径打通,这一节解决「光从哪来、怎么变弱」。方向光没有距离衰减,点光用 UE4 的窗口化反平方,聚光再叠一个锥形衰减。本节在 helio 里实现三类光源的衰减公式,并用一个 compute 着色器在真实 WebGPU 设备上回读每个距离的衰减值,与 CPU 参考逐样本比对。
光照不是一句「乘以点积」,而是「法线 + 光方向 + 视线 + 材质」到一根颜色的映射。本节在 helio 里从零实现 Lambert、Blinn-Phong、GGX 三条着色路径的 CPU 参考,再原样搬进 WGSL,在真实 WebGPU 设备上渲染球体并回读像素,要求 CPU 与 GPU 逐个字节对齐。
着色器写错时报错只有一行天书,定位全靠猜:本节真跑 getCompilationInfo(),拿到带行号、列号与字符偏移的诊断,并用 11.1 的 include 行映射把它还原成原始文件的文件:行:列,让编辑器能一键跳到出错的那一行源码。
uniform 结构体在 CPU 侧要按完全一致的内存布局填充,手写偏移是 bug 温床:本节从 WGSL 的 struct 反射出每个成员的偏移与整个缓冲区大小,按该布局自动打包参数,并真跑一次渲染回读像素,验证绑定确实生效,偏移对了像素才会对。