Linear filtering causing artifacts when rendering sprites with transparent background color

Greetings!

I’m encountering an issue with sprites where any transparent background pixels are mixed with the sprite’s opaque pixels, causing visible rendering artifacts. What’s more, the glitch happens to manifest differently depending on how the sprite is created!

Here’s what I’m doing in my app:

  • Create pixel data (and spritesheet JSON) in RGBA format after parsing a binary-encoded file (alpha is 0 for background pixels)
  • Create a RawTexture with said pixel data
  • Create SpritePackedManager using an empty string as the image URL
  • Then set its texture manually to the RawTexture created previously
  • Create a sprite instance from said sprite manager

Result:

  • The background is transparent, but there are visible artifacts at the edges
  • If I set the texture’s sampling mode to NEAREST, the artifacts disappear (at the cost of a jagged look)
  • If I create the texture from its DataURL (Base64-encoded) instead of a RawTexture, the artifacts are still there
  • BUT they use black rather than the actual background color (e.g., red)

These artifacts are presumably caused by linear filtering taking the neighboring pixel’s color despite their alpha being 0. I would expect the result be an entirely transparent background without bleeding at the edges of the visible sprite.

A comparison screenshot can be found here: https://i.imgur.com/g99gDxA.png

In the comparison screenshot, the result I want to achieve is the top left one (linear filtering, no visible artifacts).

The playground example itself (using DataURL instead of RawTexture) is located here: https://playground.babylonjs.com/#WR3TER#2

I have considered a few workarounds, but none seemed appropriate:

  • Manually alter the spritesheet’s pixel data to color the transparent (invisible) pixels differently
  • Add some shader magic to manually override the transparent pixels being mixed in (I have no idea how to do this, though)
  • I’ve also altered just about any setting I could find, to no effect. Disabling filtering is obviously not a real solution :frowning:

Surely there must be a simpler way to achieve the desired result? Maybe I’m overlooking something simple here… I hope some of you might have encountered this problem or know where to look.

Thanks for your time!

what if you turn the black to transparent on your actual png?

How do you mean? The “black” isn’t really black, it’s the RGBA color 255, 0, 0, 0 (red, but fully transparent).

It seems to only be interpreted as black (or red) by BJS because it’s invisible, though the browser renders it correctly by making it fully transparent. If I remove the transparency, it will be rendered as red by both, which is correct but not quite what I was going for :slight_smile:

You need to use the ALPHA_PREMULTIPLIED or ALPHA_PREMULTIPLIED_PORTERDUFF alpha blending mode to avoid those nasty black pixels:

https://playground.babylonjs.com/#WR3TER#3

Some docs to better understand the problem:

Note that I think the right value is ALPHA_PREMULTIPLIED_PORTERDUFF, even if there’s no visual difference between both in your case.

Also, your texture should normally by pre-alpha-multiplied for this to work correctly => in your case you don’t really need this because the alpha values are only 0 or 1 in your picture, there’s no in-between values (after a cursory glance in gimp), so in effect it’s already pre-alpha-multiplied.

2 Likes

Thank you! I had indeed tried that, minus the premultiplication of the red (transparent) pixels.

It took a while to find out, but the actual problem was that my version of BJS was too old and didn’t use the SpritePackedManager.blendMode property at all… which is why any setting I tried to apply simply wasn’t working. Ouch! I didn’t think to try it in the playground, where it would’ve worked right away.

After rectifying that, transparent pixels appeared as semi-transparent with otherwise no visible edges when using ALPHHA_PREMULTIPLIED_PORTERDUFF. Setting the RGBA (255, 0, 0, 0) to RGBA (0,0,0,0) fixed this, presumably since red * alpha = 255 * 0 = 0 was the premultiplied color?

1 Like