SuperAPI AI 工具集

renderer

编程与开发

查看项目

更新于 10-15 09:09

技能介绍

用途

这套规则用于指导使用 GPU 进行绘制的应用架构,技术栈为 WebGPU 上的 TypeGPU:使用带类型的 schema、bind group layout 与 pipeline,在更清晰的地方使用 WGSL。规则明确要求不要引入第二套渲染库。

对于 3D 模型与纹理,若可用则使用 Blender。优先使用已安装的 Blender MCP server;否则以无头方式运行(blender -b --python script.py)。脚本需保留在仓库中,使每个资产都能从代码复现,而非手工编辑。

架构

渲染器只绘制交给它的内容。应用从自身状态(游戏观测、查询结果、文档)构建普通的帧输入,渲染器通过单一接口消费它们,绝不直接访问应用或领域状态。

由一个帧函数负责编排。单个函数按顺序编码每个 pass。添加任何内容前先阅读它,并把你的 pass 加在那里,而不是另起一处。

按语义选择阶段:compute 用于准备工作(剔除、布局、实例构建);不透明几何体先做深度预通道,再做受光颜色通道;半透明内容放在不透明之后;屏幕空间 pass 作用于已解析的目标;叠加层与 UI 最后。

每个概念只有一个所有者:device 与能力、相机与投影、深度约定、帧目标、资源注册表、时间源。第二份副本(私有投影,或在 WGSL 中手工镜像的结构体)是潜在的 bug,应重构为共享所有者。

可调数值放在数据中(配置或 fixture),在加载处校验,不要作为常量散落在各 pass 中。

按各自频率更新资源:每帧、视图变化时、数据变化时(仅上传增量)或仅一次。热路径上每帧不得分配内存。

CPU 侧数学使用 pmndrs math 包(npm math,其 API.md 列出所有导出):向量、矩阵、四元数、视锥与形状剔除、噪声、带种子的随机数。其函数以输出作为第一个参数并返回它,因此预分配的 scratch 可让热路径零分配。把结果打包进预分配的 Float32Array 以供上传。不要手写第二套向量或矩阵库。

拾取与命中测试使用应用自身的形状,以及 GPU 打包所用的同一相机函数,绝不回读已绘制的像素。

资源

每个 buffer 与 texture 都经过同一个注册表,带有用于尺寸相关目标的 scope 和用于可替换资源的 slot。不得在注册表之外分配:在 TypeGPU 中,root.destroy() 不会释放 root 创建的内容。

处理异步重建(resize、pipeline 切换)。构建到新 scope 并整体替换。释放被超越或 dispose 后才完成的构建。让每个公开调用在 dispose 后都安全。

当上传与派生资源烘焙共享可变 GPU buffer 时,序列化整个替换事务。仅序列化上传仍会让较旧的回读安装过期结果;保留调用方错误而不污染后续请求。

添加一个测试,验证 resize、重建与重复 reset 后计数与字节数回到基线。

深度、混合与目标

深度是一种访问契约(read、read-write、prepassed),约定是全引擎统一的。优先在 depth32float 上使用 reverse-Z(clear 0,compare greater)。比较方向、clear 值与格式要么一起改,要么都不改。

不透明几何体绝不进行 alpha 混合。半透明 pass 读取深度且绝不写入。

分段绘制的线条在构造上应自然衔接。加法混合的片段会重叠并叠加淡出,因此接缝处求和为一;混合片段被切成方形,因为两次 over 合成的淡出永远不会求和为一。同一事物的混合层按层顺序绘制,而非按片段生成顺序。

动态光是每个材质通过同一个着色函数读取的数据。效果提供有界的每帧短生命周期点光源列表;共享的 shade 调用为每个受光层求和。发光 sprite 只照亮空气,绝不照亮周围表面,逐层的光照 hack 会与其他层产生偏差。

叠加层与 UI 在后处理之后合成,不受光照与调色影响。属于世界的内容就画在世界中。

深度预通道与颜色通道共享同一个带 @invariant position 的顶点阶段,使二者深度逐位一致。

使用 4× MSAA(WebGPU 保证支持的采样数)。对后续屏幕空间测试依赖的任何 varying 在 centroid 处插值。

每个标注作为一个组合成。其 backing 绘制在前景笔画之下;选择优先级移动整个组。通过绘制顺序修复遮挡,而非擦除预期的 backing 覆盖。效果尾部与堆叠分别设界。

为最终渲染几何体设界,包括每个独立乘数与绘制的 LOD。让 shader 变化边界、CPU 校验与剔除只有一个所有者。依赖视图的宽度扩展不得悄悄扩展高度;仅凭源尺寸无法证明最终尺寸上限。

TypeGPU 与 WebGPU 陷阱

在 GPU 探针中使用单个 TypeGPU 模块实例。将打包器优化的导入与直接包 URL(或不同的 cache query)混用会复制内部符号:导入的 shader 函数可能从解析中静默消失,WGSL 报告未解析调用。更改 shader 代码前先匹配 pass 的实际模块 URL。

只固定共享相机 bind group($idx(0))。其余由 TypeGPU 编号。

没有 fragment 阶段的 pipeline 是合法的。用它做仅深度 pass,而不是写 frag_depth,后者会禁用 early-Z。

WGSL 的 let 不可变。重新赋值会使 pipeline 失效,而应用看起来仍正常。

WGSL 保留了尚未使用的词(cast、meta 等);以其中之一命名的标识符会在解析时使模块失败。

将 uniform 结构体填充到 16 B。在每个 schema 旁保留字节大小常量,并针对它测试打包器。

默认 bind 限制很小(每阶段 8 个 storage buffer)。添加前先计数。

Apple GPU:某 pass 未绘制任何内容的 tile 会跳过其 resolve,因此已加载目标可能在那里显示旧帧。让背景 pass 也执行 resolve。时间戳查询只有作为整帧总量才有意义。CPU 上也保持矩阵为 float32,使 CPU 拾取与 GPU 绘制完全一致。

将任何校验警告或控制台错误视为渲染失败。

验证

动画使用一个可注入时钟。绝不在 pass 内使用 performance.now()、Date 或无种子随机数。固定时钟可产生确定性捕获。

在单元测试中测试打包与 shader 数学的 CPU 镜像。然后在真实 GPU(非软件回退)上运行的浏览器中渲染最窄的真实场景,并查看图像。

统计不是像素。将计数与证明主体已绘制并取景的像素或裁剪检查配对。将 streak 与其公布的端点匹配,当类别共享样式时不要仅凭颜色。对快速运动跟踪连续帧裁剪,使离开静态相机不被误认为淡出。

从契约而非复制的常量推导检查。仅在书面说明理由时更改像素阈值。

通过在同一台机器上交错开启与关闭该功能来测量 GPU 成本,而非两次独立运行。机器上的其他负载会淹没微小差异。

战术缩放下的宽度变化

当世界空间宽度变化几乎不改变截图时,在再次调参前,追踪完整宽度路径经过屏幕空间最小值、core/glow 层与后处理。用同一相机与事件比较原生战术视图与近景视图。对于移动的亚像素特征,在拥挤的游戏视图以及孤立裁剪中检查连续帧;细的静止画面可能掩盖闪烁或在地形上消失。

重复运动需要完整周期

对于节奏或同步性主张,捕获启动与多个完整的工作/休息周期,包括其最长停顿。将原生运动帧与每个 actor 的源事件时间戳配对;重叠的可见轨迹不能证明同时发射,短暂错开的开场不能证明持续独立。在多个种子上测试随机时序,并报告实测间隔,而非承诺不间断活动。

原生生成身份拥有重叠景物

空间归属无法识别某个树干由哪片作者森林生成。重叠形状可能复制树木或分配另一来源的树冠。从原生生成导出不可变的原始来源关联,在消费者中保留无损 prop ID,并让每个来源树干只放置一次。当其他形状交错时,精确矩形导出序号也需要显式的作者 ID 映射。通过公开导出与生产放置来证明这一点,包括空来源与生成范围之外的普通 prop。

归属、数值采样与渲染验收是彼此独立的断言。保留首次失败的生产回读及其编译源;匹配的归属标志不能解决失败的距离 oracle。在停止点,保留一个未激活的候选并恢复运行时基线,而不是放宽其标准。

资源检查需要一致的视图历史

暂停的快进可能在 UI 绘制该发布前就完成数据交付。在移动相机前等待其绘制 tick 与呈现时钟。否则保留的 buffer 可能在一次 reset 中看到额外的细节层级,看起来像泄漏。在更改容量策略或弱化字节断言前,用实际分配创建/销毁记录归因差异。

尸体身份不冻结其锚点

下落或静止身体的权威可能在建筑倒塌后改变其支撑。为同一尸体身份刷新已发布位置,而不重启其死亡剪辑、不改变朝向、不让淡出的尸体返回。静态锚点变化必须使尸体发布版本失效;仅移动缓存对象会让 GPU 实例停留在旧高度。敌方锚点来自该方最后观测到的尸体状态,因此渲染无法从当前物理世界推断未见的倒塌。