godot icon indicating copy to clipboard operation
godot copied to clipboard

Z fighting with voxel rendering

Open elvisish opened this issue 1 year ago • 47 comments


Bugsquad note: This issue has been confirmed several times already. No need to confirm it further.


Godot version

3.5.2 RC2

System information

Windows 10, Vulkan, RTX 3060 (466.81)

Issue description

When moving the camera around meshes that are stacked, flashes that appear in strips of the side edge will show.

Steps to reproduce

  • Add a box with a texture that has a lighter side than the facing side.
  • Move the camera around to observe the flickering.

https://github.com/godotengine/godot/assets/16231628/aae5436f-ff8d-47dd-951b-458d7aa27503

Minimal reproduction project

broken_seams.zip

elvisish avatar Jul 05 '23 21:07 elvisish

Some related issues: https://github.com/godotengine/godot/issues/16337 https://github.com/godotengine/godot/issues/67673 https://github.com/godotengine/godot-proposals/issues/651 https://github.com/godotengine/godot/issues/27837 https://github.com/godotengine/godot/issues/35067 https://github.com/Zylann/godot_voxel/issues/510 https://github.com/Zylann/godot_voxel/issues/96

elvisish avatar Jul 05 '23 21:07 elvisish

I had a look, the problem is as described in some of the topics you linked. There are two issues that typically cause this kind of thing:

  • Geometric gaps
  • Texture sampling

In your project, I replaced the texture with a blank unshaded material and the cracks disappeared to me, which suggested texture filtering.

You appear to be using an atlas texture. Texture sampling in GPUs is subject to numerical error, which can cause crossing boundaries from where you expect (which can be compounded with mipmapping). The solution with a texture atlas is to apply padding around the zone you intend to use, where the colors at the boundary are replicated.

https://www.google.com/search?q=texture+atlas+padding&oq=texture+atlas+padding

This same problem occurs in 2D. This is the reason we have UV contract in batching to try and counteract this, but the best recommendation is for users to apply padding in texture atlases. Godot 4 I believe natively supports auto-padding for 2D atlases. The problem also occurs with skinned mesh textures, which is why textures for skinned meshes also need padding applied, and a gap between UV islands.

This is really a consequence of how GPUs work. There's no easy (and efficient) way of fixing this with atlases except using padding, which is why you will see this same recommendation for other engines.

lawnjelly avatar Jul 06 '23 07:07 lawnjelly

While I'm not sure this is actionable from a code perspective (a contributor might get around to padding grid atlases, or 2D only as in master), it might be good to add something in the docs as this seems to come around fairly regularly.

lawnjelly avatar Jul 06 '23 07:07 lawnjelly

You appear to be using an atlas texture. Texture sampling in GPUs is subject to numerical error, which can cause crossing boundaries from where you expect (which can be compounded with mipmapping). The solution with a texture atlas is to apply padding around the zone you intend to use, where the colors at the boundary are replicated.

Here's a version using a texture for a single face and a shader that darkens each wall depending on it's facing direction: broken_seams_shader.zip

It's also exhibits the same issue:

https://github.com/godotengine/godot/assets/16231628/b4edf32e-6ecc-4acf-9ebc-708e753edb7e

The texture I used is just an imported png applied to an albedo (in both cases) and I'm not sure entirely how I'd go about padding it?

elvisish avatar Jul 06 '23 07:07 elvisish

Actually, correction, on looking at this case, there does seem to be some interaction with the shader, rather than texture padding. I'm not sure yet, will get back.

Yes it does seem likely there's something going on in the shader.

UPDATE: This is quite a weird one, and I suspect it is GPU precision issue. What I think is happening is your shader has abrupt shading depending on the normal, in your second project (above post) most of the blocks are near 0 lightness, but occasionally you are getting lines of near 1 lightness appearing. I'm suspecting that the "left / right" faces are occasionally poking through due to numerical error and you are seeing their pixels overwrite the pixels from the front facing ones.

I'm not absolutely sure on this yet, as float comparisons in shaders can sometimes give borderline quirk results. But outputting the normal as color suggests it is pretty stable.

EDIT: Yes you can also get sparklies in the editor by setting the editor viewport to: View Z near 0.01 View Z far 50000

I believe this is the same problem, the z values for the side faces are making them occasionally be drawn in front of the front facing faces.

sparklies

sparklies

The term often used for this is "z fighting". Some ideas to help alleviate it:

  • Modify your camera near and far to make best use of the z range
  • Merge and remove internal walls in your geometry

I'm not sure offhand what z mode we use for OpenGL (or whether user can change this), just in case it is causing more fighting than necessary, but I have limited time available to look at this today.

lawnjelly avatar Jul 06 '23 08:07 lawnjelly

Modify your camera near and far to make best use of the z range

I've been playing with that, but I'm using a frustum camera and it's difficult to get the fov correct while adjusting the near/far, also the far is retro-styled (short draw distance) and near is close as I don't want to clip through walls, and I couldn't find any good setting that worked for those.

Merge and remove internal walls in your geometry

I'm using gridmaps so this is impossible, unfortunately.

elvisish avatar Jul 06 '23 09:07 elvisish

You might also be able to use some hackery to get around this. Some ideas:

  • Make the cube in blender, and contract the faces just a tad so they almost match up. This may help z fighting, but in exchange you might get gaps.
  • Modify the vertex shader .. if the normal of the vertex in camera space is to the side of the camera (rather than front on), then push the vertex slightly away from the camera.

lawnjelly avatar Jul 06 '23 09:07 lawnjelly

  • Modify the vertex shader .. if the normal of the vertex in camera space is to the side of the camera (rather than front on), then push the vertex slightly away from the camera.

This might be the easiest to do, since I'm using this shader on all of my walls in the gridmap. I'm not entirely sure how to do this though (I tried some of the other vertex solutions in the threads I linked, but none of them worked, I imagine this would work differently though).

elvisish avatar Jul 06 '23 09:07 elvisish

I am not sure what workflow you are using, but the best way to get MagicaVoxel meshes into a game engine is to optimise them so that they are "watertight". You then won't get z fighting issues as it is contiguous mesh. There is a blender addon to do this conversion. You will also have a much more performant mesh if you are using lots of voxel meshes. https://www.blendermarket.com/products/vox-importer

fracteed avatar Jul 07 '23 09:07 fracteed

I am not sure what workflow you are using, but the best way to get MagicaVoxel meshes into a game engine is to optimise them so that they are "watertight". You then won't get z fighting issues as it is contiguous mesh. There is a blender addon to do this conversion. You will also have a much more performant mesh if you are using lots of voxel meshes. https://www.blendermarket.com/products/vox-importer

They're not voxels, I'm not sure why the title was changed, I'm just using MeshInstance cubes.

elvisish avatar Jul 07 '23 09:07 elvisish

Ah, no worries. I have had similar issues in the past and just ended having the cubes scale slightly below 1 at 0.99 or so. This solved my z fighting, but not the most elegant solution to be sure.

fracteed avatar Jul 07 '23 10:07 fracteed

Ah, no worries. I have had similar issues in the past and just ended having the cubes scale slightly below 1 at 0.99 or so. This solved my z fighting, but not the most elegant solution to be sure.

I did try that, along with using the Scale setting in gridmaps, but nothing has so far fixed it, the rendering just seems determined no matter what to flash the side of the cube through.

elvisish avatar Jul 07 '23 10:07 elvisish

Have you tried recreating the same scene without gridmaps, as a test, to see if it has the same issue. I am using a lot of grid snapped cubic structures in my game for a procedural city. I just use standard instancing, not gridmap and have not seen any z fighting if they are scaled to 0.99. It may be something specific to gridmap rendering?

fracteed avatar Jul 07 '23 10:07 fracteed

Have you tried recreating the same scene without gridmaps, as a test, to see if it has the same issue. I am using a lot of grid snapped cubic structures in my game for a procedural city. I just use standard instancing, not gridmap and have not seen any z fighting if they are scaled to 0.99. It may be something specific to gridmap rendering?

Yeah, this example I posted is actually just the meshes stacked, I recreated without gridmaps to see if it was gridmap causing the problem (it wasn't, it behaves identically).

scaled to 0.99

I actually haven't tried scaling down, only up (1.001, 1.01, etc), I'll try and see if it helps, thanks...

EDIT: just tried, 0.999 makes it even worse.

elvisish avatar Jul 07 '23 10:07 elvisish

  • Make the cube in blender, and contract the faces just a tad so they almost match up. This may help z fighting, but in exchange you might get gaps.

I think the problem with this would be the orientation of the cube, it wouldn't work depending on which side it's rotated, and I got even worse flickering with the gridmap cells scaled to 0.999, so I imagine that would be much the same.

A vertex shader seems to be the only solution, but it's surely overkill for what is a rendering fault? Is there no way of increasing the precision of the engine somehow? If not, I'll gladly try a vertex shader if anyone has any sugestions (I'm really beginner with shaders myself so I wouldn't know how to write this).

elvisish avatar Jul 08 '23 09:07 elvisish

Is there no way of increasing the precision of the engine somehow?

No, other than increasing the distance of the camera's near plane (which means objects very close to the camera may be culled). The default value is set quite low (0.05) – most other engines use something that would be akin to 0.1.

Calinou avatar Jul 08 '23 14:07 Calinou

Is there no way of increasing the precision of the engine somehow?

No, other than increasing the distance of the camera's near plane (which means objects very close to the camera may be culled). The default value is set quite low (0.05) – most other engines use something that would be akin to 0.1.

I have tried adjusting that and it still doesn't fix it, unfortunately.

elvisish avatar Jul 08 '23 14:07 elvisish

Does anybody know what format Godot uses for the depth buffer? Is it an 8bit UNORM/FLOAT? And I am guessing Godot doesn't store reverse depth? I am thinking this could be fixed for the majority of the screen by converting to depth to reverse floating point depth. But I am not too sure on godot's depth buffer implementation.

M0liusX avatar Jul 08 '23 19:07 M0liusX

I have a feeling this comes from the fact that we don't use invariant positions in the vertex shader. We had to disable it to work around a MESA bug.

It would be helpful if someone facing this issue could try testing with a version of the engine with the following line uncommented:

https://github.com/godotengine/godot/blob/ac5d7dc82187940a5fb2908e276cf8eb0861cac4/drivers/gles3/shaders/scene.glsl#L361

clayjohn avatar Jul 09 '23 11:07 clayjohn

Again, not too familiar with the internals of Godot, but invariant tries to prevent variance in functional expression across different shaders. But since this is the same object instance z fighting with another object of the same instance, I don't see how adding invariant will fix the issue in this particular example.

M0liusX avatar Jul 09 '23 18:07 M0liusX

The only thing I can see where adding invariant works here is because Z prepass is a different shader than just the vertex shader of the object. In that case removing Z prepass from the options should also fix it, as a proof of concept.

M0liusX avatar Jul 09 '23 18:07 M0liusX

Does anybody know what format Godot uses for the depth buffer? Is it an 8bit UNORM/FLOAT? And I am guessing Godot doesn't store reverse depth? I am thinking this could be fixed for the majority of the screen by converting to depth to reverse floating point depth. But I am not too sure on godot's depth buffer implementation.

Godot uses a 24-bit depth buffer with standard ordering (see discussion in https://github.com/godotengine/godot-proposals/issues/3539). Some GPUs fall back to a 32-bit depth buffer if they don't support 24-bit depth buffers, such as AMD GPUs on Vulkan.

Reverse floating-point has many caveats for user-provided shaders that we may not want to impose on users, so I'm not sure if it's the way to go.

Calinou avatar Jul 09 '23 18:07 Calinou

Thanks! Yeah, there is never one perfect solution. The bane with game engines. The easiest solution I see here is for the user to have grid entries without the certain side walls depending on orientation so there wouldn't be any primitives even there to cause z fighting.

Though as far as Godot is involved with fixing issues like this, I do think having different options available for users on depth buffer techniques is ideal. However, I won't hand wave the enormous work it would take to get all the advanced features that depend on reading depth values to work with the different techniques.

M0liusX avatar Jul 09 '23 18:07 M0liusX

The easiest solution I see here is for the user to have grid entries without the certain side walls depending on orientation so there wouldn't be any primitives even there to cause z fighting.

If you mean having separate mesh entries for each wall, this is actually the least easy solution I would say, it would require at least two versions of every cell which would be an organisation nightmare, along with needing to ensure they were orientated correctly. In fact there might need to be three or four versions for corner walls in both directions (should rotating upside-down cause any texture issues visually). If you mean a shader, that would be fine but I don't think there'd be any way of knowing if a cell is occluded by another and have the shader hide the face.

elvisish avatar Jul 09 '23 18:07 elvisish

You don't need a mesh for each wall just a mesh for each cube with certain walls missing. But you wouldn't even need to place them yourself. You could just place the default cube like you already have it done. Then through a script, find all adjacent cubes to every cube, and replace the grid entry with the cube that doesn't have the unnecessary walls.

M0liusX avatar Jul 09 '23 19:07 M0liusX

You don't need a mesh for each wall just a mesh for each cube with certain walls missing. But you wouldn't even need to place them yourself. You could just place the default cube like you already have it done. Then through a script, find all adjacent cubes to every cube, and replace the grid entry with the cube that doesn't have the unnecessary walls.

At least six meshes for each cube (top face only, side left face only, side left and right face only, side left, side right and bottom face only, etc) and a custom script to manually figure out which walls are adjacent on which axis to fix a floating point precision rendering bug with the engine is definitely not the easiest solution? I don't even know if I'd be able to write a script that would make that work, it's almost like auto-tiling in 3 dimension!

elvisish avatar Jul 09 '23 20:07 elvisish

I can confirm that this is still a problem as of godot 4.2-beta4

Fireflies seems to happen on some edges but not others, I dont understand why, all the edges are the same mesh all vertices are connected, normals are normal. It does not show up in blender.

No shader used just a normal BaseMaterial3D

image

knil79 avatar Nov 06 '23 23:11 knil79

As a workaround, add a plane with an unshaded black material behind the area that exhibits fireflies.

Calinou avatar Nov 07 '23 10:11 Calinou

As a workaround, add a plane with an unshaded black material behind the area that exhibits fireflies.

I don't think this is easily possible with gridmaps (or very performant)?

elvisish avatar Nov 07 '23 12:11 elvisish

I don't think this is easily possible with gridmaps (or very performant)?

Depending on how your GridMap is laid out, it may be possible to do so. You could make this black plane part of a GridMap cell itself or add a single large plane below the level. Performance-wise, rendering unshaded materials is fast.

For indoor scenes, you can also change the background color to black. For hybrid indoor/outdoor scenes, you could use an Area3D node that detects when the player is deep enough into an interior and turn the background black while also disabling the DirectionalLight3D for added performance.

Calinou avatar Nov 07 '23 12:11 Calinou