-
Notifications
You must be signed in to change notification settings - Fork 37
[Bug] 桌宠的Spine组件之间出现低Alpha缝合线的调查报告 #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
深入调查1. 契机经用户提醒,发现在早期的 ArkPets 版本中,该问题不存在。进一步的测试表明,此问题引入于 v2.4.2 的一个 Bug 修复。 在这个 Commit 中,我们将 SkeletonRenderer 的预乘 Alpha(Premultiplied Alpha,PMA)选项改成了 /* Pre-multiplied alpha shouldn't be applied to models released in Arknights 2.1.41 or later,
otherwise you may get a corrupted rendering result. */
renderer.setPremultipliedAlpha(false); 这是因为,我们发现在《明日方舟》游戏版本 未曾想到,这一针对”部分模型“的修复,反而导致”另一部分“模型的渲染出现了缝合线。 以下列举了几个会出现伪影的模型:
2. 禁用 PMA 为什么会导致缝合线的产生禁用 PMA 时, FinalColor.rgb = (Incoming.rgb * Incoming.a) + (Existing.rgb * (1 - Incoming.a)); 启用 PMA 时, FinalColor.rgb = (Incoming.rgb) + (Existing.rgb * (1 - Incoming.a)); 由此可知,”另一部分“模型会产生缝合线的直接原因是:这类模型本应该启用 PMA,但是却没有启用,由此导致
3. 如何判断是否应该启用 PMA本应禁用 PMA 却启用了 PMA 时,”部分模型“会出现伪影;本应启用 PMA 却禁用了 PMA 时,”另一部分“模型会出现缝合线。那么,判断一个模型是否需要启用 PMA 的依据是什么? 经过对原始 AB 文件的研究,可以发现,需要启用 PMA 的模型,它的纹理图的 RGB 通道和 Alpha 通道是分离的。这是因为《明日方舟》早期采用的纹理是由 ETC 编码的,而 ETC 编码模式不支持 Alpha 通道,所以需要两张图片(一张 RGB 和一张 Alpha)来进行存储。 由于在生成 RGB 通道的图片时,已经经过一次预乘 Alpha 操作了。那么,合并两张通道图(再堆叠一层 Alpha 通道)之后,我们得到的图片就是预乘 Alpha 的图片。因此,这一类纹理图片就需要启用 PMA 渲染。 而不需要启用 PMA 的模型,它的纹理图没有分离 Alpha 通道,而是直接使用 RGBA 格式存储的。这是因为《明日方舟》后来使用了 ASTC 编码来存储纹理。由于 ASTC 编码和之前的 ETC 编码相比,可以存储 Alpha 通道,因此不需要做通道分离的操作。 这样一来,和之前的”做过通道分离的图片“相比,没有做过通道分离的图片缺少了一次预乘 Alpha 的计算。因此,这一类纹理图就不需要启用 PMA 渲染。 4. 启用 PMA 为什么会产生伪影首先,没有做通道分离的这类纹理,它还采用了一个渗色技术(Bleeding)——通过将非透明像素的颜色扩展到透明像素区域,防止图像在变换和采样时出现颜色污染。 下图展示了一个使用渗色技术的纹理图(RGB 通道): 启用 PMA 时,纹理图的 RGB 值本该乘以一次 Alpha,但是却没有乘。这就会导致本该是”透明“区域,在经过变换和采样后,不可见像素的颜色影响到了可见像素的颜色。于是,伪影产生了。 为了验证”采用渗色技术的需要禁用 PMA 的纹理“会产生伪影,我们可以人工地给一个”没有渗色的需要启用 PMA 的纹理“进行渗色。 使用如下代码进行人工渗色(参考自这个 C++ 项目的实现): import numpy as np
from PIL import Image
def apply_bleeding(image: np.ndarray, remove_alpha: bool = False):
height, width, _ = image.shape
output = np.copy(image).astype(np.uint8)
status = np.zeros((height, width), dtype=np.uint8)
OPAQUE, TIGHT, LOOSE = 0, 1, 2
OFFSETS = [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)]
pending = []
pending_next = []
for y in range(height):
for x in range(width):
if output[y, x, 3] == 0: # Not opaque
is_loose = True
for dy, dx in OFFSETS:
sy, sx = y + dy, x + dx
if 0 <= sy < height and 0 <= sx < width:
if output[sy, sx, 3] > 0:
is_loose = False
break
if is_loose: # No opaque neighbor (loose)
status[y, x] = LOOSE
else: # Has opaque neighbor (tight)
status[y, x] = TIGHT
pending.append((y, x))
while pending:
for y, x in pending:
count = 0
r, g, b = 0, 0, 0
for dy, dx in OFFSETS:
sy, sx = y + dy, x + dx
if 0 <= sy < height and 0 <= sx < width:
if status[sy, sx] == OPAQUE:
r += output[sy, sx, 0]
g += output[sy, sx, 1]
b += output[sy, sx, 2]
count += 1
if count > 0: # Do color bleeding
output[y, x, 0] = r // count
output[y, x, 1] = g // count
output[y, x, 2] = b // count
status[y, x] = OPAQUE
for dy, dx in OFFSETS:
sy, sx = y + dy, x + dx
if 0 <= sy < height and 0 <= sx < width:
if status[sy, sx] == LOOSE:
pending_next.append((sy, sx))
status[sy, sx] = TIGHT
else:
pending_next.append((y, x))
swap = pending
pending = pending_next
pending_next = swap
pending_next.clear()
if remove_alpha:
for y in range(height):
for x in range(width):
output[y, x, 3] = 255
return output
if __name__ == '__main__':
path = "build_char_377_gdglow_summer#12.png"
img = np.asarray(Image.open(path))
out = apply_bleeding(img)
img = Image.fromarray(out)
img.show()
# img.save("temp.png") 渗色后的纹理,在启用 PMA 时,确实会产生同样的伪影,如图所示:
|
解决方法为了修复这一问题,使得既没有伪影出现、也没有缝合线出现,有两类解决方法:
经过反复测试,目前拟定解决方法如下: 在解包器中,如果导出模型时,遇到了没有进行通道分离的纹理,那么对该纹理进行一次额外的预乘 Alpha。 使用的代码如下: def apply_premultiplied_alpha(rgba:"Image.Image"):
"""Multiplies the RGB channels with the alpha channel.
Useful when handling non-PMA Spine textures.
:param rgba: Instance of RGBA image;
:returns: A new image instance;
:rtype: Image;
"""
img_rgba:Image.Image = rgba.convert('RGBA')
data = np.array(img_rgba, dtype=np.float32)
data[:, :, :3] *= data[:, :, 3:] / 255.0
data_int = np.clip(data, 0, 255).astype(np.uint8)
return Image.fromarray(data_int, "RGBA") 通过这种方式,我们就能把所有没有启用 PMA 的图片都强制启用 PMA。然后,我们在渲染时,全部启用 PMA 就行了。
经过 RenderDoc 工具的测试,发现非 PMA 图片的渗色已经被完全移除,如图所示: ”部分模型“和”另一部分“模型共存的效果如图所示: 可以发现,伪影和缝合线已经被完美解决了。 收尾工作将于 ArkPets v3.7 发布此修复( 08b1bdb )。之前基于 Shader 的缝合线补偿功能已被移除( 344713c )。 注意,用户必须更新模型库来获得新解包的(强制启用 PMA 的)纹理图,否则仍会出现伪影。软件和模型库的兼容性参见附表。
|
附表表 1:PMA 对渲染表现的影响
表2:各个桌宠版本的 PMA 情况
表3:各个模型库版本的 PMA 情况
表4:桌宠版本与模型库版本的兼容性
表注: |
Uh oh!
There was an error while loading. Please reload this page.
问题描述
在部分桌宠模型(大部分是较早实装的模型)渲染后,在它们的 Spine 组件之间(例如头发、眼眶和膝盖处)均可见一条缝合线。如图所示:
现象调查
1. 成因分析
因为缝合线后面的背景可以穿透过来,所以推断缝合线是由低 Alpha 值导致的。为便于观察具体的 Alpha 值分布,进行 Alpha 单通道上色后,结果如图所示:
可见,缝合线的 Alpha 值主要介于 0.5-0.95 之间,以 0.75-0.95 居多。
2. 检查 Spine 源图片
Spine 源图片采用解包后图片的 RGB-A 通道合并而生成,理论上不可能出现错误。观察 Spine 源图片,未见组件边缘的颜色和 Alpha 值异常,如图所示:
3. 检查低 Alpha 色块的 RGB 值
在片段着色器进行一些处理,使得 Alpha 大于 0.5 的部分变为完全不透明,以此检查这些部分的 RGB 值。
得到的渲染结果如图所示:
可见,缝合线的 RGB 值表现为灰度而非彩色。处理后,缝合线以灰度模式填充。
但是在游戏中,尤其是模型的膝盖部分,并不是灰度填充,而是与相邻组件具有相似颜色。
解决建议
综上所述,高度怀疑游戏中使用了特殊的着色器,消除了缝合线。为了缓解桌宠的缝合线问题,给出两种解决方案:直接将缝合线的 Alpha 值提高(正如“调查过程.3”所提及的那样),将缝合线填充为灰度。The text was updated successfully, but these errors were encountered: