diff --git a/doc/classes/OccluderInstance3D.xml b/doc/classes/OccluderInstance3D.xml index cc4bddc229da..a9c99df8f8f0 100644 --- a/doc/classes/OccluderInstance3D.xml +++ b/doc/classes/OccluderInstance3D.xml @@ -1,8 +1,14 @@ + Provides occlusion culling for 3D nodes, which improves performance in closed areas. + Occlusion culling can improve rendering performance in closed/semi-open areas by hiding geometry that is occluded by other objects. + The occlusion culling system is mostly static. [OccluderInstance3D]s can be moved at run-time, but doing so will trigger a background recomputation that can take several frames. It is recommended to only move [OccluderInstance3D]s sporadically (e.g. for procedural generation purposes), rather than doing so every frame. + The occlusion culling system works by rendering the occluders on the CPU in parallel using [url=https://www.embree.org/]Embree[/url], drawing the result to a 160×90 buffer then using this to cull 3D nodes individually. In the 3D editor, you can preview the occlusion culling buffer by choosing [b]Perspective > Debug Advanced... > Occlusion Culling Buffer[/b] in the top-left corner of the 3D viewport. + [b]Baking:[/b] Select an [OccluderInstance3D] node, then use the [b]Bake Occluders[/b] button at the top of the 3D editor. Only opaque materials will be taken into account; transparent materials (alpha-blended or alpha-tested) will be ignored by the occluder generation. + [b]Note:[/b] Occlusion culling is only effective if [member ProjectSettings.rendering/occlusion_culling/use_occlusion_culling] is [code]true[/code]. Enabling occlusion culling has a cost on the CPU. Only enable occlusion culling if you actually plan to use it. Large, open scenes with few or no objects blocking the view will generally not benefit much from occlusion culling. @@ -25,8 +31,17 @@ + The visual layers to account for when baking for occluders. Only [MeshInstance3D]s whose [member VisualInstance3D.layers] match with this [member bake_mask] will be included in the generated occluder mesh. By default, all objects are taken into account for the occluder baking. + To improve performance and avoid artifacts, it is recommended to exclude dynamic objects, small objects and fixtures from the baking process by moving them to a separate visual layer and excluding the layer here. + + + The simplification to apply to the generated occluder resource. Higher values result in a less detailed occluder mesh, which improves performance but reduces culling accuracy. + The geometry is rendered on the CPU, so it is important to keep its geometry as simple as possible. Since the buffer is rendered at a low resolution, less detailed occluder meshes generally still work well. The default value is fairly aggressive, so you may have to decrase it if you run into false negatives (objects being occluded even though they are visible by the camera). A value of [code]0.0001[/code] will act conservatively, and will keep geometry [i]perceptually[/i] unaffected in the occlusion culling buffer. Depending on the scene, a value of [code]0.0001[/code] may still simplify the mesh noticeably compared to disabling simplification entirely. + Setting this to [code]0.0[/code] disables simplification entirely and will use the original meshes' vertices as-is. + [b]Note:[/b] This uses the [url=https://meshoptimizer.org/]meshoptimizer[/url] library under the hood, similar to LOD generation. + The occluder resource for this [OccluderInstance3D]. You can generate an occluder resource by selecting an [OccluderInstance3D] node then using the [b]Bake Occluders[/b] button at the top of the editor. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 0d1fa0e70f7c..f14c748fa54f 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1619,6 +1619,8 @@ + If [code]true[/code], [OccluderInstance3D] nodes will be usable for occlusion culling in 3D in the root viewport. In custom viewports, [member Viewport.use_occlusion_culling] must be set to [code]true[/code] instead. + [b]Note:[/b] Enabling occlusion culling has a cost on the CPU. Only enable occlusion culling if you actually plan to use it. Large, open scenes with few or no objects blocking the view will generally not benefit much from occlusion culling. Number of cubemaps to store in the reflection atlas. The number of [ReflectionProbe]s in a scene will be limited by this amount. A higher number requires more VRAM. diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 4a62d3ec7be2..eea9ba36c815 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -246,6 +246,8 @@ + If [code]true[/code], [OccluderInstance3D] nodes will be usable for occlusion culling in 3D for this viewport. For the root viewport, [member ProjectSettings.rendering/occlusion_culling/use_occlusion_culling] must be set to [code]true[/code] instead. + [b]Note:[/b] Enabling occlusion culling has a cost on the CPU. Only enable occlusion culling if you actually plan to use it, and think whether your scene can actually benefit from occlusion culling. Large, open scenes with few or no objects blocking the view will generally not benefit much from occlusion culling. If [code]true[/code], the viewport will use the primary XR interface to render XR output. When applicable this can result in a stereoscopic image and the resulting render being output to a headset. diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index f3e174c01ba4..3ee732c2c1ea 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -31,6 +31,7 @@ #include "occluder_instance_3d.h" #include "core/core_string_names.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/resources/surface_tool.h" RID Occluder3D::get_rid() const { if (!occluder.is_valid()) { @@ -213,6 +214,15 @@ bool OccluderInstance3D::get_bake_mask_value(int p_layer_number) const { return bake_mask & (1 << (p_layer_number - 1)); } +void OccluderInstance3D::set_bake_simplify(float p_threshold) { + simplify = p_threshold; + update_configuration_warnings(); +} + +float OccluderInstance3D::get_bake_simplify() const { + return simplify; +} + bool OccluderInstance3D::_bake_material_check(Ref p_material) { StandardMaterial3D *standard_mat = Object::cast_to(p_material.ptr()); if (standard_mat && standard_mat->get_transparency() != StandardMaterial3D::TRANSPARENCY_DISABLED) { @@ -240,7 +250,7 @@ void OccluderInstance3D::_bake_node(Node *p_node, PackedVector3Array &r_vertices } if (valid) { - Transform3D global_to_local = get_global_transform().affine_inverse() * mi->get_global_transform(); + const Transform3D global_to_local = get_global_transform().affine_inverse() * mi->get_global_transform(); for (int i = 0; i < mesh->get_surface_count(); i++) { if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { @@ -257,21 +267,38 @@ void OccluderInstance3D::_bake_node(Node *p_node, PackedVector3Array &r_vertices } } - Array arrays = mesh->surface_get_arrays(i); + const Array arrays = mesh->surface_get_arrays(i); + + const int vertex_offset = r_vertices.size(); + const PackedVector3Array vertices = arrays[Mesh::ARRAY_VERTEX]; + + const int index_offset = r_indices.size(); + PackedInt32Array indices = arrays[Mesh::ARRAY_INDEX]; + + if (!Math::is_zero_approx(simplify) && SurfaceTool::simplify_func) { + // meshoptimizer is available; use it to simplify the occluder geometry. + // This can speed up occluder rendering significantly. + Ref st = memnew(SurfaceTool); + st->begin(Mesh::PRIMITIVE_TRIANGLES); + + for (int j = 0; j < vertices.size(); j++) { + st->add_vertex(vertices[j]); + } + for (int j = 0; j < indices.size(); j++) { + st->add_index(indices[j]); + } + + indices = st->generate_lod(simplify); + } - int vertex_offset = r_vertices.size(); - PackedVector3Array vertices = arrays[Mesh::ARRAY_VERTEX]; r_vertices.resize(r_vertices.size() + vertices.size()); + r_indices.resize(r_indices.size() + indices.size()); Vector3 *vtx_ptr = r_vertices.ptrw(); for (int j = 0; j < vertices.size(); j++) { vtx_ptr[vertex_offset + j] = global_to_local.xform(vertices[j]); } - int index_offset = r_indices.size(); - PackedInt32Array indices = arrays[Mesh::ARRAY_INDEX]; - r_indices.resize(r_indices.size() + indices.size()); - int *idx_ptr = r_indices.ptrw(); for (int j = 0; j < indices.size(); j++) { idx_ptr[index_offset + j] = vertex_offset + indices[j]; @@ -352,12 +379,16 @@ void OccluderInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bake_mask_value", "layer_number", "value"), &OccluderInstance3D::set_bake_mask_value); ClassDB::bind_method(D_METHOD("get_bake_mask_value", "layer_number"), &OccluderInstance3D::get_bake_mask_value); + ClassDB::bind_method(D_METHOD("set_bake_simplify", "threshold"), &OccluderInstance3D::set_bake_simplify); + ClassDB::bind_method(D_METHOD("get_bake_simplify"), &OccluderInstance3D::get_bake_simplify); + ClassDB::bind_method(D_METHOD("set_occluder", "occluder"), &OccluderInstance3D::set_occluder); ClassDB::bind_method(D_METHOD("get_occluder"), &OccluderInstance3D::get_occluder); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "occluder", PROPERTY_HINT_RESOURCE_TYPE, "Occluder3D"), "set_occluder", "get_occluder"); ADD_GROUP("Bake", "bake_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_bake_mask", "get_bake_mask"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_simplify", PROPERTY_HINT_RANGE, "0.0,0.1,0.0001"), "set_bake_simplify", "get_bake_simplify"); } OccluderInstance3D::OccluderInstance3D() { diff --git a/scene/3d/occluder_instance_3d.h b/scene/3d/occluder_instance_3d.h index 173614b80cab..fec518d7e962 100644 --- a/scene/3d/occluder_instance_3d.h +++ b/scene/3d/occluder_instance_3d.h @@ -72,6 +72,7 @@ class OccluderInstance3D : public VisualInstance3D { private: Ref occluder; uint32_t bake_mask = 0xFFFFFFFF; + float simplify = 0.02; void _occluder_changed(); @@ -102,6 +103,9 @@ class OccluderInstance3D : public VisualInstance3D { void set_bake_mask_value(int p_layer_number, bool p_enable); bool get_bake_mask_value(int p_layer_number) const; + void set_bake_simplify(float p_threshold); + float get_bake_simplify() const; + BakeError bake(Node *p_from_node, String p_occluder_path = ""); OccluderInstance3D();