OpenGL 上面 PNG 图片边缘问题的解决

之前一直苦恼于 SDL 的材质采样算法,用的是最简单的 Nearest,而且不能变。

当缩放的时候会出现大块像素,这倒还好,但是旋转变形的时候怪异的锯齿就让人非常不爽了。

nearest sampler

比如这张,放大看

在尝试无果的情况下我开始用 OpenGL 代替 SDL2 了,OpenGL 默认的重采样算法是 Linear,比 Nearest 好,不会出现这些情况。然而我用 PNG 作为材质的时候,图案透明边缘出现了问题,有白线:

linear

在网上用 “opengl png border” 之类的关键词搜索,Google 告诉我有可能是 Premultiplied alpha problem。

意思是像素有四个通道,RGB,还有 Alpha 通道,一般透明图片和不透明背景混合是这样计算的,背景的像素 RGB 乘上 1 – 透明图像的 Alpha 值,再加上透明图像的 RGB * Alpha。

Img_{rgb} *  Img_{\alpha } + Bg_{rgb} * (1 - Img_{\alpha})

然而,有些图片用一种特别的方式来储存透明,那就是图片储存的时候就 RGB 分量都预先乘上 Alpha 值,这样用原来的公式计算透明图片和不透明背景混合就会出错,表现就是多了黑边。这时候解决的方法是用这种混合公式:

Img_{rgb} + Bg_{rgb} * (1 - Img_{\alpha})

我在 stackoverflow 上提问,别人也回答我是这个原因。然而我觉得不对,因为用 nearest 滤波算法,透明是正常的,如果是这个问题,那么应该和图像滤波算法无关。(提问还被 downvote,扣了两分)

如果忽略了 Alpha 通道,我的图片是这样的:

alphaless

这样我猜测,Linear 滤波的时候有一个错误,那就是把透明像素仍然计算进来作为采样点了,这样就能解释白边和 Nearest 滤波正常的情况了。搜索了一下,一个论坛一个帖子的一个回复也说了这种情况。但是并没有提出很好的解决方法。

最后我想,可以用 OpenGl 的片断着色器自己写,网上材质滤波算法应该有现成的代码,稍微修改一下处理透明度的部分就可以解决了:

最终效果

这就是最终效果,在变形下也能正确显示,而且没有白边问题。代码在这里。处理透明度的代码在二十行……嗯,算法是抄来的。

做完了以后突然想到,如果把图片转换成 Premultiplied alpha 其实也能解决白边问题。不过想想如果要省事不自己写滤波,转换过程就要在 CPU 了,很浪费。

OpenGL 上面 PNG 图片边缘问题的解决》上有3条评论

  1. Jay

    确实关键的解法就是在texture2d之后color.rgb *= color.a就能达到pre-multiplied alpha的效果。不过你需要进一步强调指定混合模式需要是One OneMinusSrcAlpha,不然像素的alpha会在被混合一次。

    回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注