Zylann / godot_voxel

Voxel module for Godot Engine
MIT License
2.66k stars 250 forks source link

Redrawing occasionally shows brief holes in the terrain #57

Open TokisanGames opened 5 years ago

TokisanGames commented 5 years ago

Often when modifying a terrain, an update draws an artifact that breaks the illusion of being deep inside the terrain. It's quick, but not imperceptible even at high framerates. If you turn on vsync and/or multithreading the redraw speed is slower and they are even more noticeable.

In all three examples below, I'm digging deep into a mountain, yet one frame shows light peeking through, then it disappears. In the first example, this might have happened on two sections simultaneously. Most edits draw fine, but I could reproduce the artifact in less than a minute.

It only occurs during the redraw of the edit. Once the edit completes it's fine. It's like it deletes the mesh, then gets hung up long enough for the renderer to draw the frame as is, then finally creates the new mesh in the next frame.

image

If you want to see it, you'll probably want to use my demo (in my PR) so you can test high amounts of changes. I've been regularly updating it and it does a pretty good job of keeping the camera within the terrain. See the readme for controls, but right-click destroys terrain.

TokisanGames commented 5 years ago

This is with the latest update, including the voxelblock fix you did today.

Sometimes the redraws happen over two frames. Here the lower blue is an actual hole I've dug through. The upper left blue shouldn't be there. You can see in the last two frames the ceiling fills in there:

voxel artifact

Zylann commented 5 years ago

I suspect this is happening because the edit you made is intersecting more than one block. In the worst case, there are 8 blocks to remesh if you do this at an intersection. Because that's an asynchronous process, the re-meshed results aren't guaranteed to come back at the same frame. So you will see through the terrain for a very brief moment. It is more likely to happen if the process of meshing and creating the Mesh resources takes longer than a frame.

No mesh get deleted here (to my knowledge at least). The holes are just the consequence of seeing removed voxels in one mesh, but the neighbor mesh hasn't been updated yet so you are seeing its insides.

I think this is also happening in Minecraft, especially when using TNT. I also saw that in Minetest as well (tested just now).

Something we could try to minimize this would be to set request batching to 8 in the updater, so that it will always process by packs of 8 and come back all at once, but the main thread creation of meshes could still cause it to happen over two frames. It could be told to run more as well, but you'll get stuttering. So other possibility would be to hold on before updating the meshes, which would make updates feel a bit slower to come.

TokisanGames commented 5 years ago

Using the VoxelTool::do_sphere in C++ has reduced artifacting significantly at a sphere radius of 3.5. However at a radius of 10.5, which isn't huge, it is very common and obvious.

Zylann commented 5 years ago

At such a large radius it becomes really hard to expect this to not happen. radius of 10 can span across 3x3x3 blocks (27) which would all have to complete remeshing during a single frame.

TokisanGames commented 5 years ago

It's not too big though. My character is 2.5 units tall. I mean big to me would be like 100.... speaking of which trying it out hmm it doesn't work. But 50 works and it's interesting. Here the redrawing looks kind of cool, kind of matrixy.

image

Zylann commented 5 years ago

I mean big to me would be like 100.... speaking of which trying it out hmm it doesn't work

LOD terrain has a limitation of how large the edited area is, because you can only edit within a margin of LOD0.

nic96 commented 4 years ago

I have this same issue even when destroying just one block at a time in blocky terrain. Would it be possible to run the mesher first as if the blocks that are supposed to be destroyed were like transparent blocks where other faces get rendered behind first?

Zylann commented 4 years ago

Destroying a block is instant. There is no "first" to speak of. You could only do that kind of workaround if it took time to destroy, which introduces complexity which you could handle in game logic. Solutions you could use in a script include temporarily replacing the mined cube with a transparent variant, like you said, and reverting it if the player decides to stop before it's destroyed. Or better, place a MeshInstance variant of the cube during one frame. Or the other way around, using a mesher to polygonize a 3x3x3 area without the cube in the main thread, and show that mesh just for one frame from the moment you destroy it, to cover eventual holes. That covers the issue for single block scenarios.

Otherwise, so far I really haven't a generic fix for this issue that doesn't come with drawbacks. When destroying single blocks It's possible to make it less likely to happen but latency issues will come up as I described here https://github.com/Zylann/godot_voxel/issues/57#issuecomment-526712761. To sum it up, the cause is that blocks are updated asynchronously. Because of threading, and because their main thread stage is staggered to avoid stuttering. One variable to tune easily the main thread part would be this: https://github.com/Zylann/godot_voxel/blob/a24b33eca1e2373dad3236edbe104fd5f5f28d61/terrain/voxel_terrain.cpp#L14 . If Godot can't handle 8 mesh updates in 8 milliseconds, then there is a chance to see a hole briefly if you are at a block boundary. Increasing this gives it more time, but also means the game has a higher chance to stall. If that still doesn't fix it, a brutal fix would be to force updates at close range around the player to also be processed immediately, in the main thread. So even more stalling, but it would do it all in one go.

With more blocks being destroyed it's much harder to avoid. It's also worth noting that Minetest and Minecraft still have the same problem to this day, which is partly covered with smoke particles. If you just want to cover up the case of single blocks being removed, game logic solutions described above sound like less trouble. (note: always test this at least in release_debug mode, it's totally possible debug has this the worst).

nic96 commented 4 years ago

I like the idea of replacing the block with a MeshInstance cube for one frame. Anyway this gives me some ideas for things to try. Thanks