godotengine / godot

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

Z fighting with voxel rendering #79081

Open elvisish opened 1 year ago

elvisish commented 1 year ago

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

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

Minimal reproduction project

broken_seams.zip

elvisish commented 1 year ago

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

lawnjelly commented 1 year ago

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:

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 commented 1 year ago

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.

elvisish commented 1 year ago

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?

lawnjelly commented 1 year ago

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:

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.

elvisish commented 1 year ago

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.

lawnjelly commented 1 year ago

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

elvisish commented 1 year ago
  • 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).

fracteed commented 1 year ago

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

elvisish commented 1 year ago

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.

fracteed commented 1 year ago

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.

elvisish commented 1 year ago

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.

fracteed commented 1 year ago

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?

elvisish commented 1 year ago

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 commented 1 year ago
  • 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).

Calinou commented 1 year ago

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.

elvisish commented 1 year ago

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.

M0liusX commented 1 year ago

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.

clayjohn commented 1 year ago

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

M0liusX commented 1 year ago

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 commented 1 year ago

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.

Calinou commented 1 year ago

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.

M0liusX commented 1 year ago

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.

elvisish commented 1 year ago

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.

M0liusX commented 1 year ago

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.

elvisish commented 1 year ago

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!

knil79 commented 10 months ago

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

Calinou commented 10 months ago

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

elvisish commented 10 months ago

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)?

Calinou commented 10 months ago

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.

elvisish commented 10 months ago

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.

If the gridmap cubes (which I'm using for example) are rotated it could cause problems, or the cubes themselves might have to be rotated so the plane appears on the correct side?

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.

Is it an issue with the background showing through or is it the other face of the "voxel"? It seems so inconsistent I can't really figure out what it actually is.

Calinou commented 10 months ago

Is it an issue with the background showing through or is it the other face of the "voxel"?

If your material has its cull mode set to Back (the default for materials created in the editor), it's the background showing through. If it's set to Disabled (the default for materials from imported glTF scenes as per its specification), it may be a backface showing through (but not always).

knil79 commented 10 months ago

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

Is there another workaround for this? I have a large outdoor cave with interior and it is very noticable when looking on the walls inside the cave, and trying to plug the holes with planes would require hundreds and hundreds of planes.

Calinou commented 10 months ago

Is there another workaround for this? I have a large outdoor cave with interior and it is very noticable when looking on the walls inside the cave, and trying to plug the holes with planes would require hundreds and hundreds of planes.

Yes, change the background color dynamically as I mentioned in https://github.com/godotengine/godot/issues/79081#issuecomment-1798380108. If you notice the color suddenly popping, smooth out the transition over time so that it's not too jarring. Many games like Minetest do that.

knil79 commented 10 months ago

Is there another workaround for this? I have a large outdoor cave with interior and it is very noticable when looking on the walls inside the cave, and trying to plug the holes with planes would require hundreds and hundreds of planes.

Yes, change the background color dynamically as I mentioned in #79081 (comment). If you notice the color suddenly popping, smooth out the transition over time so that it's not too jarring. Many games like Minetest do that.

I'm not sure I understand, you can see the sky from inside my cave and I have night and day cycle, changing the color of the sky would not be great.

It is extra frustrating that it just seems to be some triangles that it happens to, many of them are solid as rocks!

image

Another question I have is have I done something wrong when I see these fireflies? Or is this a bug? I don't see them inside blender when I create the mesh, I only see them inside Godot or in game (which also godot ofc)

Calinou commented 10 months ago

I suggest testing this change by recompiling the engine: https://github.com/godotengine/godot/issues/79081#issuecomment-1627686936

Another question I have is have I done something wrong when I see these fireflies? Or is this a bug? I don't see them inside blender when I create the mesh, I only see them inside Godot or in game (which also godot ofc)

Please upload the .blend file somewhere so we can have a look (you don't need to include textures/materials).

knil79 commented 10 months ago

I suggest testing this change by recompiling the engine: #79081 (comment)

Another question I have is have I done something wrong when I see these fireflies? Or is this a bug? I don't see them inside blender when I create the mesh, I only see them inside Godot or in game (which also godot ofc)

Please upload the .blend file somewhere so we can have a look (you don't need to include textures/materials).

I don't know how to compile myself, if someone can do it for me I will be more than willing to try it out!

My blender file HAS some holes left over inside the cave from a conversion from trenchbroom, BUT I have this position where the I get issues without a doubt, it can be a bit hard to see in inside godot, but it is very clear in game

image image

Here is the blender file (it is saved so the camera is pointed directly on to the issue area) island_tile_start.zip

Calinou commented 10 months ago

I don't know how to compile myself, if someone can do it for me I will be more than willing to try it out!

Which OS are you on?

You can find instructions for compiling here. Godot is much easier to build than the average C++ project, since it includes all the required libraries in its source tree.

knil79 commented 10 months ago

I don't know how to compile myself, if someone can do it for me I will be more than willing to try it out!

Which OS are you on?

You can find instructions for compiling here. Godot is much easier to build than the average C++ project, since it includes all the required libraries in its source tree.

I am on windows 11.

I am compiling now, but according to git(hub) that line has been uncommented a year ago... at least in the master branch

https://github.com/godotengine/godot/blame/master/drivers/gles3/shaders/scene.glsl#L299

EDIT: I have compiled from master, double checked that "invariant gl_Position;" was uncommented, which it was.

still looks like this image

jdies commented 10 months ago

Godot 4.1.1 Win 10 Vulkan

Camera near plane 1.0m Camera far plane 100.0m Grid map cell size: 1x1x1 Cube mesh size: 1x1x1

Z fighting happens with box meshes from the engine

https://github.com/godotengine/godot/assets/8107538/1248376d-31e5-4c49-b0de-95458f7fba0d

Replacing the gridmap with multiple box mesh instances has the same issue

Changing the box mesh to a plane mesh removes the issue

RobTheFiveNine commented 10 months ago

As per @jdies last post, I have been experiencing this when using the GridMap with cube tiles, including Godot box meshes with no texture applied. I've noticed it being much more noticeable than in the previous examples provided though.

https://github.com/godotengine/godot/assets/49003204/674ef6d7-6aca-4728-bed3-d3d2fe67461b

I have experienced it in 4.1.1, 4.1.2 and 4.1.3, only tested so far on Ubuntu 23.10.

Something I have found to notably reduce the effect is anti-aliasing. Specifically, enabling a combination of TA and MSAA x4 almost eliminates it. There is still some flickering if you look closely, but nowhere near as noticeable as without any (see video below for example with TA enabled and MSAA x4):

https://github.com/godotengine/godot/assets/49003204/35df93b4-b7e0-4c05-82bb-63ba6c6b9125

And as per @jdies last post - this doesn't seem to happen when using a plane mesh.

I have uploaded a sample project here that the above videos were created from: https://github.com/RobTheFiveNine/godot-gridmap-flicker-demo

chutchinson commented 9 months ago

For those using a texture atlas ("trim sheet"), I solved this problem in my project by contracting UVs by a half-pixel. I did this by attaching a custom script to my GLTF scene(s) that iterates through all mesh resources, modifies the UVs, and overwrites the mesh resources. I do not need antialiasing or multisampling in my project, but even with MSAA enabled there are no seams.

@tool
extends EditorScenePostImport

const TEXTURE_SIZE := 256.0
const TEXEL := 1.0 / TEXTURE_SIZE

func _post_import(scene):
    var mesh_tool = MeshDataTool.new()
    _iterate(mesh_tool, scene)
    return scene

func _iterate(mesh_tool: MeshDataTool, node):
    if node is MeshInstance3D:
        _process_mesh(mesh_tool, node.mesh)
    for child in node.get_children():
        _iterate(mesh_tool, child)

func _process_mesh(mesh_tool: MeshDataTool, mesh: ArrayMesh):
    mesh_tool.create_from_surface(mesh, 0)

    # iterate over faces one quad at a time
    for face in range(0, mesh_tool.get_face_count() - 1, 2):
        _contract_quad(mesh_tool, face)

    # modify mesh
    mesh.clear_surfaces()
    mesh_tool.commit_to_surface(mesh, 0)

    # overwrite existing mesh resource
    ResourceSaver.save(mesh, mesh.resource_path, ResourceSaver.FLAG_COMPRESS)

func _contract_quad(mesh: MeshDataTool, face: int): 
    var uvs = {}
    var center = Vector2(0.0, 0.0)

    # iterate over all face (quad) vertices
    for idx in range(6):
        var fidx = wrapi(idx / 3, 0, 2)
        var vidx = wrapi(idx, 0, 3)
        var vertex = mesh.get_face_vertex(face + fidx, vidx)
        var uv = mesh.get_vertex_uv(vertex)
        center += uv * (1.0 / 6.0)
        uvs[vertex] = uv

    # compute half-pixel scaling factor
    const factor = 1.0 - (TEXEL * 0.5)

    # scale each UV towards the center (contraction)
    for key in uvs:
        mesh.set_vertex_uv(key, (uvs[key] - center) * factor + center)

This script assumes:

elvisish commented 9 months ago

For those using a texture atlas ("trim sheet"), I solved this problem in my project by contracting UVs by a half-pixel. I did this by attaching a custom script to my GLTF scene(s) that iterates through all mesh resources, modifies the UVs, and overwrites the mesh resources. I do not need antialiasing or multisampling in my project, but even with MSAA enabled there are no seams.

@tool
extends EditorScenePostImport

const TEXTURE_SIZE := 256.0
const TEXEL := 1.0 / TEXTURE_SIZE

func _post_import(scene):
  var mesh_tool = MeshDataTool.new()
  _iterate(mesh_tool, scene)
  return scene

func _iterate(mesh_tool: MeshDataTool, node):
  if node is MeshInstance3D:
      _process_mesh(mesh_tool, node.mesh)
  for child in node.get_children():
      _iterate(mesh_tool, child)

func _process_mesh(mesh_tool: MeshDataTool, mesh: ArrayMesh):
  mesh_tool.create_from_surface(mesh, 0)

  # iterate over faces one quad at a time
  for face in range(0, mesh_tool.get_face_count() - 1, 2):
      _contract_quad(mesh_tool, face)

  # modify mesh
  mesh.clear_surfaces()
  mesh_tool.commit_to_surface(mesh, 0)

  # overwrite existing mesh resource
  ResourceSaver.save(mesh, mesh.resource_path, ResourceSaver.FLAG_COMPRESS)

func _contract_quad(mesh: MeshDataTool, face: int):   
  var uvs = {}
  var center = Vector2(0.0, 0.0)

  # iterate over all face (quad) vertices
  for idx in range(6):
      var fidx = wrapi(idx / 3, 0, 2)
      var vidx = wrapi(idx, 0, 3)
      var vertex = mesh.get_face_vertex(face + fidx, vidx)
      var uv = mesh.get_vertex_uv(vertex)
      center += uv * (1.0 / 6.0)
      uvs[vertex] = uv

  # compute half-pixel scaling factor
  const factor = 1.0 - (TEXEL * 0.5)

  # scale each UV towards the center (contraction)
  for key in uvs:
      mesh.set_vertex_uv(key, (uvs[key] - center) * factor + center)

This script assumes:

  • Texture atlas size is 256x256
  • Meshes are composed of exactly one surface
  • Mesh UVs are composed of quads (2 triangles per face, 6 vertices total)
  • Mesh UVs are snapped to pixels
  • Mesh resources are saved to individual files (not using inherited scene)

This looks promising, I'm using a single texture applied to simple MeshInstances in the example, might it be possible this could work using separate textures or must they be atlases?

eben-vranken commented 4 months ago

Bump, this issue is still present and it shouldn't need solutions as convoluted as these.

Calinou commented 4 months ago

@eben-vranken Please don't bump issues without contributing significant new information. Use the :+1: reaction button on the first post instead.

Kurpotato commented 4 months ago

I was searching for solutions to this problem, and found that yesterday there was an article posted about "reverse Z" coming to 4.3: https://godotengine.org/article/introducing-reverse-z/

The technical information goes over my head, but do I understand correctly that this would fix the issue with seams/fireflies?

Calinou commented 4 months ago

I was searching for solutions to this problem, and found that yesterday there was an article posted about "reverse Z" coming to 4.3:

It might help a little (particularly at long distances), but it won't resolve the issue entirely.