怎么修掉地块和精灵图之间的接缝
地块边缘上的发丝细缝。精灵周围一圈暗边。镜头一滚就冒出来又消失的那一行像素。 它们看起来是同一个 bug,其实是三个,修法也是三套—— 而试错了方向,正是这件事要耗掉一个下午的原因。
1. 十秒钟分辨它们
慢慢移动镜头,盯住其中一条缝。
它在闪——某些位置有、某些位置没有,或者随着滚动在爬。 那是亚像素定位。地块的交界落在了两个屏幕像素之间,渲染器必须取整; 有时两块地块取整的方向一致,就没有缝,有时不一致,就有。
它钉在原地——永远是同一条边,镜头怎么动都在。 那是纹理渗色。采样越过了图集里那一帧的边界,捞到了不属于这一帧的东西。
糊的不只是缝,是整张图——画面本身也有点虚。 那是贴图过滤,而且它还在让前面两种变得更糟。
这一测值得在动手改任何东西之前做,因为三种修法互不相通。 给图集扩边对闪烁的裂缝毫无作用, 像素级完美镜头也治不好在打包图集时就已经烙进去的暗边。
2. 渗色:采样越过了边界
图集里的一帧,是一张更大的贴图里的一个矩形。 当渲染器以非 1:1 的方式绘制它——1.5 倍缩放、有旋转、镜头在非整数位置—— 最边上那个像素的采样点会落在「这一帧的最后一个像素」和 「紧挨着它的东西的第一个像素」之间,而带过滤的采样会两边都取一点。
紧挨着它的东西通常是透明的间距。 一种颜色和透明混在一起,得到的是这种颜色更暗、更透的版本——一圈暗边。 如果邻居是另一帧,你得到的就是错误精灵的一抹残影,更糟,也更容易看出来。
加大间距解决不了这个问题。这就是那个坑: 最直觉的反应是把帧摆得更开一些, 而更宽的一片透明像素仍然是透明像素。 采样只越界一两个像素——间距多宽,它捞到的都是那片透明。
解法是扩边: 把间距填上这一帧自己最外面那一行一列的拷贝, 这样越界的采样捞到的就是它本该在里面捞到的颜色。 一两个像素就够,并且需要两倍于它的间距, 好让朝对方生长的两个邻居不会碰上。精灵图生成器里就有这个开关;桌面端的打包工具多数也有,名字通常也叫这个。
还有一个相关但不同的问题,值得知道是因为症状看起来一模一样: PNG 的完全透明的像素里也可以存着颜色信息, 而多数编辑器往那里写的是黑色。这些黑被混进来, 就会让所有东西都带一圈暗晕。 Godot 的导入面板管这个修法叫 Fix Alpha Border, 别的工具叫预乘 alpha 或者 alpha 渗透。 它和图集扩边不是一回事,修好一个不会修好另一个。
3. 亚像素裂缝:交界落在了像素之间
两块地块在世界坐标里严丝合缝。到了屏幕上,它们的交界算出来是 x = 100.5。 渲染器把左边那块画到 100,右边那块从 101 开始, 中间就露出一条一像素宽的背景。 镜头挪一点点,交界落到 101.0,两块地块取整方向一致,那条线就没了。
这和图集毫无关系,纯粹是算术问题。修法都是同一个思路:
缩放倍数保持整数。2 倍或者 3 倍时,整像素位置放大后还是整像素位置。 1.5 倍时不是,而且在别处怎么取整都救不回来。
把位置取整到整像素——尤其是镜头的位置, 因为它一动全场都动。多数引擎有对应的设置或组件,不用手写。
不要给单个精灵做小数缩放。把精灵缩到 0.999「把缝盖住」是很多人第一个想到的办法, 它拿一条会闪的缝换来一个永远不对的尺寸。 靠让地块互相重叠一点点来盖缝也一样: 换个镜头位置,同样的问题会在别处再冒出来。
4. 过滤方式:它让前两种都更糟
双线性过滤几乎在所有地方都是默认值,而它对像素画就是错的默认值。 它让画面发虚,而且它本身就是渗色得以发生的机制: 邻近采样只取一个像素、从不混合,所以它根本捞不到邻居的颜色。
所以把过滤设成邻近采样,能同时治好发虚和减轻渗色。 但它不能完全消除渗色——在非整数缩放下, 采样点仍然可能落到边界的另一侧—— 所以对于会被缩放的图集,扩边还是值得做。
块压缩也一起关掉。DXT 和它的亲戚是用一对插值端点去近似 4×4 的色块, 这等于在渲染器还没碰到之前,就已经在图集内部把颜色跨帧糊开了。 像素画贴图存成 PNG 本来就很小,用不着压。
5. 各引擎要改的设置
每个两三处,而且不在同一个地方。
Godot
导入面板:Filter 设 Nearest,打开 Fix Alpha Border。 项目设置 → Rendering → 2D:Snap 2D Transforms to Pixel。 用 TileMap 的话,图块集的 separation 和 margin 必须和你实际导出的那张图对得上。
Unity
贴图:Filter Mode 设 Point,Compression 设 None, 关掉 Mip Maps。定位那一半加上 Pixel Perfect Camera 组件。 如果用 Sprite Atlas,检查它的 padding—— 打包器不做扩边的话,就在打包前先扩好。
Phaser
游戏配置里的 pixelArt: true 会同时设好邻近采样和像素取整。 镜头缩放保持整数。用 tilemap 的话, 传给 load.spritesheet 的 margin 和 spacing 要和这张图当初打包时用的一致。
常见故障
缝随着镜头移动时有时无
亚像素定位。缩放取整数,镜头位置取整。
永远是同一条边发暗,和镜头无关
纹理渗色。给图集扩边——加间距是没用的。
我加了间距,一点变化都没有
意料之中。加间距只是让采样能捞到更多透明,不是更少。 要把间距用扩边填上。
所有东西都带一圈暗晕,单张精灵也一样
完全透明的像素里存着颜色信息。Godot 里这个修法叫 Fix Alpha Border, 别处找 alpha 渗透或者预乘 alpha。
某条边上露出了另一个精灵的一小条
从图集里相邻那一帧渗过来的。这两帧之间根本没有间距。
2 倍缩放没事,1.5 倍就坏
这就是整数倍那条规则。小数缩放从构造上就会把交界放到像素之间。