Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transparency problems #621

Open
ddel-rio opened this issue Apr 4, 2024 · 11 comments
Open

Transparency problems #621

ddel-rio opened this issue Apr 4, 2024 · 11 comments

Comments

@ddel-rio
Copy link
Sponsor

ddel-rio commented Apr 4, 2024

Describe the bug
I am trying to replicate the behavior of Minecraft transparent objects using this engine since some time ago.

To reproduce
In the attached MRP there are 3 types of transparent objects: water, leaves and glass.
See the material configurations and models in the library and how they interact in the game.

mrp.zip

Expected behavior
I would especially like to be able to place transparent objects under water without them disappearing.
In the screenshots section I make a comparison of the current behavior with that of Minecraft.

Screenshots
Captura de pantalla 2024-04-04 140225
Captura de pantalla 2024-04-04 140318
Captura de pantalla 2024-04-04 140837
Captura de pantalla 2024-04-04 140913

Environment

  • OS: Windows 11
  • GPU: NVIDIA GeForce RTX 4070 Laptop
  • Godot Version: v4.3.dev.custom_build [f6a78f83a]
  • Module Version: a0628c7
  • Renderer: Vulkan (Forward+)
@Zylann
Copy link
Owner

Zylann commented Apr 4, 2024

You normally control this via transparency_index and culls_neighbors.
The former might need a documentation update, because the way it works is to make the face of a model visible if the neighbor model has a higher transparency index (instead of just testing if it's different).

In your demo, water has a lower transparency index (1) than glass (3) and leaves (2), therefore their sides will be culled.
To obtain Minecraft result, you could set water to 2, glass to 1, and leaves to 0, while also making leaves use a non-transparent material, because Minecraft uses alpha clip instead of transparency for leaves. Leaves may also need to have cull_neighbors set to false, as is the case in Minecraft.

The first screenshot shows a different problem which I'm not sure how to fix. Godot uses a relatively primitive transparency sorting to render transparent objects. The way it does that means it sometimes causes issues where one shows on top of the other even if it's actually behind. Chunks batch each material into a single mesh surface, so if glass and water are using the same material, they will be part of the same surface, and Godot might be unable to sort them, so whichever the graphics card draws last will show on top of the other (but I'm unsure if it will even with different surfaces).
Vanilla minecraft actually has similar problems in some situations when looking at certain angles.

@ddel-rio
Copy link
Sponsor Author

ddel-rio commented Apr 5, 2024

The first screenshot shows a different problem which I'm not sure how to fix. Godot uses a relatively primitive transparency sorting to render transparent objects. The way it does that means it sometimes causes issues where one shows on top of the other even if it's actually behind. Chunks batch each material into a single mesh surface, so if glass and water are using the same material, they will be part of the same surface, and Godot might be unable to sort them, so whichever the graphics card draws last will show on top of the other (but I'm unsure if it will even with different surfaces).
Vanilla minecraft actually has similar problems in some situations when looking at certain angles.

Although I prefer that this error does not occur either, to obtain a minimum quality in my game I would be satisfied if I can solve the following two problems:

1 - Transparency Index Priority

The former might need a documentation update, because the way it works is to make the face of a model visible if the neighbor model has a higher transparency index (instead of just testing if it's different).

I think because of this logic I will never be able to get the desired result.
Because although I can see the glass through the leaves, I cannot see the leaves through the glass.

Captura de pantalla 2024-04-05 210534
Captura de pantalla 2024-04-05 210559

2 - Water Z-Fighting
In order to see the limit of the water surface while diving I have had to set the "cull mode" property of the water material to disabled.
But now some transparent objects cause Z-Fighting and I have no idea how to fix it.

Captura de pantalla 2024-04-05 210637

MRP
I have simplified the mrp a lot to be able to test exclusively transparency interactions.

mrp.zip

@ddel-rio
Copy link
Sponsor Author

ddel-rio commented Apr 5, 2024

I've been thinking and have come up with two possible solutions that might work, at least temporarily until better solutions are found:

  1. Make the face visible if the neighbor's transparency index is different instead of lower.
    Or give the option to choose between the two modes.
  2. Add an exceptions property to models.
    An array of indexes of the library blocks.
    For example: If I am water and the neighbor's face is leaves, I am not going to draw my face because the leaves are on my exceptions list.

@Zylann
Copy link
Owner

Zylann commented Apr 6, 2024

although I can see the glass through the leaves, I cannot see the leaves through the glass.

Except, as I mentionned, leaves should have lower transparency index, and don't cull neighbor faces either using the cull_neighbors property to false. This way, leaves should remain visible through glass, water and other leaves.

Make the face visible if the neighbor's transparency index is different instead of lower

I'm not sure yet about changing that, as it might cause other problems elsewhere.

Add an exceptions property to models

I want to avoid adding more exceptions if possible, especially if that involves large lookup tables for the algorithm to fetch. This logic is quite sensitive because it runs in a very calculation-intensive part, so the simpler it is, the better. If the former solution (changing > to !=) works for you, do you still need the latter?
If you can build the module yourself, you can try changing it here to check if it meets all your needs:

if (other_vt.empty || (other_vt.transparency_index > vt.transparency_index) || !other_vt.culls_neighbors) {

In order to see the limit of the water surface while diving I have had to set the "cull mode" property of the water material to disabled. But now some transparent objects cause Z-Fighting and I have no idea how to fix it.

Currently this will happen with leaves if they have cull_neighbors set to false, which was the solution found to fix #546. But in the case of water it seems Minecraft still culls backfaces specifically, while somehow keeping front faces, while also having both front and back faces for water facing air (non-waterlogged leaves):
image
So I'm not sure if disabling backface culling entirely is a solution here, because it would prevent from doing this. Perhaps water models should contain both front and backfaces (as in really two quads per face), and if the cull_neighbors property is set to false, back faces of neighbors should remain culled?
If that's the way to go, doing this change would require some changes in both the baking and meshing process in order to separate the handling of front and back faces.

In cases models are such that the water faces are legitimally neither culled front nor back, Z-fighting could happen either way. But in Minecraft, this happens (non-waterlogged stairs):
image
Notice the thin gaps at the edges (apart from the unfortunate overlaying of selection outline). It turns out water models not only have both front and back faces, but also have faces that are very slightly offset inwards, just to avoid Z-fighting at close range:
image
(I have a slightly modded version of the game so I don't know if Vanilla does something different)
That is probably doable in the model itself without change to the module, but might affect the ability to detect side triangles properly. If that turns out to be a problem, maybe a side detection margin property could be introduced.

For glass, there would be another issue: with transparency indices doing >, water can have an index higher than glass, therefore glass culls water and would still avoid the issue here. But if the check was != like you suggested, then neither glass nor water would get culled and they would Z-fight. And if you made them equal, they would cull each other which is not wanted either.

Looking at your second MRP:
I changed leaves transparency_index to 1 and glass to 2, so now leaves can be seen through glass (and glass remains visible through leaves):
image

@ddel-rio
Copy link
Sponsor Author

ddel-rio commented Apr 6, 2024

Take a look at this configuration:

mrp.zip

It's almost perfect, but it has 3 problems:

  1. I have to choose whether when I look through the glass or the leaves I always see the face of the leaves or the glass.
  2. On very rare occasions I seem to see z-fighing on a glass that is in the water if I see it from outside the water.
  3. The leaves have cull neighbors enabled.

I honestly wouldn't mind continuing my game with this configuration.

In any case, the fact that I cannot set the cull neighbors of the leaves to disabled is what hurts me the most because I feel that the game loses a lot of visual charm.
But if I disable it, serious z-fighting problems begin, so I prefer this.
I would like to leave the thread open because I think the logic of the cull neighbors property should be reviewed.

The second problem is very hard to see so I don't care.
And the first one, although it is not ideal, I am going to choose to always show the leaves, I don't care that much either.

Some screenshots:
Captura de pantalla 2024-04-06 142618
Captura de pantalla 2024-04-06 142628
Captura de pantalla 2024-04-06 142736

I have also tried using a shared material and using Depth Pre-Pass instead of Alpha but some serious problem always appeared.

This article could be interesting for this topic:
https://docs.godotengine.org/en/stable/tutorials/3d/3d_rendering_limitations.html#transparency-sorting
Maybe what needs to be fixed is godot and not the voxel engine.

@Zylann
Copy link
Owner

Zylann commented Apr 6, 2024

I have to choose whether when I look through the glass or the leaves I always see the face of the leaves or the glass.

I don't follow. You want to see both grass and glass on either side? Minecraft doesn't do that and this is a recipe for Z-fighting too.

On very rare occasions I seem to see z-fighing on a glass that is in the water if I see it from outside the water.

I don't believe seeing underwater glass from outside water specifically causes Z-fighting. That would either be caused by transparency sorting, or the fact that your configuration makes water faces not be culled next to glass and then material settings trigger actual Z-fighting (which would show up regardless of being inside or outside water).

The leaves have cull neighbors enabled.

the fact that I cannot set the cull neighbors of the leaves to disabled is what hurts me the most

In Minecraft, leaves don't have it enabled. Why can't you disable it? Do you mean you actually want it to be enabled, so that leaves cull other leaves?

if I disable it, serious z-fighting problems begin, so I prefer this.

Which ones? I tried disabling it, and the only Z-fighting issue was caused by water, I already described how it could be handled. But otherwise I'm not sure.

I would like to leave the thread open because I think the logic of the cull neighbors property should be reviewed

It's still unclear as to how it should be reviewed though. One actionable change would be the way to handle separate checks for backfaces on some models like water, but apart from that I'm not decided. I would need very specific cases (but constraints should remain reasonable, the reference being mainly Minecraft, and I would like to keep baked checks as simple as possible).

@ddel-rio
Copy link
Sponsor Author

ddel-rio commented Apr 6, 2024

Ideally you should be able to see the face of the glass through the leaves and the face of the leaves through the glass, like in minecraft:
Captura de pantalla 2024-04-06 165050
Captura de pantalla 2024-04-06 165126

But in my current configuration (the best at the moment although I am still researching a better one) I have to choose:

  1. Case A
    Captura de pantalla 2024-04-06 142618
    Captura de pantalla 2024-04-06 142628
    Leaves cull mode = disabled
    Leaves transparency index = 1
    Glass cull mode = back
    Glass transparency index = 2
    If I swap the indices, case D occurs.

  2. Case B
    Captura de pantalla 2024-04-06 170852
    Captura de pantalla 2024-04-06 170902
    Glass cull mode = disabled
    Glass transparency index = 1
    Leaves cull mode = back
    Leaves transparency index = 2
    If I swap the indices, case C occurs.

I have chosen to always see the leaves because with the case B, render priority problems occur.
Surely this doesn't happen with the leaves because they are Alpha Scissor.
Captura de pantalla 2024-04-06 170916

I always have to put one of the two materials with cull mode = disabled because if not the faces disappear depending on which side you look from:

  1. Option C
    Captura de pantalla 2024-04-06 171804
    Captura de pantalla 2024-04-06 171810
    Leaves cull mode = back
    Leaves transparency index = 1
    Glass cull mode = back
    Glass transparency index = 2

  2. Option D
    Captura de pantalla 2024-04-06 171846
    Captura de pantalla 2024-04-06 171840
    Glass cull mode = back
    Glass transparency index = 1
    Leaves cull mode = back
    Leaves transparency index = 2

This is what happens to me when I disable cull neighbors.
https://github.com/Zylann/godot_voxel/assets/57621742/61dab1a6-2b31-4674-91bb-58da00965ab9

In my mind what should happen is that you can see the faces of the leaves if the neighbors are also leaves.
So far so good.
But for some reason it is producing Z-Fighting with the water and not with the glass.

I don't know what the ideal solution may be, I am a humble programmer with no studies XD.
For the moment I will try to provide evidence of what is happening and if an idea occurs to me I will comment on it.

I also want to try modifying this line, as you suggested above, to see if it works in my game.

if (other_vt.empty || (other_vt.transparency_index > vt.transparency_index) || !other_vt.culls_neighbors) {

@Zylann
Copy link
Owner

Zylann commented Apr 6, 2024

Ideally you should be able to see the face of the glass through the leaves and the face of the leaves through the glass, like in minecraft

Once again, as I showed you, currently you can, if you use:

  • Leaves: cull_neighbors=false, transparency_index=1, default material culling mode (back)
  • Glass: cull_neighbors=true, transparency_index=2, default material culling mode (back)

This configuration lets you see glass through leaves, leaves through glass, and leaves will keep neighbor faces when connected to other leaves producing the same volume effect as Minecraft.

image

cull_neighbors was really just added to behave the way Minecraft does, where front faces of neighbor blocks are never culled by leaves.

But in my current configuration (the best at the moment although I am still researching a better one) I have to choose:

Case A

Leaves cull mode = disabled
Leaves transparency index = 1
Glass cull mode = back
Glass transparency index = 2
If I swap the indices, case D occurs.

You have changed material culling modes, which is not necessary if the goal is to replicate Minecraft, and you haven't turned off cull_neighbors on leaves, so of course you won't get what Minecraft does. You will see leaves backfaces but not glass because leaves would cull glass due to cull_neighbors being true.

Case B

Glass cull mode = disabled
Glass transparency index = 1
Leaves cull mode = back
Leaves transparency index = 2
If I swap the indices, case C occurs.

You have changed material culling modes, which is not necessary if the goal is to replicate Minecraft, and you haven't turned off cull_neighbors on leaves. You will see glass backfaces but not leaves because glass would cull leaves due to having lower transparency index.

I always have to put one of the two materials with cull mode = disabled because if not the faces disappear depending on which side you look from:

Case C:
Leaves cull mode = back
Leaves transparency index = 1
Glass cull mode = back
Glass transparency index = 2

That case is still not going to work until you disable cull_neighbors. You left it enabled, so like case A, leaves will cull glass front faces so you won't see glass through leaves.

Case D:
Glass cull mode = back
Glass transparency index = 1
Leaves cull mode = back
Leaves transparency index = 2

And same here in reverse. cull_neighbors is still not disabled and indices are inverted so glass culls leaves instead.

This is what happens to me when I disable cull neighbors.

What you are experiencing when you disable cull neighbors is about how water is handled. This is a separate problem that can be solved as I described, having a separate handling of backfaces. Currently you are trying to workaround it by changing material settings and disabling backface culling, but that's not the right way to go if the goal is to replicate Minecraft.

In my mind what should happen is that you can see the faces of the leaves if the neighbors are also leaves.

And that's what cull_neighbors=false was intended for. And Minecraft does behave like that: water front faces are not culled by leaves, just like the front faces of other leaves (or anything else) is not culled by leaves.

But for some reason it is producing Z-Fighting with the water and not with the glass.

It Z-fights in water because you are seeing water backfaces (as you also turned off backface culling in water material), which currently are considered the same as front faces by the mesher. What I already described in my previous post, was to separate the handling of front and back faces so they can be culled with their own settings. Doing this means to include both front and back faces in the water model itself instead of changing the material, because otherwise it's impossible to deal with it conditionally (Minecraft renders water such that you can sometimes see front and back faces, but sometimes you only see front faces). In the case of leaves, that means leaves would never cull neighbor front faces, but would still be able to cull backfaces. So looking at leaves from underwater would not show Z-fighting water on top, yet looking at water through leaves would still show water front faces (unless waterlogged; your screenshots show waterlogged leaves, mine dont).

In addition to this, having a tiny offset of water sides would prevent Z-fighting at close range since in some cases water backfaces would have to show up anyways (which almost repeats what I said in my previous post)


Regarding transparency sorting, it's a rabbit hole really.
Here are more Minecraft screenshots:
image
image
image
Just... use alpha clip glass :D

@ddel-rio
Copy link
Sponsor Author

ddel-rio commented Apr 7, 2024

Sorry, I was hell-bent on doing it all through materials, cull neighbors and transparency index.
By talking to my artist, reviewing the information you have provided, and doing experiments in Minecraft I believe I have come to a greater understanding of this topic.

Specifically, your second post is of special relevance regarding the idea of handling the front and back faces of the water separately.

According to what I have observed in Minecraft, the water mesh has this shape #.
Captura de pantalla 2024-04-07 144446

In the following image I show you an exaggerated version of what Minecraft does.
On the left is the water block and on the right is the surface block.
The surface of the water is the same mesh as that of the water block, except that the upper face is not displaced towards the interior of the cube.
Additionally, each face is made up of two quads, one facing outward and one facing in, as you suggested above.
Captura de pantalla 2024-04-07 150708

Given this evidence, I think that the implementation of the new property called perhaps "cull margin" can be beneficial for the engine to better recreate the functioning of Minecraft.

However, there is one question that I cannot resolve.
If we take as an example the following block of leaves with water:
Captura de pantalla 2024-04-07 145340

How does Minecraft know to just erase the water faces when you place the block in the ocean to get the following result?
Captura de pantalla 2024-04-07 151948
Perhaps a block should be allowed to have multiple transparency indices?

@Zylann
Copy link
Owner

Zylann commented Apr 7, 2024

How does Minecraft know to just erase the water faces when you place the block in the ocean to get the following result?

Lateral and bottom faces will be erased due to normal culling rules, because they technically touch (within side cull margin).

However, I don't know about the top face. Transparency indices would not solve that one because it's not considered a "side" of the block, in turn because it's not even ON a side, it is noticeably lower. Minecraft being "its own engine", I wouldn't be surprised if there was a bold special case just for these blocks in their mesher (and in general they can do that without much thinking for highly game-specific reasons, which I can't do as easily without making my own project game-specific as well).
Right now, to handle this you would have to create a separate model for this case, which you would update in the terrain when the voxel on top of it changes: voxel on top contains water? Switch to "fully waterlogged". If not, switch to "having space on top".


On top of that, fluid block rendering itself may be a lot more complicated than regular models. Because not only water raises and lowers top vertices procedurally based on neighboring levels, it also does that for neighboring waterlogged blocks:
image
That would require a tremendous amount of models, so it's possible that this special case is handled by that same procedural code rather than static rules.

I considered a few options for fluid blocks:

  • VoxelMesherScript and VoxelMesherComposite, pretty much exposing a way to implement custom meshers in scripts and a way to compose multiple meshers "adding" onto the same final mesh. Most radical, but scripts will almost always be too slow for this, unless maybe using C# or a native language.
  • VoxelBlockyModelProceduralScript: have a script run to mesh specific models from within the 3D for loop of VoxelMesherBlocky. More specific and constrained, but still relatively problematic due to the slowness of scripts and its insertion within high-performance code, which would have to drop several optimizations just to potentially expose stuff to such scripts.
  • VoxelBlockyFluid, VoxelBlockyModelFluid and fluid_logged property on models. This one would be an opinionated implementation of fluids meshing and "waterlogged" models directly within the engine. "opinionated" meaning that it would be less customizable than a script, due to picking one approach and not supporting certain things, either on purpose or lack of implementation. Due to how models work, there would still have to be one fluid model per level so that they are given their own index and settings (if you have 16 levels, 16 fluid models. If you have 100, then 100 fluid models).

These are only ideas though, I haven't fully thought on how the mesher would handle these while staying fast.

Although at this point it's moving away from the topic of "transparency problems", and this is not stuff I'm planning yet in the near future, as I have other things in progress.


Generally, just want to note that if you planned to replicate Minecraft in all its aspects, there is a very long way to go. This engine offers a base, but not a full implementation of everything Minecraft does. You will find water is only a tiny fraction of all the things that would be required, and yet this engine isn't entirely focused on that kind of terrain, so even if you opened an issue for everything it will take a very long time for everything to be addressed, and sometimes you might have to write native code or tweak the engine (not saying nothing will happen, just being realistic about the state of things).

@ddel-rio
Copy link
Sponsor Author

ddel-rio commented Apr 7, 2024

Generally, just want to note that if you planned to replicate Minecraft in all its aspects, there is a very long way to go. This engine offers a base, but not a full implementation of everything Minecraft does. You will find water is only a tiny fraction of all the things that would be required, and yet this engine isn't entirely focused on that kind of terrain, so even if you opened an issue for everything it will take a very long time for everything to be addressed, and sometimes you might have to write native code or tweak the engine (not saying nothing will happen, just being realistic about the state of things).

I am aware of this and it is also not my intention to recreate exactly everything that Minecraft does.
However, I think it is positive to at least point out possible aspects to improve in the engine, even if they are not going to be implemented or will take time to do so.
There will be other features that may have much more priority right now than these things so I am realistic and not demanding in this sense.

I am also able to verify that this engine is powerful enough in its current state to be able to make a game once you understand how to control it.

To exemplify this I would like to share again the mrp with the best configuration I have achieved (more than enough to have a very good quality in my game):

mrp.zip

The water surface is double-sided.
This way it is not necessary to touch the material or the cull mode so the Z-Fighting problems are fixed and I can see the surface from inside and outside the water.

Captura de pantalla 2024-04-07 160302

I think this project has become a good example of how to configure transparencies so that they look good in a game.


I would like to share a trick that I have discovered.
If you have render priority issues (in my case with glass):

2024-04-07.16-25-19.mp4

You can solve this by setting the material transparency to Depth Pre-Pass and the render priority to -1.
Captura de pantalla 2024-04-07 162757

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants