NEE's Blog

《奥伯拉丁的回归》全屏抖动稳定技术解析

March 20, 2026

本文翻译自 Return of the Obra Dinn - Fullscreen, Round 3,原载于 TIGSource Forums,作者 Lucas Pope 是《奥伯拉丁的回归》的独立开发者。


全屏适配,第三轮

感谢大家提出的所有建议。我真的尝试了每一种方法,最终得出结论:在保持游戏风格的同时解决全屏不适感的最佳方案,是稳定”游动”的抖动(dither)效果并抑制闪烁的像素点。虽然做了一些妥协,但最终还是达成了目标。这是我写的第三篇完整的开发日志了——每次回顾之前的版本,总会冒出新想法或发现值得尝试的东西。到现在,我已经不在乎了。

抖动处理原理

首先快速解释一下。Obra Dinn 在内部以 8 位灰度渲染所有内容,然后在后处理阶段将最终输出转换为 1 位色。从 8 位到 1 位的转换是通过将源图像的每个像素与抖动图案(dither pattern)中对应的点进行比较来完成的。如果图像像素值大于抖动图案点的值,输出位就设为 1,否则为 0。输出被缩减为 1 位,而观众的眼睛会将这些像素重新融合,近似还原出更多位数的色彩深度。

抖动原理

通过抖动图案对源图像进行阈值处理

这个过程的两个组成部分是源图像和抖动图案。Obra Dinn 针对不同情况使用了两种不同的图案:一个 8x8 的 Bayer 矩阵用于获得更平滑的色调范围,以及一个 128x128 的蓝噪声场(blue noise field)用于获得更少秩序感的输出。

Bayer 和蓝噪声

Bayer / 蓝噪声

引擎内效果

引擎内效果(无线框)。球体使用 Bayer,其他地方使用蓝噪声

请保持静止

基本的抖动处理对于静态图像效果很好,但对于移动或动画图像就不那么理想了。当源图像逐帧变化时,静态的抖动图案和低分辨率输出就成了大问题。原本应该是实体形状和色调的东西,现在看起来就像一团扭动的像素。

移动的球体

移动球体时的效果

如今,抖动主要在源图像是静态的或输出分辨率很高的情况下使用。当人们看到这种低分辨率的游动抖动效果时,第一反应不是”哦,抖动就是这样工作的”,而是”这是什么扭曲抖动的效果,我要怎么关掉它。”

原始效果

展示 A。为了您的舒适度,已降低对比度

试着在移动时聚焦某个东西,然后见证 Obra Dinn 全屏问题的核心症结。有一些解决方案,基本上归结为”这种风格行不通,换掉它。”我在这条路上走了很远,尝试了不同的风格,然后又折回来想:也许我不应该让这些荒谬的小像素欺负我。

稳定抖动效果

为了让你的眼睛有最好的机会重新组合一切,抖动效果在抖动图案点与输出像素呈 1:1 对应关系时效果最好。但是,仅与输出相关联意味着作为场景后处理效果,渲染的几何体与对其进行阈值处理的图案之间没有联系。每一帧,移动的场景元素都会与不同的值进行阈值比较。我想要的是让抖动图案”钉”在几何体上,并随着场景的其余部分移动时保持稳定。

这核心是一个映射问题。正如这篇文章的长度所表明的,理想的抖动图案映射(与屏幕 1:1)和理想的场景映射(与几何体 x:1)之间存在冲突,所以准备好接受一些妥协吧。我的大部分工作都集中在将输入抖动图案映射到不同的空间,以便更好地将图案与场景几何体关联起来。这里的一切都是在预阈值处理阶段完成的。

纹素空间(Texel Space)

我的第一次尝试是在纹素空间映射抖动图案。这相当于在场景渲染期间对物体纹理进行抖动,而不是在 8 位输出的后处理阶段。我没想到这会效果很好,但想看看完美匹配场景的映射是什么样子。

纹素空间映射

纹素空间中的抖动图案

好吧,预期完全符合。物体都被不同地映射,所以它们的图案比例不匹配。这些可以统一。真正的问题是锯齿。像这样从一个空间重新采样到另一个空间会导致锯齿,而抖动图案不能像传统纹理那样轻松进行 mipmap 或过滤。不过,继续往下看:

纹素空间应用

应用到移动场景

这不是完全失败——图案很好地钉在几何体上。锯齿产生了自己的游动效果,统一或缩放映射对此没有帮助。纹素会随着与相机距离的变化而改变大小,所以总会有抖动图案像素在重新采样到屏幕时产生严重的锯齿。

运动扭曲(Motion Warping)

如果我想让抖动图案跟踪其下方的移动几何体,为什么不直接使用场景中每个渲染像素的位置变化来扭曲图案呢?确实,为什么不呢。这有点像运动模糊,每个像素跟踪它从上一帧开始的移动。在这种情况下,我更新抖动纹理,使其图案随场景移动。如果场景像素在上一帧中没有表示,就在那里重新加载抖动图案。由于游戏的静态特性,这项技术变得简单多了——我只需要担心相机的移动,而不需要担心单个物体。

运动扭曲

扭曲抖动图案以保持与场景的帧间连贯性

这是一个相当快速粗糙的尝试,但有几件事很清楚。首先,它某种程度上有效。其次,抖动图案需要一个邻域——它不能是单个像素。如果你像这个方法那样单独考虑每个像素,你会得到图案中明显的断裂和不连续性。我在这个测试场景中移动了相机来突出箱子上的这些问题。查看扭曲的抖动图案本身会让这更容易看到。

扭曲映射

用扭曲的抖动图案对纯灰色进行阈值处理

这些不连续性归因于我选择的不同像素深度和阈值。我推理出一个基于跟踪区域、平均其深度并将该区域所有抖动图案点移动相同数量的精细修复方案。区域边界处的不连续性可以通过明显的照明变化或线框线来隐藏。这将通过游戏现有的用于线框生成的彩色区域设置来实现。当我坐下来实现所有这些时,深度项从我提出的第一个方程中消失了,给了我一个更简单的替代方案:

带偏移的屏幕映射

在组合扭曲抖动的方程时,一个非常简单的变换出现了:

DitherOffset = ScreenSize * CameraRotation / CameraFov

偏移原理

基于相机旋转偏移屏幕映射的抖动图案

基本上,这表示我希望屏幕映射的抖动图案在相机旋转一个视野角度时正好移动一个屏幕。这保持了与屏幕的 1:1 映射,同时也考虑了视图中场景几何体的简化变换。这实际上只匹配屏幕中心的移动,但这该死的世界真好,它几乎足够好了。

偏移效果

偏移抖动图案以跟踪每个相机 FOV 旋转一个屏幕

注意椅子上抖动像素如何似乎大部分随几何体移动。球体也是如此。与视图更垂直的平面匹配得不太好;地板仍然是一团糟。

虽然不完美,但简单地偏移屏幕映射的抖动图案使整体图案和场景移动足够接近,眼睛可以更好地一起跟踪它们。我对此相当满意。在清理代码和提交所有内容时,也许写一两篇开发日志时,完美钉住抖动的想法一直萦绕在我心头:

世界空间 - 立方体贴图

到目前为止的实验表明,抖动图案和场景几何体之间的任何关联都必须忽略场景的深度信息。实际意义上,这意味着抖动可以在相机旋转期间钉在几何体上,但在相机平移期间不行。对于 Obra Dinn 来说这不是什么坏事,考虑到游戏的缓慢节奏和玩家的观察角色。你通常四处走动、停下来、看东西。走动时,屏幕上变化的东西太多了,游动的抖动不那么明显。

考虑到这一点,我的下一个尝试是通过将抖动图案预渲染到以相机为中心的立方体的六个面上,间接地将其映射到几何体。立方体随相机平移但保持与世界对齐。混合了:一点屏幕,一点场景。

相机立方体

映射到以相机为中心的立方体的抖动图案

立方体贴图视图

相机向上看向角落的视图。映射比例放大以便清晰展示

立方体的映射在直接看向面时效果很好,在瞄准角落时效果不太好。不过,当相机旋转时,抖动图案在 3D 空间中保持完美固定。即使粗糙,结果也很有希望。

立方体贴图效果

用立方体贴图的抖动图案对场景进行阈值处理

这就对了。作为后处理阶段,这比纹素空间映射更通用,这是好事。问题现在归结为特定的立方体贴图。理想的映射是立方体上的一个纹素始终解析为屏幕上的一个像素,无论相机如何旋转。这对立方体来说是不可能的…

世界空间 - 球体贴图

…但我用球体非常接近了。

球体贴图

将抖动图案映射到球体内部

找到这个特定的球形贴图花了一些时间。没有办法将方形纹理完美平铺到球体上。本来可以以六边形网格或其他可以在球体上平铺的东西来重新定义抖动矩阵。也许可能吧,我没试。相反,我只是对方形平铺进行修改,直到这个精心调整的”环形”映射的原始抖动图案给出了好的结果。

球体贴图效果

应用到场景

比立方体好。仍然有很多锯齿。球形贴图的点大小与屏幕像素大小非常相似——差得刚好产生摩尔纹。我能感觉到接近了,而这种锯齿的一个非常简单的修复方法是超采样(supersample):以更高分辨率应用抖动阈值处理,然后下采样。

超采样球体贴图

2 倍超采样球形贴图的抖动图案,然后下采样到 1 倍

超采样效果

在 2 倍下阈值处理,然后下采样到 1 倍

这是我得到的最好结果。有一些妥协:

  1. 抖动图案点在屏幕边缘变得更大且效果更差
  2. 对于大多数相机旋转,图案没有上下左右对齐
  3. 由于最终盒子下采样,输出不再是 1 位

但优点相当显著:

  1. 抖动对于所有相机旋转都完美钉住。这在游戏中感觉有点不可思议
  2. 即使在全屏模式下,游动抖动带来的不适感完全消失
  3. 游戏的像素风格得以保留

可以通过简单的 50% 阈值将输出减少回 1 位来消除妥协 #3。结果仍然比没有超采样更好(下面的三重比较是经过阈值处理的)。

最终对比

并排对比

默认调色板对比

游戏默认调色板

总结

为一个人们不会注意到其缺失的东西投入 100 小时感觉有点奇怪。确切地说,没有人会想,”天哪这抖动稳定得要命。这里完全是魔法。”不过,我不想给人们制造他们不知道应该有的问题,所以这是值得修复的。

带偏移的屏幕空间映射在 1 倍下效果最好,球体贴图在 2 倍下效果最好。现在所有场景渲染都在 800x450(从 640x360 提升),这在不牺牲低分辨率风格的情况下提高了可读性。最终游戏将有两种显示模式:

DIGITAL(数字模式)

  • 边框盒装、屏幕空间偏移抖动、1 位输出

ANALOG(模拟模式)

  • 全屏、球体贴图抖动、柔化输出

译者注

这篇文章是游戏开发中技术与艺术完美结合的绝佳案例。Lucas Pope 为了解决一个看似小众的视觉问题(全屏模式下的抖动游动),投入了大量时间探索各种技术方案。

值得学习的几个点:

  1. 问题定义的重要性 - 作者清楚地识别出核心问题是抖动图案与几何体的”关联性”,而不是简单地认为”这种风格不行”

  2. 系统性探索 - 从纹素空间、运动扭曲、屏幕偏移、立方体贴图到球体贴图,每一步都基于上一步的发现

  3. 妥协的艺术 - 最终方案并非完美,但在多个约束(保持风格、解决不适、性能)之间找到了平衡

  4. 超采样的巧妙应用 - 一个看似简单技术解决了复杂的锯齿问题

对于游戏开发者来说,这篇文章也是对”细节决定成败”的最好诠释——用户可能永远不会注意到抖动变得稳定了,但他们会感受到整体体验的提升。

comments powered by Disqus