Zylann / godot_voxel

Voxel module for Godot Engine
MIT License
2.59k stars 244 forks source link

Heightmap banding #73

Open darrylryan opened 4 years ago

darrylryan commented 4 years ago

I notice that there's noticable banding/terracing in the terrains when heightmaps are used but the procedural generation results in a smoother mesh. Could this support .exr perhaps to allow a better range of different heights or is there some other reason that happens?

zauberparacelsus commented 4 years ago

Yeah, that terracing issue is a fundamental problem with 8bit heightmaps. I've seen it in another program too when I used PNG for the heightmap. In that case, I worked around the issue by running a smoothing filter on the terrain. If smoothing is possible in godot_voxel, that could offer a workaround until higher-bit image formats are supported.

Zylann commented 4 years ago

Some of the bumpiness of the terrain in the demo project is due to 8-bit ranges, but you could easily use an EXR already, because Godot supports this format. But I believe the 8-bit range isn't the only issue.

Terracing is actually caused by two similar problems:

So this could be down to fixing the way those streams sample their volume function.

Zylann commented 4 years ago

I just experimented with an idea to improve the conversion from a heightmap to a 3D distance field. The idea is, when sampling a given 3D point, get the height directly at XZ, and the 8 neighbors. Calculate the distance to 8 segments between middle and each neighbor, then pick the closest. It works within a 3x3 area centered on the sample, because our voxels only need to represent values between -1 and 1 anyways. So even if a sample is 100 units above ground but there is a cliff 2 units away, it won't matter because the distance is higher than 1 (of course it remains an approximation).

Results

With blurring technique: image With segment technique: image

It also has the effect of producing more correct results when the heightmap contains rectangles of uniform height. image

With blurring technique: image With segment technique: image

There is still terracing for 2 different reasons:

Zylann commented 4 years ago

A new option is available in 3ead96f06e80b7295b335826d8b2eb9bbce4768a

TokisanGames commented 4 years ago

It's interesting. While it makes those towers more accurate to the heightmap, it makes a lot of the ground on the hills spiky now.

Zylann commented 4 years ago

I didn't notice spikes. Screenshot? (LOD > 0 doesn't count)

TokisanGames commented 4 years ago

LOD 0. Not artifacts. Just the slope of hills. Instead of being smooth now they are covered in small pointy bumps maybe 10 “cm" tall, so my character bounces a lot more as he traverses the terrain. Going to bed, but I can post a screenshot later.

Zylann commented 4 years ago

I bet this is just due to the lack of precision in the heightmap image itself. With a blurred technique it's less bumpy because that's what blur does (uniformly, not adaptatively), but with the segment technique it's more accurate... and the truth is the image isn't accurate so it better reflects its... innacuracy, if that makes sense^^ Would be interesting to try this with an EXR.

At this point getting rid of banding completely would be to:

Zylann commented 4 years ago

Breaking news.

image

What you are seeing is the same 8-bit image heightmap using only simple VERTICAL SDF mode. Terracing is gone. (in very large part). The secret? It was there all along... in VoxelStreamNoise3D. I added iso_scale = 0.1 to the distance field and now everything looks much smoother. Will add this to heightmap generators soon.

There is a small bad news though: the segment method I thought was really good somehow doesn't like this. But given results we can get now with vertical or blurred modes, I'm thinking of removing it.

Note: I'm pretty sure if we feed the normal map from the source heightmap to the shader, we can make this terrain look as detailed as a classic heightmap one, fading it in above some distance :p As seen on this old screenshot from my heightmap plugin: vertex_to_pixel

TokisanGames commented 4 years ago

(me) it makes a lot of the ground on the hills spiky now.

@Zylann I didn't notice spikes. Screenshot? (LOD > 0 doesn't count)

(me) Not artifacts. Just the slope of hills. Instead of being smooth now they are covered in small pointy bumps maybe 10 “cm" tall, so my character bounces a lot more as he traverses the terrain.

Sorry, forgot to post. Here's the current heightmap generator without using the blur option. These spikes used to not be there. It looks so strange and unlike the actual heightmap. What good is the non-blur, spiky option?

image

With the blur option it looks like it did with my old November build. Still kind of spiky, but at least not worse than before.

image

TokisanGames commented 4 years ago

I've seen more subtle terracing happen in far away blocks even with VoxelStreamNoise: image The cause of this one is less clear to me...

image

I still see this banding on distant LODs using OpenSimplexNoise as of b1dc8d37625f29. E.g. Obvious if you set OSN period to 256, persistence to 0.15. I also get it on FastNoise (upper picture).

I found that if I scale the iso_scale by the LOD it largely fixes the banding issue, but introduces another. (lower picture)

//FastNoise
const float iso_scale = 20.0 / (lod + 1);

// OpenSimplexNoise
const float iso_scale = noise.get_period() * 0.1 / (lod + 1);

Without the divisor, we get ugly terracing, weird lips between chunks, and the occasional white dot:

image

With this specific divisor (/ (lod + 1)), it mostly eliminates the banding, and makes the transitions between LODs much less noticable when moving around, except between LOD 4-5 (but that's very far away). It also removes the weird lip, but in it's place you have more white dots and in some cases a split between meshes.

image

I think the divisor might be too strong. / ((lod > 3) ? 2 : 1); This is less strong and gives more popping LOD transitions, and still some white dots.

I'm still experimenting with this equation, but I'm hoping there is an equation in the middle, based on LOD, that will allow no banding, no weird lip, and sealed chunks. Any ideas?

TokisanGames commented 4 years ago

Note: I'm pretty sure if we feed the normal map from the source heightmap to the shader, we can make this terrain look as detailed as a classic heightmap one, fading it in above some distance :p As seen on this old screenshot from my heightmap plugin:

That looks amazing. How might we do that? Could we do that with noise?

Zylann commented 4 years ago

As discussed on Discord, the key is to find a good enough iso_scale multiplier to make the best use of the limited sampled range. Increasing bit depth can help too but at the moment Transvoxel doesn't support higher than 8 bits.

Remember that the image heightmap demo really has bumps in the near lods. This is because the image itself is of poor quality and can only have 256 height values along the Y axis, which is very small. Blurring helps a tiny bit, but it often shows that using a better quality source is a better option. If you use 2D noise you won't see any bumps in the near lods, like 3D noise, with proper iso scaling.

If LOD modifies the SDF you will break... LOD. Indeed the rule is, for the same position, to alway return the same value, no matter what. If the value is different for different LODs even if the fetched position is the same, it will introduce errors in the interpolation and small gaps will appear between transitions.

About the normal map: the idea is that if you know the heightmap of your whole world in advance, you can generate the voxels with it, but also precompute a normal map from it, which you can use in a shader beyond some distance to create the illusion of crispness.

MGilleronFJ commented 2 years ago

I think this is mostly fixed?

Also the normalmap idea is getting a new alternative in the smooth_normalmaps branch https://github.com/Zylann/godot_voxel/blob/smooth_normalmaps/doc/source/smooth_terrain.md#distance-normals