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

feat: Instancer LOD #604

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

laurentsenta
Copy link

@laurentsenta laurentsenta commented Jan 25, 2025

Contributes to #43
Introduce four levels of LODs for multimesh instances and min / max shadow lods.

See thread in Discord

Task list:

  • Decide on using an array or hardcoded floats for the distances
  • Actually use the LOD'd mesh in the instancer
  • Shadow LOD
  • figure out the visibility margin thingy
  • Fix the "everything resets on loading" issue
  • Remove visibility range, and figure out upgrade path
  • Field validation
  • Update
  • Cleanup
  • Shadow min / max config
  • Field validation (bis)
  • Update the doc
  • Also update the notes on "in the future we'll chunk MMIs" this is already implemented

CleanShot 2025-01-25 at 19 36 50

Regarding validation:

This PR introduces a bunch of fields that are related to each other:

  • maximum lod value is valid if the scene is loaded and must be less than mesh count
  • maximum shadow lod is valid if <= maximum lod and >= minimum shadow lod (and vice versa for minimum shadow lod)
  • Cast Shadows validity depends on the shadow lods:
    • if cast shadow is double-sided or shadows only, it will interact weirdly with min/max shadow lods

Checking values in the setters will be super annoying to use, as in "I'm trying to lower the maximum lod, but nothing happens" > "yes, you need to lower the minimum shadow lod first".

This is a perfect use case for configuration warning, which is a work in progress:

@laurentsenta laurentsenta marked this pull request as draft January 25, 2025 16:44
@laurentsenta laurentsenta force-pushed the feat/instancer-lod branch 4 times, most recently from 43ff44e to 0cc19cf Compare January 25, 2025 18:38
_shadow_lod = CLAMP(p_lod, 0, get_maximum_lod());
LOG(INFO, "Setting shadow LOD: ", _shadow_lod);
emit_signal("instancer_setting_changed");
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need maintainer input: every time I reopen the project, the maximum lod and shadow lod values are reset to -1,
I think this is because the mesh is not loaded when the editor tries to set the instance's value.

If you change the values and then close and reopen the scene, that'll work.

Is there an idiomatic way to avoid this?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When Godot loads the scene file it loads all settings from the scene file. You don't really know which one it will load first. If this is loaded before the scene, get_mesh_count() will be zero, which sets your _maximum_lod = -1.

If packed_scene.is_null() then you shouldn't check get_mesh_count() and accept the value as it is. It won't be used while the scene is null.

When packed_scene is loaded, you can verify that maximum_lod is valid. Same w/ shadow lod.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in fcaccb9

@laurentsenta laurentsenta force-pushed the feat/instancer-lod branch 3 times, most recently from ccdc7ab to 1f2af27 Compare January 26, 2025 09:06
@laurentsenta laurentsenta changed the title (wip) feat: Instancer LOD feat: Instancer LOD Jan 26, 2025
@laurentsenta laurentsenta marked this pull request as ready for review January 26, 2025 22:25
Copy link
Contributor

@Xtarsia Xtarsia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice work overall.

Cory will be along asking for a good squash no doubt..

The demo changes can likley be dropped unless you want to spend time tidying them up.

Docs effort is stellar!

src/terrain_3d_instancer.cpp Outdated Show resolved Hide resolved
src/terrain_3d_instancer.cpp Show resolved Hide resolved
@TokisanGames
Copy link
Owner

Cory will be along asking for a good squash no doubt..

It can be squashed down to 3 commits: code, docs, demo changes.

I'll review in a couple of days after Xtarsia is satisfied and I get more progress on my collision commit.

@Saul2022
Copy link

Looking great, just one curiosity, so to add lods, we do it like we usually do with visibility ranges and hlod.

@TokisanGames
Copy link
Owner

@Saul2022 Should be able to setup lods in your mesh scene like this, and that's it. Or adjust defaults if desired.

image

Copy link
Owner

@TokisanGames TokisanGames left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, very good work on this. Thank you for the effort. Please rebase it as you address the notes.

  • Playing with the options and thinking about the names, I think they can use some adjustments to be clearer. I'd prefer to move away from "higher, lower, minimum, maximum" because lods numbers are a reverse scale, where higher/better is a lower number. The docs say "lods above this...", does "above" mean "larger number / lower detail"? Or "higher detail / lower number"? Does "maximum lod" mean "maximum distance / lowest detail" or "maximum detail / lowest distance"? I think "last" is not only shorter, but more universal in meaning lowest detail, farthest away.

    • maximum_lod means "don't show lods lower than this lod". I think last_lod is a little better. It's also lod_count-1.
    • maximum_shadow_lod means "show shadows for this lod and higher(0) - ie don't show shadows for lods lower than this value". This would be better named last_shadow_lod.
    • minimum_shadow_lod means "use this shadow impostor lod or lower(3) - ie when showing a lod higher(0) than this lod, use this one as its shadow impostor". This would be better named max_shadow_imposter_lod, or max_shadow_lod.
  • If minimum_shadow_lod is > maximum_shadow_lod, the latter doesn't work. Or more clearly, if max_shadow_imposter_lod > last_shadow_lod, then it shows shadows beyond the last_shadow_lod. Given the above descriptions it should not. It should only show shadows for lods up to last_shadow_lod.

  • I actually don't think we need a maximum_shadow_lod. Lights already have a shadow distance. We don't need to cull it ourselves. Having it prevents us from needing to create and store the MMI, but that generation is quick and only on start, and the memory should be shared with my related note elsewhere.

In my specifications here on discord I expressed the need for maximum_lod(last_lod) and shadow_lod. The lods should show shadows at all distances that the user has configured their Lights for, it just may be at a lower level LOD.

  • MeshAsset/Cast Shadows / shadows only doesn't work. Shadows Off does work.

  • MeshAsset set_scene_file doesn't do any filtering or sorting on meshes named LOD0-LOD3. It happens that sometimes the LODS might be out of order for an artist or an engine reason. I would look for all of the mesh instances that end in LOD#, then sort them and use the first 4. If I couldn't find any LODs, then just take the first original 4. Maybe print some messages saying, "found 8 lods, using the first 4." or "Meshes don't end with LOD#, assuming first 4 are LOD0-LOD3".

  • MeshAsset set_scene_file - You could also fix a bug where if they provide a scene with a MeshInstance3D as the root node, it won't use it. Probably because the find_child starts looking at the children and ignores the scene root.

ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod_1_visibility_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod_1_visibility_range", "get_lod_1_visibility_range");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod_2_visibility_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod_2_visibility_range", "get_lod_2_visibility_range");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod_3_visibility_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_lod_3_visibility_range", "get_lod_3_visibility_range");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_count", PROPERTY_HINT_NONE), "set_mesh_count", "get_mesh_count");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can specify "" for the setter, then you don't need set_mesh_count or _validate_property.


//DEPRECATED 1.0 - Remove 1.1
void Terrain3DMeshAsset::set_visibility_range(const real_t p_visibility_range) {
set_lod_0_visibility_range(p_visibility_range);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visibility_range marks the distance the plants are visible at all, not necessarily lod0. I would have this set the distance value of the last lod, as that will be the closest to previous behavior.

}

return MIN(MAX_LOD_COUNT - 1, get_mesh_count() - 1);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this function and the handling of maximum_lod (last_lod).

lod_count is CLAMP(_packed_scene.is_valid() ? _meshes.size() : 1, 1, MAX_LOD_COUNT)
and last_lod is lod_count - 1.

If there is no packed scene, there's still 1 generated mesh. The defaults can be appropriate for when there's only 1 mesh (which may be generated or not).

@@ -47,8 +47,9 @@ class Terrain3DInstancer : public Object {
void _destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id);
void _backup_regionl(const Vector2i &p_region_loc);
void _backup_region(const Ref<Terrain3DRegion> &p_region);
Ref<MultiMesh> _create_multimesh(const int p_mesh_id, const TypedArray<Transform3D> &p_xforms = TypedArray<Transform3D>(), const PackedColorArray &p_colors = PackedColorArray()) const;
Ref<MultiMesh> _create_multimesh(const int p_mesh_id, const int lod, const TypedArray<Transform3D> &p_xforms = TypedArray<Transform3D>(), const PackedColorArray &p_colors = PackedColorArray()) const;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lod should be p_lod

@@ -88,6 +121,7 @@ class Terrain3DMeshAsset : public Terrain3DAssetResource {

Ref<Mesh> get_mesh(const int p_index = 0);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

index can be renamed to lod

return get_cast_shadows();
}
// That lod relies on the min shadow lod
if (p_lod_id <= get_minimum_shadow_lod()) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// This lod uses the shadow impostor lod

}
}
}
}

void Terrain3DInstancer::_setup_mmi_lod(MultiMeshInstance3D *p_mmi, const Ref<Terrain3DMeshAsset> &p_ma, const int p_lod) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be named _setup_mmi_lod_ranges


// Create MM and assign to MMI
mmi = cell_mmi_dict[cell];
mmi->set_multimesh(_create_multimesh(mesh_id, lod, xforms, colors));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like you are creating 5 MultiMeshes for Shadow, and LOD0-3. So that's 5 meshes (necessary) and 5x the transforms and colors (MMI internals copy incoming data so we can't share it) to display 4 meshes.

You could instead reverse the for loop to count from last_lod to SHADOW_LOD. Then when creating the shadow MMI, grab the already created LOD# Multimesh reference and insert it instead of creating an identical copy.

ClassDB::bind_method(D_METHOD("get_thumbnail"), &Terrain3DMeshAsset::get_thumbnail);

ADD_PROPERTY(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE), "set_name", "get_name");
ADD_PROPERTY(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_NONE), "set_id", "get_id");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_offset", PROPERTY_HINT_RANGE, "-20.0,20.0,.005"), "set_height_offset", "get_height_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, ".01,10.0,.005"), "set_density", "get_density");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_visibility_range", "get_visibility_range");
//ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_margin", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_visibility_margin", "get_visibility_margin");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_margin", PROPERTY_HINT_RANGE, "0.,4096.0,.05,or_greater"), "set_visibility_margin", "get_visibility_margin");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort all of the lod settings under the scene file, and put this above those properties to put them in a group ADD_GROUP("LODs", "");. I don't know, but you might be able to then add ADD_GROUP("", ""); to get out of the group.

return MIN(MAX_LOD_COUNT - 1, get_mesh_count() - 1);
}

void Terrain3DMeshAsset::refresh_valid_lods() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This function should be called _validate_lods()
  • Use internal names, not functions.

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

Successfully merging this pull request may close these issues.

4 participants