Open darrylryan opened 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.
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:
VoxelStreamImage
samples the image in a quite naive way: smooth voxels are polygonized from a distance field, and what this stream does is an approximation, it's not a true distance from the surface, instead it's a vertical distance. So it gets more inaccurate the steeper the slope is. To smooth this out a little, for each cell, it gets a 3x3 area and averages them. So yes @zauberparacelsus this stream does smooth the image already. The result isn't too bad, but still retains artifacts due to the distance approximation, causing banding at any LOD level.
I've seen more subtle terracing happen in far away blocks even with VoxelStreamNoise
:
The cause of this one is less clear to me, but it could be due to a similar problem when the volume gets sampled at lower LODs, as the distance field sharply goes from -1 to 1 without a fading interval: that means instead of smooth "shades" of distance fields, we get blocky values again.
So this could be down to fixing the way those streams sample their volume function.
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).
With blurring technique: With segment technique:
It also has the effect of producing more correct results when the heightmap contains rectangles of uniform height.
With blurring technique: With segment technique:
There is still terracing for 2 different reasons:
A new option is available in 3ead96f06e80b7295b335826d8b2eb9bbce4768a
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.
I didn't notice spikes. Screenshot? (LOD > 0 doesn't count)
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.
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:
Breaking news.
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:
(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?
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.
I've seen more subtle terracing happen in far away blocks even with
VoxelStreamNoise
: The cause of this one is less clear to me...
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:
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.
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?
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?
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.
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
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?