godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.8k stars 21.13k forks source link

Textures bleeding white when using an UV-based texture atlas with mipmapping #27837

Open mnn opened 5 years ago

mnn commented 5 years ago

Godot version: 3.1 64b

OS/device including version: Linux 64b, GTX 1070 (proprietary drivers v418)

Issue description:

Here is an example:

All those white tops on green blocks are supposed to be green, it is the same green texture.

I have half of texture size padding around block textures (all block textures are combined in one texture).

What I tried:

Steps to reproduce: Place a box with UV mapping limiting its texture just to a small part, increase camera visibility range, move block far away.

Minimal reproduction project:

bleeding_white.zip

Screenshot_20190409_083203

nikhilCad commented 5 years ago

Try making them unlit

mnn commented 5 years ago

In the test project there is only an ambient light. I tried checking "Unshaded" and "Do Not Receive Shadows" in material, it had no effect. Or did you mean something else?

In my game I want terrain to be able to receive light and shadows.

mnn commented 5 years ago

After some more testing, I found out it is not isolated to distant objects. It is more profound at distance, but happens at close range too, if looking at almost 0° at a surface.

Visible on leaves (the two green blocks different from other green blocks [grass]): Screenshot_20190409_110251

I had similar setup (one texture with all block textures and same padding) in Unity and the padding fixed all issues. I think Godot should support this naive approach too (I know it can be implemented without padding using shader magic, but I know very little about shaders.)

kaadmy commented 5 years ago

This is due to mipmapping, if you enable texture anisotropy this very specific issue is solved, but you may run into other mipmap issues later on or if you disable anisotropy for any reason. Using a shader to limit UV coordinates would definitely work but would require more manual material parameter adjustment. Another possibility is using a TextureArray instead, since those allow each texture to be sampled individually but should still be nearly as efficient as an atlas.

mnn commented 5 years ago

This is due to mipmapping

This happens with and without mipmaps. Am I missing something?

Another possibility is using a TextureArray instead, since those allow each texture to be sampled individually but should still be nearly as efficient as an atlas.

if you enable texture anisotropy this very specific issue is solved, but you may run into other mipmap issues later on or if you disable anisotropy for any reason

I'll try that (hopefully on Friday), thank you :).

kaadmy commented 5 years ago

If you change the import properties of the source image, nothing will happen. The filtering/mipmap flags are tied to the atlas.

mnn commented 5 years ago

When generating Image instance I passed true in use_mipmaps arg and called generate_mipmaps after image was constructed. In texture flags are ImageTexture::FLAGS_DEFAULT & ~ImageTexture::FLAG_FILTER (so mipmaps should be enabled). Is it required to be enabled also somewhere else?

kaadmy commented 5 years ago

You have to enable texture anisotropic filtering as well, I'm not sure what the constant name is, but it's in the import panel for the texture.

mnn commented 5 years ago

They were enabled by default (the default flag). I tried without mipmaps, without aniso, without both, without any flags, but the bleeding effect is still present :cry:. I'll have to take a look at that TextureArray, but implementing that won't be as easy as swapping few flags. I am not sure how it works, I hope it is not based on materials (1 mat = 1 block texture), because I don't think I could use that for a terrain for performance reasons.

kaadmy commented 5 years ago

Even with anisotrpoy, the mipmap bleeding still exists, but less visible.

Anisotropy enabled: scrot_region_20190412_100733

Anisotropy disabled: scrot_region_20190412_100728

Uradamus commented 4 years ago

So I decided I wanted to give this another go, taking @Calinou 's advice - from my closed mipmap bleeding issue above - to try generating the mipmaps manually. I went through and carefully cleaned up all of the mipmaps for a new version of the test texture in GIMP, the sub-tiles are all perfectly power of 2 aligned on each level down to around 8 or so before I just let all the remainder go to black as at that point a tile would be less than a single pixel and then exported it as dds without compression. I also changed up the mesh so that the UVs were also perfectly aligned with power of 2 edges in the texture space, which means there are no more bleed margins, so this would have to be run without filtering (or preferably closest/nearest neighbor if it were an option) to fully avoid showing any red in an ideal situation. I also enabled anisotropic, but even with all of that it still looks like complete garbage in Godot. Just a few meters out from the camera it starts to bleed.

This wouldn't just be an issue for regular atlases either, it will affect trim sheets as well which are still in heavy use throughout the industry and not going anywhere soon. Sorting this out should really ought to be a priority for 4.0 when surely more folks will start taking Godot for 3D serious and more will start to run into troubles with this problem.

Also if the Texture Arrays are to be taken seriously as an alternative to atlases to deal with this, then Godot will need to have UVs reworked to use an arbitrary number of named UV maps, instead of the currently severely limited 2 slot system that will never do in a situation where more than 1-2 array indices are needed per mesh, which would be quite often. And this is made worse if you want to bake lighting for mobile or older hardware support and got to waste one of those 2 precious slots on that. But still, even then it doesn't solve the trim sheet use case which will continue to suffer from this bleed issue even if they are used with arrays.

There is still a huge market for retro style lower poly games with pixel art textures, especially with an engine that wants to target solo, hobbyist, and newcomer developers and atlases will always be an important part of such game developers' toolsets. So I can't stress enough how important it is that some solution be found to this issue.

I have a feeling the main culprit is down to Godot only ever using linear filtering for everything, even when filtering is disabled. If we could at least get a closest/nearest neighbor option that would be a huge step in the right direction, especially for those like me who are looking for more of a low res/pixel art retro feel for their games. Though some other options like cubic/bicubic/bilinear could be explored as options that would allow some filtering for those looking to use higher res trim sheets with a bit of bleed reduction (though they will still probably suffer from it in the distance, but still far better than the current situation).

And just be aware this isn't just me, I know at least one other dev who was working on fan ports of some old PSX era games that has run up against this as well and has basically quit his project over this and is hoping it will be fixed for 4.0 so he can get back to them finally. But the way things have been going, I have my doubts which is why I wanted to make sure this got brought up again in hopes someone will actually take it seriously this time...

Calinou commented 4 years ago

I have a feeling the main culprit is down to Godot only ever using linear filtering for everything, even when filtering is disabled.

I don't understand what you mean here. If you disable filtering on textures, no filtering will be applied...

If you mean using bilinear filtering instead of trilinear filtering, you can check Use Nearest Mipmap Filter in the Project Settings.

Uradamus commented 4 years ago

That option doesn't change anything at all. It looks the same in viewport and while the game is playing whether that is on or off.

Uradamus commented 4 years ago

Testing.zip Here is the project files.

Uradamus commented 4 years ago

Here is a more practical example from my friend's project, using a map I made for him. I enabled that use nearest mipmap filter option and for the texture I disabled everything but mip maps in the first and then disabled those as well for the second. mipmaps on no mipmaps

kaadmy commented 4 years ago

I just tried running @Uradamus' testing project, and got these very different results with different versions of Godot:

Godot v3.1 stable scrot_window_20200316_094229

Godot v3.2.1 stable scrot_window_20200316_095238

I ran both versions with both my internal GPU (Intel HD 620) and dedicated GPU (NVidia GeForce 940MX) and got the same results with both GPUs.

I'm not entirely sure if this is due to some caches or something (I removed .import between runs), if so let me know.

Uradamus commented 4 years ago

I'm also running 3.2.1, your second image is the same as what I'm seeing and isn't far off from what I was seeing months ago when I started the other issue.

The idea of the test was to map the green with white border sub-tile from the atlas to the mesh, while being surrounded by all red tiles, to make it really obvious if any bleeding would occur and how bad it would be at various distances.

I've never seen results like your first image though, not sure what would cause that.

QbieShay commented 2 years ago

Hello, found this issue while working on my VFX project. it seems like enabling/disabling mipmaps causes edges to appear on my flipbooks. Screenshot from 2022-05-23 12-18-52 (amazing flipbooks from https://twitter.com/SrRafles/status/1526401269136601092)

It seems like Unity has a way to deal with this issue (at least for my own problem) with the "fix mipmap border" option:https://forum.unity.com/threads/what-does-the-border-mipmaps-setting-actually-do.26633/

It copies the border pixels from the texture into each mipmap level. So for example if the border of the texture is black, then with "border mipmaps" on, all mip levels will have black border.

Otherwise the smaller mip levels could get gray border (for example), because each level is downsampled previous level.

This approach could solve my issue, not sure about others.

QbieShay commented 2 years ago

Update: Thanks to Clayjohn and Calinou I managed to find the solution to this issue specifically. Using textureLoD based on distance instead of UV derivative solved my issue.

Calinou commented 2 years ago

Update: Thanks to Clayjohn and Calinou I managed to find the solution to this issue specifically. Using textureLoD based on distance instead of UV derivative solved my issue.

Could you post the shader code sample here, so that others can learn from it?

elvisish commented 1 year ago

I'm just using a shader material on meshinstance cubes and I'm getting the problem: image

elvisish commented 1 year ago

I've experimented with MeshInstances stacked, Gridmaps, removing my ShaderMaterial and replacing with SpatialMaterial, using Perspective and Frustum cameras, anisotropic filtering, MSAA, FXAA, etc and nothing will fix this edge bleed. I've tried scaling the meshes, which should surely solve it but nope, just flickers as if nothing has changed. Is this a depth buffer error or something? I tried changing the 32-BPC, HDR, Debanding, basically anything I can any nothing will fix it:

https://github.com/godotengine/godot/assets/16231628/ecbefc51-aa97-466a-abf4-64092bb8dd90