verify-pixels

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Prove pixels unchanged

验证像素未发生变化

A rendering change that must not alter the image is not proven by eyeballing a screenshot at one pose. Build a deterministic capture, compare byte-exact counts across a pose × phase matrix, and prove the capture harness itself is trustworthy before trusting what it reports about the change.
仅通过观察单一姿态下的截图,无法证明渲染修改未改变图像效果。应构建确定性捕获流程,在姿态×阶段矩阵中进行字节级精确对比,并先验证捕获工具的可靠性,再用其验证代码修改的结果。

Make the frame deterministic

确保帧的确定性

  • Drive every animated shader or vertex effect from one app-owned time value, never
    Date.now()
    or
    performance.now()
    read inside the render path, so a captured phase is exactly reproducible.
  • Freeze the clock with
    app.timeScale = 0
    before capturing; nothing should advance between frames you did not explicitly step.
  • Step frames explicitly: set
    app.autoRender = false
    once, then set
    app.renderNextFrame = true
    before each frame you want rendered. The engine renders exactly that frame and clears the flag — do not rely on the free-running render loop plus a timed screenshot.
  • Read the rendered pixels with
    await device.readPixelsAsync(x, y, w, h, pixels)
    against the exact backbuffer, not a re-encoded screenshot (e.g.
    canvas.toDataURL
    ) that can introduce compression or colour-management differences the eye won't catch. In the installed engine this method lives on
    WebglGraphicsDevice
    , not the base
    GraphicsDevice
    type, so narrow to it (or branch on
    device.isWebGL2
    ) before calling; a WebGPU project needs its own equivalent readback. If the project already has a screenshot path, hold it to the same rule: fixed size, fixed pose, no lossy step before the byte comparison.
  • 所有动画着色器或顶点效果均由应用程序控制的单一时间值驱动,渲染流程中绝不能使用
    Date.now()
    performance.now()
    获取时间,这样才能保证捕获的阶段完全可复现。
  • 捕获前通过
    app.timeScale = 0
    冻结时钟;除非你明确触发,否则帧之间不应有任何内容更新。
  • 显式控制帧步进:先设置
    app.autoRender = false
    ,然后在需要渲染的每一帧前设置
    app.renderNextFrame = true
    。引擎会精确渲染该帧并清除标志——不要依赖自由运行的渲染循环加定时截图的方式。
  • 使用
    await device.readPixelsAsync(x, y, w, h, pixels)
    读取渲染后的像素,读取对象是精确的后台缓冲区,而非经过重新编码的截图(如
    canvas.toDataURL
    ),后者可能引入人眼无法察觉的压缩或色彩管理差异。在已安装的引擎中,该方法属于
    WebglGraphicsDevice
    ,而非基础的
    GraphicsDevice
    类型,因此调用前需将设备类型转换为该类型(或根据
    device.isWebGL2
    进行分支处理);WebGPU项目需要使用对应的回读方法。如果项目已有截图流程,需遵循相同规则:固定尺寸、固定姿态,在字节对比前不进行任何有损处理。

Build a pose × phase matrix

构建姿态×阶段矩阵

Single-pose, single-frame proof cannot see everything a rendering refactor can break. Choose at least two representative camera poses — angles that would expose a dropped instance, a wrong batch bound, or a seam differently — and at least two animation phases, including one mid-animation, not only frame zero. Capture every pose × phase pair for the build before the change and the build after it, with the same canvas size, camera, and lighting each time.
单一姿态、单帧验证无法覆盖渲染重构可能引发的所有问题。至少选择两个具有代表性的相机姿态——能够暴露实例丢失、批处理边界错误或接缝问题的角度——以及至少两个动画阶段,其中一个需为动画中途状态,而不只是初始帧。在代码修改前后的构建版本中,分别捕获所有姿态×阶段组合,每次捕获需保持相同的画布尺寸、相机参数和光照条件。

Run a same-build control first

先运行同版本控制验证

Before trusting any diff between the old and new build, capture the same pose × phase matrix twice from the unmodified build. Two captures of identical, frozen state must be bit-identical. If they are not, the capture path itself is the source of noise — an unseeded animation, an asset still loading, a GPU timing race — and must be fixed before it can say anything about the real change.
在信任新旧版本之间的任何差异结果前,先从未修改的版本中两次捕获相同的姿态×阶段矩阵。相同冻结状态下的两次捕获必须完全一致(比特级相同)。如果不一致,说明捕获流程本身存在噪声——例如未设置种子的动画、仍在加载的资源、GPU时序竞争等——必须先修复这些问题,才能用捕获流程验证真实的代码修改。

Gate and report

校验与报告

Byte-compare each pose × phase pair between the two builds; do not diff by looking. Zero differing pixels passes outright. Any nonzero diff must be reviewed on-screen, and its cause and extent stated in the change description — never merged silently. Report the actual count every time, for example "0 of 65536 pixels differ" or "312 of 65536 pixels differ, confined to the object's silhouette edge". "Looks the same" or "no visible difference" is not a result.
对两个版本中每个姿态×阶段组合的像素进行字节级对比;不要通过肉眼观察来判断差异。零差异像素则直接通过。任何非零差异都必须在屏幕上进行审查,并在修改说明中说明其原因和影响范围——绝不能静默合并。每次都要报告实际的差异像素数,例如“65536个像素中0个存在差异”或“65536个像素中312个存在差异,仅局限于对象的轮廓边缘”。“看起来一样”或“无可见差异”不属于有效结果。