Skip to content

Commit

Permalink
Implement simplification of generated occluders in OccluderInstance3D
Browse files Browse the repository at this point in the history
The meshoptimizer library is used if the `bake_simplify` property is set
to a value greater than 0.

The default simplifcation threshold (0.02) is fairly aggressive,
but occluders don't need to be very detailed to work well.

This also adds documentation for occlusion culling in the
class reference XML.
  • Loading branch information
Calinou committed Aug 16, 2021
1 parent 4344022 commit e6199a5
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 8 deletions.
15 changes: 15 additions & 0 deletions doc/classes/OccluderInstance3D.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="OccluderInstance3D" inherits="Node3D" version="4.0">
<brief_description>
Provides occlusion culling for 3D nodes, which improves performance in closed areas.
</brief_description>
<description>
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 &gt; Debug Advanced... &gt; 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.
</description>
<tutorials>
</tutorials>
Expand All @@ -25,8 +31,17 @@
</methods>
<members>
<member name="bake_mask" type="int" setter="set_bake_mask" getter="get_bake_mask" default="4294967295">
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.
</member>
<member name="bake_simplify" type="float" setter="set_bake_simplify" getter="get_bake_simplify" default="0.02">
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.
</member>
<member name="occluder" type="Occluder3D" setter="set_occluder" getter="get_occluder">
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.
</member>
</members>
<constants>
Expand Down
2 changes: 2 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1619,6 +1619,8 @@
<member name="rendering/occlusion_culling/occlusion_rays_per_thread" type="int" setter="" getter="" default="512">
</member>
<member name="rendering/occlusion_culling/use_occlusion_culling" type="bool" setter="" getter="" default="false">
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.
</member>
<member name="rendering/reflections/reflection_atlas/reflection_count" type="int" setter="" getter="" default="64">
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.
Expand Down
2 changes: 2 additions & 0 deletions doc/classes/Viewport.xml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@
<member name="use_debanding" type="bool" setter="set_use_debanding" getter="is_using_debanding" default="false">
</member>
<member name="use_occlusion_culling" type="bool" setter="set_use_occlusion_culling" getter="is_using_occlusion_culling" default="false">
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.
</member>
<member name="use_xr" type="bool" setter="set_use_xr" getter="is_using_xr" default="false">
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.
Expand Down
47 changes: 39 additions & 8 deletions scene/3d/occluder_instance_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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<Material> p_material) {
StandardMaterial3D *standard_mat = Object::cast_to<StandardMaterial3D>(p_material.ptr());
if (standard_mat && standard_mat->get_transparency() != StandardMaterial3D::TRANSPARENCY_DISABLED) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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<SurfaceTool> 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];
Expand Down Expand Up @@ -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() {
Expand Down
4 changes: 4 additions & 0 deletions scene/3d/occluder_instance_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class OccluderInstance3D : public VisualInstance3D {
private:
Ref<Occluder3D> occluder;
uint32_t bake_mask = 0xFFFFFFFF;
float simplify = 0.02;

void _occluder_changed();

Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit e6199a5

Please sign in to comment.