Skip to content

Commit 19bde01

Browse files
committed
Added instancer affining based on voxel generator SDF
1 parent 7d6b0f4 commit 19bde01

12 files changed

+392
-57
lines changed

doc/source/changelog.md

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ At the moment, this module doesn't have a distinct release schedule, so this cha
77

88
Semver is not yet in place, so each version can have breaking changes, although it shouldn't happen often across minor versions.
99

10+
1.5 - ongoing development - `master`
11+
-----------------------------------------
12+
13+
- `VoxelInstanceGenerator`: Added an option to affine positions based on the voxel generator SDF (only available with `VoxelGeneratorGraph`).
14+
15+
1016
1.4 - ongoing development - `master`
1117
--------------------------------------
1218

Binary file not shown.
Binary file not shown.

doc/source/instancing.md

+38-10
Original file line numberDiff line numberDiff line change
@@ -32,28 +32,56 @@ Items created this way come with a default setup, so you should be able to see s
3232

3333
### Block LOD
3434

35-
The range at which items spawn is based on the LOD system of the voxel terrain itself. This is configured in the `lod_index` property of [VoxelInstanceLibraryItem](api/VoxelInstanceLibraryItem.md). For example, choosing `0` will make the item spawn at the closest range, and fade quickly in the distance. Higher indexes will spawn on a larger range, so will also start to appear earlier as the player gets closer. Instances spawn in the same "blocks" as the ground.
35+
The range at which items spawn is based on the LOD system of the voxel terrain itself, either on high-resolution chunks, or low-resolution ones. This is configured in the `lod_index` property of [VoxelInstanceLibraryItem](api/VoxelInstanceLibraryItem.md). For example, choosing `0` will make the item spawn on chunks of LOD0 at closest range, and fade quickly in the distance. Higher indexes will spawn on bigger chunks covering a larger range.
3636

3737
![Screenshot showing the effect of lod_index on the range of instances](images/instances_lod_index.webp)
3838

3939
Usually landscapes may be composed of multiple layers so that the closer you get, the more details come in. Bigger items use high lod indexes to be seen from far away, while smaller items may use lower indexes.
4040

4141
![Screenshot of landscape using layers of instances](images/landscape_with_instances.webp)
4242

43-
There is a balance to consider when choosing the appropriate `lod_index`: currently, larger indexes are *much more imprecise*, because they work on top of a lower-resolution mesh. When getting closer, it's possible that such instances are seen floating above ground, or sinking into it. This mostly happens in areas with sharp changes such as ridges, crevices or caves:
43+
!!! note
44+
When making grass or other items, it may be a good idea to fade meshes based on distance from the camera using a custom shader, so they won't disappear abruptly. Using a ground texture of similar colors also helps to make it blend.
4445

45-
![Screemshot of misaligned instances](images/misaligned_instances.webp)
46+
### Placement precision
4647

47-
To combat this, you can adjust the `offset_along_normal` parameter in the `generator` associated to the item. This depends on the asset, so designing them such that they can have part of their bottom sunk into the ground can give some margin of error.
48+
There is a balance to consider when choosing the appropriate `lod_index`: currently, larger indexes are *much more imprecise*, because they work on top of a lower-resolution mesh. This allows to generate instances much quicker at larger distances, without having to calculate full-precision LODs.
4849

49-
Sometimes it might not be enough, so this problem still has to be worked out in the future. Possible approaches include:
50+
The downside is, as getting closer and terrain calculates higher-resolution LODs, it's possible that such instances are seen floating above ground, or sinking into it. This mostly happens in areas with sharp changes such as ridges, crevices or caves:
5051

51-
- Querying the world generator to approximate the surface without using the mesh (not suitable if the ground was edited)
52-
- Gradually snap the instances somehow as higher-resolution data becomes available
53-
- Load edited voxels for the entire world at once so they can be queried even from far distance (takes more memory)
52+
![Screenshot of misaligned instances](images/misaligned_instances.webp)
5453

55-
!!! note
56-
When making grass or other items, it may be a good idea to fade meshes based on distance from the camera using a custom shader, so they won't disappear abruptly. Using a ground texture of similar colors also helps to make it blend.
54+
#### Offset along normal
55+
56+
A first option to adjust placement, is to adjust the `offset_along_normal` parameter in the `generator` associated to the item. This depends on the asset, so designing them such that they can have part of their bottom sunk into the ground can give some margin of error.
57+
58+
#### Affining from generator SDF
59+
60+
Another option is to enable `affine_from_generator_sdf_enabled`, at the cost of slower instance generation. This is preferably used when `lod_index` > 0, as LOD0 already has maximum precision.
61+
62+
Without affining:
63+
64+
![Screenshot of instances spawned from LOD2 an viewed at LOD0, without affining. Many are not properly on ground](images/instance_gen_sdf_affining_off.webp)
65+
66+
With affining:
67+
68+
![Screenshot of instances spawned from LOD2 an viewed at LOD0, with affining on, resulting in better positionning](images/instance_gen_sdf_affining_on.webp)
69+
70+
This option queries the `VoxelGenerator` at floating point positions to approximate where the surface is, assuming the mesh-based position was a good starting point.
71+
The generator can only return SDF values, which roughly tells how "close" each 3D point is from the surface. With at least 2 nearby samples or more, we can interpolate to snap positions closer to that surface.
72+
73+
Limitations:
74+
75+
- Requires a generator that supports series generation (i.e query arbitrary floating point positions to get voxel data from, instead of voxel chunks). At time of writing, only `VoxelGeneratorGraph` supports this.
76+
- Doesn't work if the terrain is modified: if a player comes back to the edited area and instances have to re-generate on top, estimations from the generator will not account for edited areas. To workaround this, instances could be set as `persistent`, so editing terrain marks them as modified and saves their positions onwards instead of re-generating.
77+
- Tends to "bury" or "float" instances far away when their low-resolution mesh is active, since they get moved closer to the high-resolution one. This should however not be as noticeable as before, when that was happening at close range.
78+
79+
80+
#### Research
81+
82+
More options may be researched in the future in case the current workarounds aren't enough:
83+
84+
- Gradually snap instances as higher-resolution meshes become available. Requires fast mesh raycast, if possible doable from threads (that unfortunately excludes Godot physics).
5785

5886

5987
### Mesh LOD

terrain/instancing/generate_instances_block_task.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ void GenerateInstancesBlockTask::run(ThreadedTaskContext &ctx) {
2828
surface_arrays,
2929
up_mode,
3030
gen_octant_mask,
31-
mesh_block_size
31+
mesh_block_size,
32+
voxel_generator
3233
);
3334

3435
for (const Transform3f &t : tls_generated_transforms) {

terrain/instancing/generate_instances_block_task.h

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef ZN_VOXEL_GENERATE_INSTANCES_BLOCK_TASK_H
22
#define ZN_VOXEL_GENERATE_INSTANCES_BLOCK_TASK_H
33

4+
#include "../../generators/voxel_generator.h"
45
#include "../../util/containers/std_vector.h"
56
#include "../../util/godot/core/array.h"
67
#include "../../util/tasks/threaded_task.h"
@@ -24,6 +25,7 @@ class GenerateInstancesBlockTask : public IThreadedTask {
2425
float mesh_block_size;
2526
Array surface_arrays;
2627
Ref<VoxelInstanceGenerator> generator;
28+
Ref<VoxelGenerator> voxel_generator;
2729
// Can be pre-populated by edited transforms
2830
StdVector<Transform3f> transforms;
2931
std::shared_ptr<InstancerTaskOutputQueue> output_queue;

terrain/instancing/load_instance_block_task.cpp

+22-20
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,29 @@
1212

1313
namespace zylann::voxel {
1414

15-
LoadInstanceChunkTask::LoadInstanceChunkTask( //
16-
std::shared_ptr<InstancerTaskOutputQueue> output_queue, //
17-
Ref<VoxelStream> stream, //
15+
LoadInstanceChunkTask::LoadInstanceChunkTask(
16+
std::shared_ptr<InstancerTaskOutputQueue> output_queue,
17+
Ref<VoxelStream> stream,
18+
Ref<VoxelGenerator> voxel_generator,
1819
std::shared_ptr<InstancerQuickReloadingCache> quick_reload_cache,
19-
Ref<VoxelInstanceLibrary> library, //
20-
Array mesh_arrays, //
21-
Vector3i grid_position, //
22-
uint8_t lod_index, //
23-
uint8_t instance_block_size, //
24-
uint8_t data_block_size, //
25-
UpMode up_mode //
20+
Ref<VoxelInstanceLibrary> library,
21+
Array mesh_arrays,
22+
Vector3i grid_position,
23+
uint8_t lod_index,
24+
uint8_t instance_block_size,
25+
uint8_t data_block_size,
26+
UpMode up_mode
2627
) :
27-
//
28-
_output_queue(output_queue), //
29-
_stream(stream), //
30-
_quick_reload_cache(quick_reload_cache), //
31-
_library(library), //
32-
_mesh_arrays(mesh_arrays), //
33-
_render_grid_position(grid_position), //
34-
_lod_index(lod_index), //
35-
_instance_block_size(instance_block_size), //
36-
_data_block_size(data_block_size), //
28+
_output_queue(output_queue),
29+
_stream(stream),
30+
_voxel_generator(voxel_generator),
31+
_quick_reload_cache(quick_reload_cache),
32+
_library(library),
33+
_mesh_arrays(mesh_arrays),
34+
_render_grid_position(grid_position),
35+
_lod_index(lod_index),
36+
_instance_block_size(instance_block_size),
37+
_data_block_size(data_block_size),
3738
_up_mode(up_mode) //
3839
{
3940
#ifdef DEBUG_ENABLED
@@ -237,6 +238,7 @@ void LoadInstanceChunkTask::run(ThreadedTaskContext &ctx) {
237238
task->up_mode = _up_mode;
238239
task->surface_arrays = _mesh_arrays;
239240
task->generator = item.generator;
241+
task->voxel_generator = _voxel_generator;
240242
task->transforms = std::move(layer.transforms);
241243
task->output_queue = _output_queue;
242244

terrain/instancing/load_instance_block_task.h

+13-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef VOXEL_LOAD_INSTANCE_BLOCK_TASK_H
22
#define VOXEL_LOAD_INSTANCE_BLOCK_TASK_H
33

4+
#include "../../generators/voxel_generator.h"
45
#include "../../streams/voxel_stream.h"
56
#include "../../util/godot/core/array.h"
67
#include "../../util/math/vector3i.h"
@@ -18,17 +19,18 @@ struct InstancerQuickReloadingCache;
1819
// Loads all instances of all layers of a specific LOD in a specific chunk
1920
class LoadInstanceChunkTask : public IThreadedTask {
2021
public:
21-
LoadInstanceChunkTask( //
22-
std::shared_ptr<InstancerTaskOutputQueue> output_queue, //
23-
Ref<VoxelStream> stream, //
22+
LoadInstanceChunkTask(
23+
std::shared_ptr<InstancerTaskOutputQueue> output_queue,
24+
Ref<VoxelStream> stream,
25+
Ref<VoxelGenerator> voxel_generator,
2426
std::shared_ptr<InstancerQuickReloadingCache> quick_reload_cache,
25-
Ref<VoxelInstanceLibrary> library, //
26-
Array mesh_arrays, //
27-
Vector3i grid_position, //
28-
uint8_t lod_index, //
29-
uint8_t instance_block_size, //
30-
uint8_t data_block_size, //
31-
UpMode up_mode //
27+
Ref<VoxelInstanceLibrary> library,
28+
Array mesh_arrays,
29+
Vector3i grid_position,
30+
uint8_t lod_index,
31+
uint8_t instance_block_size,
32+
uint8_t data_block_size,
33+
UpMode up_mode
3234
);
3335

3436
const char *get_debug_name() const override {
@@ -40,6 +42,7 @@ class LoadInstanceChunkTask : public IThreadedTask {
4042
private:
4143
std::shared_ptr<InstancerTaskOutputQueue> _output_queue;
4244
Ref<VoxelStream> _stream;
45+
Ref<VoxelGenerator> _voxel_generator;
4346
std::shared_ptr<InstancerQuickReloadingCache> _quick_reload_cache;
4447
Ref<VoxelInstanceLibrary> _library;
4548
Array _mesh_arrays;

0 commit comments

Comments
 (0)