mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.73k stars 35.38k forks source link

Black/gray outline shows around transparent textures when mipmaps are used. #28238

Closed Giwayume closed 6 months ago

Giwayume commented 6 months ago

Description

minFilter = LinearMipmapLinearFilter

image

minFilter = LinearFilter

image

If a texture with transparency is drawn in front of another object, when mipmaps are in use, an unwanted outline is generated.

Reproduction steps

  1. Map a texture with a transparent background to a plane, and set up the camera so the plane is drawn smaller than the texture (so that minFilter is applied).
  2. Enable mipmaps.

Code

const texture = new TextureLoader().load(
    image.src,
    () => {  },
);
texture.encoding = sRGBEncoding;
texture.magFilter = NearestFilter;
texture.minFilter = LinearMipmapLinearFilter;

Live example

https://jsfiddle.net/b14e3ndf/

Screenshots

No response

Version

r149

Device

No response

Browser

No response

OS

No response

gkjohnson commented 6 months ago

This is an asset issue and not something three.js can fix. The texture needs to be adjusted so that the RGB values in the transparent sections are not black. I'd recommend asking at the forums for more help on how to deal with this issue:

https://discourse.threejs.org/

Giwayume commented 6 months ago

They aren't, everything that is under partial transparency is fully yellow. Only the fully transparent pixels are black.

This is expected behavior if you're using a drawing brush in something like photoshop, the brush should only affect the color of the pixels underneath it. It won't attempt to change the transparent area of the entire canvas that has nothing to do with where the brush touched.

I don't agree with the assessment that this is something that should be fixed in the asset. But if WebGL has a general issue where it is incorrectly reading values from fully transparent pixels and there is no way to fix that... guess nothing can be done.

image image

gkjohnson commented 6 months ago

They aren't, everything that is under partial transparency is fully yellow. Only the fully transparent pixels are black.

Zooming into the pixels in your image editor is not necessarily representative of what's directly in the saved asset and loaded.

I don't agree with the assessment that this is something that should be fixed in the asset. But if WebGL has a general issue where it is incorrectly reading values from fully transparent pixels and there is no way to fix that... guess nothing can be done.

It isn't a matter of disagreement - this is a common, well understood issue when linearly interpolating transparent assets and generating mip maps. It occurs in all graphics APIs, not just WebGL. The pixels in the transparent area are black so when texture samples are interpolated or mipmaps are generated the RGB channel result is mixed with black.

Giwayume commented 6 months ago

Zooming into the pixels in your image editor is not necessarily representative of what's directly in the saved asset and loaded.

It is in this case. Easy to verify. If you're talking about what's actually in memory when Photoshop loads an image like this, who knows, maybe they modify the image a bit in memory to kind of expand the colors on the border of an image with full transparency. Seems like an expensive operation to do for every single layer. Or Photoshop just renders zoomed out previews full resolution then scales down? That's also very expensive. Or, don't tell me, Photoshop avoids this artifact by rendering on the CPU...

The pixels in the transparent area are black so when texture samples are interpolated or mipmaps are generated the RGB channel result is mixed with black.

I understand the concept, but at the same time I could provide a linear interpolation algorithm that throws fully transparent pixels out and doesn't have this issue. The fact that the hardware can't do this just means the spec is trying to save on performance and doesn't consider it useful enough to avoid this artifact. No one writing a useful scaling algorithm would try to interpolate invisible pixels!

mrdoob commented 6 months ago

I don't agree with the assessment that this is something that should be fixed in the asset. But if WebGL has a general issue where it is incorrectly reading values from fully transparent pixels and there is no way to fix that... guess nothing can be done.

image image

To avoid the artifacts you're seeing the top image needs to be all yellow.

For me this was a painful concept to learn back in the 90s 😬

Edit: In Photoshop you have to edit the colors of the image separately from the alpha channel and make sure the colors are not being premultiplied when saving.

Giwayume commented 6 months ago

Premultiplied alpha is an issue for handling for semi-transparent pixels, it seems this issue is the color blending occurs across fully transparent pixels. All partially transparent pixels are already yellow. I'm working on an image editor, not a video game, so the answer for me isn't as simple as "fix your asset".

If you open the smiley face asset in Photoshop you don't see the black outline when you zoom out. Maybe the answer is they're using the CPU for scaling, either way there is a solution for display that doesn't involve modifying the fully transparent black pixels.

Giwayume commented 6 months ago

I don't want to do something like write a shader that pulls the nearest color values from alpha > 0 pixels into alpha == 0 pixels that operates on each layer. I've seen how terrible the performance of shaders are that do cross pixel reads like a gaussian blur.