本文翻译自 Fullscreen, Round 3,原载于 TIGSource Forums,是独立游戏《奥伯拉丁的回归》(Return of the Obra Dinn)开发者 Lucas Pope 的技术分享。
背景与问题
在上一篇技术日志中,我尝试了各种方案来解决全屏模式下的视觉不适问题。结论是:保持游戏独特风格的最佳方式,是稳定抖动图案(dither pattern)并减少闪烁像素。
这是第三次完整的技术日志了。每次写完都会有新想法冒出来,但现在,我只想把这事儿解决了。
抖动处理基础
首先简单解释一下抖动(Dithering)的工作原理。《奥伯拉丁的回归》在内部以 8-bit 灰度渲染所有内容,然后在后处理阶段将最终输出转换为 1-bit。转换过程通过将源图像的每个像素与抖动图案中对应点的阈值进行比较来完成:如果图像像素值大于抖动图案点值,输出位设为 1,否则为 0。

这个过程有两个核心组件:源图像和抖动图案。游戏中使用两种不同的图案:
- 8x8 Bayer 矩阵:更平滑的灰度过渡
- 128x128 蓝噪声场:更有机的、无序的输出


球体使用 Bayer,其他地方使用蓝噪声
核心问题:动态场景的抖动
基础的抖动处理对静态图像效果很好,但对于运动或动画图像就糟糕了。
当源图像帧与帧之间变化时,静态的抖动图案和低分辨率输出就成了大问题——本该是实体的形状和阴影,看起来就像一堆蠕动的像素乱麻。

现代游戏中的抖动大多用于静态图像或高分辨率输出场景。当玩家看到这种低分辨率的游走抖动效果时,第一反应不是”哦这是抖动算法的特性”,而是”这扭曲抖动效果是什么鬼?怎么关掉?”

试着在移动时聚焦某一点——这就是《奥伯拉丁的回归》全屏问题的核心。虽然有各种”换个风格吧”的解决方案,但我不想让这些该死的小像素推着我走。
稳定抖动图案的技术探索
为了让眼睛能最好地重组图像,抖动图案的点需要与输出像素保持 1:1 对应。但仅与输出关联意味着作为后处理效果,抖动图案与被渲染的几何体之间没有联系——每帧移动的场景元素都会与不同的阈值进行比较。
我真正想要的是:让抖动图案”钉”在几何体上,随场景一起稳定移动。
方案一:纹素空间映射(Texel Space)
我的第一次尝试是将抖动图案映射到纹素空间。这相当于在场景渲染期间对物体纹理进行抖动,而不是在8-bit输出的后处理阶段。

结果正如预期:
- 不同物体的映射不同,图案比例不匹配
- 真正的致命问题是走样(aliasing):从一个空间重采样到另一个空间必然产生走样,而抖动图案不能像传统纹理那样做 Mipmap 或过滤

虽然图案很好地固定在几何体上,但走样产生了另一种游走效果。纹素大小随相机距离变化,总会有抖动图案像素在重采样到屏幕时严重走样。
方案二:运动扭曲(Motion Warping)
既然想让抖动图案跟踪下方移动的几何体,何不根据每个渲染像素的位置变化来扭曲图案?
这有点像运动模糊——每个像素跟踪它从上一帧到现在的移动。在这个方案中,我更新抖动纹理使其与场景一起移动。如果某像素在上一帧不存在,就重新加载抖动图案。

快速测试后几点很明显:
- 确实有点效果
- 抖动图案需要邻域——不能是独立的像素
逐像素考虑会导致图案中出现明显的断裂和不连续性(注意箱子上的效果):

方案三:屏幕映射 + 偏移(Screen-mapping With Offset)
在推导运动扭曲的方程时,一个非常简单的变换出现了:
DitherOffset = ScreenSize * CameraRotation / CameraFov

基本思想:当相机旋转一个视野(FOV)角度时,屏幕映射的抖动图案正好偏移一个屏幕的距离。这保持了与屏幕的 1:1 映射,同时考虑了简化的场景几何变换。

注意椅子和球体上的抖动像素看起来大部分随几何体移动,但更垂直于视线的平面(如地板)匹配得不好。
虽然不完美,但简单偏移屏幕映射的抖动图案让整体图案和场景移动足够接近,眼睛可以更好地追踪它们。
方案四:世界空间 - 立方体贴图(Cube Mapping)
之前的实验表明,抖动图案与场景几何的任何关联都必须忽略深度信息。这意味着抖动图案可以在相机旋转时固定到几何体上,但在相机平移时不行。
考虑到游戏的慢节奏和玩家的观察角色——通常是走动、停下、观察——下一个尝试是将抖动图案预渲染到以相机为中心的立方体的六个面上。立方体随相机移动但保持世界方向。


立方体贴图在正对侧面时效果好,对着角落时效果差。但抖动图案在相机旋转时完美固定在3D空间中。

问题在于:理想的映射应该是立方体上的一个纹素始终精确解析为屏幕上的一个像素,无论相机如何旋转。立方体做不到这一点…
方案五:世界空间 - 球面映射(Sphere Mapping)
…但用球体我得到了相当接近的效果。

找到这个特定的球面映射花了一些时间——正方形纹理无法完美平铺到球体上。我本可以用六边形网格重新定义抖动矩阵,但我只是在方形平铺上做了一些调整,用”环形”映射得到了不错的结果。

比立方体好,但仍有大量走样。球面映射的点大小与屏幕像素大小非常接近——刚好差到产生摩尔纹。
解决这种走样的简单方法是超采样(Supersampling):在更高分辨率下应用抖动阈值,然后下采样。


最终方案
这是我得到的最优方案。有一些妥协:
妥协:
- 抖动图案点在屏幕边缘变大且效果降低
- 对于大多数相机旋转,图案不按上-下-左-右对齐
- 由于最终的盒式下采样,输出不再是纯 1-bit
优势:
- 所有相机旋转下抖动都完美固定——游戏中感觉有点不可思议
- 游走抖动的不适感完全消失,即使全屏模式下
- 游戏的像素风格得以保留
可以通过简单的 50% 阈值将输出还原为 1-bit,结果仍然比不超采样好:


技术总结
投入 100 小时解决一个”不存在也没人会在意”的问题确实有点奇怪。但正因为如此,玩家不会有”这个抖动效果怎么这么糟糕”的困扰。
技术要点:
- 屏幕空间偏移映射在 1x 分辨率下效果最佳
- 球面映射在 2x 超采样下效果最佳
- 场景渲染分辨率从 640x360 提升到 800x450
最终游戏的两种显示模式:
| 模式 | 特点 |
|---|---|
| DIGITAL | 边框窗口、屏幕空间偏移抖动、1-bit 输出 |
| ANALOG | 全屏、球面映射抖动、柔化输出 |
个人思考
这篇文章展示了游戏开发中技术美学的深层考量。Lucas Pope 为了一个”玩家可能根本不会注意到的细节”投入了大量精力,但这正是优秀游戏与普通游戏的区别所在。
从技术角度看,这个问题的解决过程是一个典型的迭代优化案例:
- 从最直接的方案开始(纹素空间)
- 发现问题后尝试改进(运动扭曲)
- 简化数学模型获得意外突破(屏幕偏移)
- 追求更完美的解决方案(立方体→球体)
- 最终用超采样解决走样问题
这种”像素级别的完美主义”值得每一位开发者学习。
原文链接:Return of the Obra Dinn Devlog - Fullscreen, Round 3
《奥伯拉丁的回归》是一款独特的 1-bit 风格推理解谜游戏,于 2018 年 10 月 18 日发售,获得广泛好评。