Zylann / godot_voxel

Voxel module for Godot Engine
MIT License
2.6k stars 245 forks source link

Are texture weights broken in the VoxelGeneratorGraph? #685

Closed NoTalkOnlyProx closed 2 weeks ago

NoTalkOnlyProx commented 3 weeks ago

Please forgive me for disregarding the standard bug report structure, I just honestly don't know where to begin with this. I have been seeing some positively buck-wild stuff.

Ultimately, I am struggling to use OutputWeight to control splat/weight data, and I've seen it fail in a very wide array of ways.

I have a simple little terrain generator, and just for testing purposes, I've been trying to use the heightmap data as a simple splatmap source.

Here is what that looks like: image

The ClampC node is set to clamp 0 to 1. So ideally we should see the layer 0 and layer 1 weights blend between each other smoothly, right?

Well, unfortunately, nope!

I wrote a very simple shader to demonstrate the problem:

shader_type spatial;

varying vec4 v_indices;
varying vec4 v_weights;

vec4 decode_8bit_vec4(float v) {
    uint i = floatBitsToUint(v);
    return vec4(
        float(i & uint(0xff)),
        float((i >> uint(8)) & uint(0xff)),
        float((i >> uint(16)) & uint(0xff)),
        float((i >> uint(24)) & uint(0xff)));
}

void vertex() {
    v_indices = decode_8bit_vec4(CUSTOM1.x);
    v_weights = decode_8bit_vec4(CUSTOM1.y) / 16.0;
}

void fragment() {
    ALBEDO = vec3(v_weights.x, v_weights.y, 0);
}

This is adapted from the example here: https://voxel-tools.readthedocs.io/en/latest/smooth_terrain/#shader

I have verified that v_indices.x == 0 and v_indices.y == 1 always, so none of what follows is due to index swapping.

This shader and terrain generator together produce:

image

This is very much NOT the expected result. I would have expected green on top, red on bottom. Or vice versa, I guess?.

We do get pure green on bottom. But what the heck is going on with those striations on the hills?

I am totally stumped!

To illustrate the problem further, let me show you what the x and y channels of weights look like individually.

Just the x channel: image

And now just the y channel: image

Notice, the y channel is dark in the same places that the x channel is! I do not know what to make of that.

I've spent a good number of hours trying to figure it out, though. here are my observations:

Observation 1: The example weight scale seems wrong?

Trawling through the source code, I see that weights are packed using encode_weights_to_packed_u16_lossy, not encode_indices_to_packed_u16.

// Encodes from 0..255 to 0..15 values packed in 16 bits. Lower 4 bits of input values will not be preserved.
inline constexpr uint16_t encode_weights_to_packed_u16_lossy(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
    return (a >> 4) | ((b >> 4) << 4) | ((c >> 4) << 8) | ((d >> 4) << 12);
}

So, all values are already divided by 16 before being packed. Therefore, the division by 255 in the example seems wrong to me. Hypothetically, shouldn't that be just a division by 16?

I have tried that, of course, but no luck. Which pretty much rules out that this is some kind of color wraparound striation. Here is the x channel again with that modification:

image

The x channel is, at least, reaching full brightness, now, though. So a step in the right direction? But then the y channel looks absurd

image

Observation 2: The Graph Editor yields inconsistent results for the same values depending on data source

Frustrated by all of this, I decided to start manually entering weight values, to try to see if I could get differing results.

But this is when things turned absolutely wild.

For instance, here is one possible way to set Layer 0 to 0.5, and layers 1-3 to 1.0:

image

Then, for this experiment, I also switch to

void fragment() {
    ALBEDO = v_weights.xyz;
}

Now, if I had made you guess, would you have guessed THIS would be the output?! image

If I, instead, use 4 constants to do the exact same thing:

image image image Yes, I got both of those on two separate occasions with this exact same configuration

I... had to do it twice because I forgot to screenshot the nodes the first time while writing this.

So yeah, WTF? Honestly it goes hard as glitch art, 100% keeping a copy of this build around.

But it gets even better, watch what happens when I add 0 to all the constants: image image

Okay, we've seen this before. But now, what if that "zero" comes from multiplying the heightmap by zero? Oh lord. image image

Finally, a bonus round: I got this at one point after I tried to reboot Godot, and it persisted until I clicked Terrain > Remesh. Terrain > Regenerate was not sufficient to clear it.

image

Hypothesis

Putting my thinking cap on, this seems like it must be some kind of concurrency issue. That is the only way I can imagine these graphs, which should hypothetically be outputting identical data, are somehow outputting completely different results.

Or am I just crazy? Am I doing something totally wrong here?

Honestly, I never even got any closer to figuring out the striations. Instead I got roadblocked by another, even worse bug. Personally I don't care too much about whether or not hardcoded weight values are achievable. I just want to have smooth splatmap generation.

Or perhaps the two issues are related?

Environment

image

NoTalkOnlyProx commented 3 weeks ago

A small addendum: I just noticed a warning about missing u_transition_mask.

I removed that from the demo shader here to keep things simple. Including it has had no effect.

MGilleronFJ commented 3 weeks ago

I haven't had the time to read through all of that in detail, but long story short: I didn't really know how to introduce texturing to the graph generator, and I tested that only once or twice a long time ago, then never actually used it. I used OutputSingleTexture more. My feeling right now is that individual weights are hard to use properly, complicated internally, don't scale well, and are expensive. I'm considering to remove them rather than trying to solve all the issues that may come from it. Especially since I'm also thinking of changing the texturing format into something less featured but simpler and lighter.

Side note, it would have been better if you separated your issue into distinct, simple cases with a minimal test project explaining what you expected and what happened instead. That would make it much easier to investigate at first... because here I have to process through a lot of different things and attempt to replicate screenshots (not everything can be seen), which isn't great (especially considering I don't even have clear memory of this area of the system myself, considering I wrote that a long time ago).

Regarding the tests where you add nodes to the graph that should not make a difference, these indeed look like bugs. Could be uninitialized memory. You could attempt to check if you get a more consistent result by turning off optimizations in the Advanced tab of the graph resource, because when an issue occurs it can often be one of these. Still, there seem to be multiple issues that could range from misuse to bugs, and they need to be taken apart separately.

NoTalkOnlyProx commented 3 weeks ago

I'm considering to remove them rather than trying to solve all the issues that may come from it. Especially since I'm also thinking of changing the texturing format into something less featured but simpler and lighter.

Ah, please don't remove it! For what it's worth, IMO, this is a core feature of a good terrain engine. I was really very glad to see that you had gone to the effort of adding it!

A simpler mode does make sense if users don't need that level of detail. Maybe an alternative texturing mode entirely? But I understand if your concern is keeping the code-base maintainable. Still, I would be very sad to see this feature go.

Side note, it would have been better if you separated your issue into distinct, simple cases with a minimal test project explaining what you expected and what happened instead.

I would be happy to split this issue up into multiple issues; I filed it as one single issue because I personally was not completely certain whether I was looking at two distinct issues, or one combined issue. I'll close this ticket, and split it into two separate tickets for you, now that you have determined this is the case.

I will also include more detailed instructions on how to set up the very basic terrain I have going, since you desire them.

You could attempt to check if you get a more consistent result by turning off optimizations in the Advanced tab of the graph resource, because when an issue occurs it can often be one of these.

Going from memory, I believe that I did try this, and it had no effect

Zylann commented 2 weeks ago

Ah, please don't remove it! For what it's worth, IMO, this is a core feature of a good terrain engine

As much as you would like it, I don't really want to maintain it. I have found it too complicated to manage. This is not as simple as what you'd see on a heightmap terrain system (the graph makes it look that way, but has a lot of work to do with the result in order to cram it into voxels; which is further "crunched" down by meshing). Voxels are volumetric, and per-voxel texturing often ends up used very differently, due to resource constraints and differences that make it not suitable for a bunch of cases. The format I chose originally is also quite heavy. If you make use of a lot of weights, it more than doubles the size of voxel chunks. I don't have the motivation to keep the weight nodes in the long term (you're also the only one to attempt using that in a very long time).

I'm considering to switch to a system where each voxel has only one material ID (up to 256 values (which could mean textures), highly compressible), and that's it. It's very simple, and seen most people rather use that. That's also what No Man's Sky uses. At least, this is what I'm inclined to work on eventually.

Keep in mind this is not the only way to have texture variation in the terrain. This is just one source of data for doing so. I would only use it if some parts of terrain need to have gameplay-related volumetric information about what voxels themselves are made of (a very common example are ore patches and dirt/rock changes; not grass, not snow). It can be combined with procedural techniques in shaders.

Going from memory, I believe that I did try this, and it had no effect

Even seeing no difference may help narrowing the issue down. It could be none of the optimization logic is involved. But it's better if the issue is simple and has a minimal test project.

NoTalkOnlyProx commented 2 weeks ago

Split to #686 and #687, keeping original post for recordkeeping