godot/scene/3d/occluder_instance_3d.cpp
jfons 4d9d99bb82 Implement occlusion culling
Added an occlusion culling system with support for static occluder meshes.
It can be enabled via `Project Settings > Rendering > Occlusion Culling > Use Occlusion Culling`.

Occluders are defined via the new `Occluder3D` resource and instanced using the new
`OccluderInstance3D` node. The occluders can also be automatically baked from a
scene using the built-in editor plugin.
2021-04-23 21:45:23 +02:00

336 lines
10 KiB
C++

/*************************************************************************/
/* occluder_instance_3d.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "occluder_instance_3d.h"
#include "core/core_string_names.h"
#include "scene/3d/mesh_instance_3d.h"
RID Occluder3D::get_rid() const {
if (!occluder.is_valid()) {
occluder = RS::get_singleton()->occluder_create();
RS::get_singleton()->occluder_set_mesh(occluder, vertices, indices);
}
return occluder;
}
void Occluder3D::set_vertices(PackedVector3Array p_vertices) {
vertices = p_vertices;
if (occluder.is_valid()) {
RS::get_singleton()->occluder_set_mesh(occluder, vertices, indices);
}
_update_changes();
}
PackedVector3Array Occluder3D::get_vertices() const {
return vertices;
}
void Occluder3D::set_indices(PackedInt32Array p_indices) {
indices = p_indices;
if (occluder.is_valid()) {
RS::get_singleton()->occluder_set_mesh(occluder, vertices, indices);
}
_update_changes();
}
PackedInt32Array Occluder3D::get_indices() const {
return indices;
}
void Occluder3D::_update_changes() {
aabb = AABB();
const Vector3 *ptr = vertices.ptr();
for (int i = 0; i < vertices.size(); i++) {
aabb.expand_to(ptr[i]);
}
debug_lines.clear();
debug_mesh.unref();
emit_changed();
}
Vector<Vector3> Occluder3D::get_debug_lines() const {
if (!debug_lines.is_empty()) {
return debug_lines;
}
if (indices.size() % 3 != 0) {
return Vector<Vector3>();
}
for (int i = 0; i < indices.size() / 3; i++) {
for (int j = 0; j < 3; j++) {
int a = indices[i * 3 + j];
int b = indices[i * 3 + (j + 1) % 3];
ERR_FAIL_INDEX_V_MSG(a, vertices.size(), Vector<Vector3>(), "Occluder indices are out of range.");
ERR_FAIL_INDEX_V_MSG(b, vertices.size(), Vector<Vector3>(), "Occluder indices are out of range.");
debug_lines.push_back(vertices[a]);
debug_lines.push_back(vertices[b]);
}
}
return debug_lines;
}
Ref<ArrayMesh> Occluder3D::get_debug_mesh() const {
if (debug_mesh.is_valid()) {
return debug_mesh;
}
if (indices.size() % 3 != 0) {
return debug_mesh;
}
Array arrays;
arrays.resize(Mesh::ARRAY_MAX);
arrays[Mesh::ARRAY_VERTEX] = vertices;
arrays[Mesh::ARRAY_INDEX] = indices;
debug_mesh.instance();
debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
return debug_mesh;
}
AABB Occluder3D::get_aabb() const {
return aabb;
}
void Occluder3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &Occluder3D::set_vertices);
ClassDB::bind_method(D_METHOD("get_vertices"), &Occluder3D::get_vertices);
ClassDB::bind_method(D_METHOD("set_indices", "indices"), &Occluder3D::set_indices);
ClassDB::bind_method(D_METHOD("get_indices"), &Occluder3D::get_indices);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_vertices", "get_vertices");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_indices", "get_indices");
}
Occluder3D::Occluder3D() {
}
Occluder3D::~Occluder3D() {
if (occluder.is_valid()) {
RS::get_singleton()->free(occluder);
}
}
/////////////////////////////////////////////////
AABB OccluderInstance3D::get_aabb() const {
if (occluder.is_valid()) {
return occluder->get_aabb();
}
return AABB();
}
Vector<Face3> OccluderInstance3D::get_faces(uint32_t p_usage_flags) const {
return Vector<Face3>();
}
void OccluderInstance3D::set_occluder(const Ref<Occluder3D> &p_occluder) {
if (occluder == p_occluder) {
return;
}
if (occluder.is_valid()) {
occluder->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &OccluderInstance3D::_occluder_changed));
}
occluder = p_occluder;
if (occluder.is_valid()) {
set_base(occluder->get_rid());
occluder->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &OccluderInstance3D::_occluder_changed));
} else {
set_base(RID());
}
update_gizmo();
}
void OccluderInstance3D::_occluder_changed() {
update_gizmo();
}
Ref<Occluder3D> OccluderInstance3D::get_occluder() const {
return occluder;
}
void OccluderInstance3D::set_bake_mask(uint32_t p_mask) {
bake_mask = p_mask;
}
uint32_t OccluderInstance3D::get_bake_mask() const {
return bake_mask;
}
void OccluderInstance3D::set_bake_mask_bit(int p_layer, bool p_enable) {
ERR_FAIL_INDEX(p_layer, 32);
if (p_enable) {
set_bake_mask(bake_mask | (1 << p_layer));
} else {
set_bake_mask(bake_mask & (~(1 << p_layer)));
}
}
bool OccluderInstance3D::get_bake_mask_bit(int p_layer) const {
ERR_FAIL_INDEX_V(p_layer, 32, false);
return (bake_mask & (1 << p_layer));
}
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) {
return false;
}
return true;
}
void OccluderInstance3D::_bake_node(Node *p_node, PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node);
if (mi && mi->is_visible_in_tree()) {
Ref<Mesh> mesh = mi->get_mesh();
bool valid = true;
if (mesh.is_null()) {
valid = false;
}
if (valid && !_bake_material_check(mi->get_material_override())) {
valid = false;
}
if ((mi->get_layer_mask() & bake_mask) == 0) {
valid = false;
}
if (valid) {
Transform 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) {
continue;
}
if (mi->get_surface_override_material(i).is_valid()) {
if (!_bake_material_check(mi->get_surface_override_material(i))) {
continue;
}
} else {
if (!_bake_material_check(mesh->surface_get_material(i))) {
continue;
}
}
Array arrays = mesh->surface_get_arrays(i);
int vertex_offset = r_vertices.size();
PackedVector3Array vertices = arrays[Mesh::ARRAY_VERTEX];
r_vertices.resize(r_vertices.size() + vertices.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];
}
}
}
}
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *child = p_node->get_child(i);
if (!child->get_owner()) {
continue; //maybe a helper
}
_bake_node(child, r_vertices, r_indices);
}
}
OccluderInstance3D::BakeError OccluderInstance3D::bake(Node *p_from_node, String p_occluder_path) {
if (p_occluder_path == "") {
if (get_occluder().is_null()) {
return BAKE_ERROR_NO_SAVE_PATH;
}
}
PackedVector3Array vertices;
PackedInt32Array indices;
_bake_node(p_from_node, vertices, indices);
if (vertices.is_empty() || indices.is_empty()) {
return BAKE_ERROR_NO_MESHES;
}
Ref<Occluder3D> occ;
if (get_occluder().is_valid()) {
occ = get_occluder();
} else {
occ.instance();
occ->set_path(p_occluder_path);
}
occ->set_vertices(vertices);
occ->set_indices(indices);
set_occluder(occ);
return BAKE_ERROR_OK;
}
void OccluderInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bake_mask", "mask"), &OccluderInstance3D::set_bake_mask);
ClassDB::bind_method(D_METHOD("get_bake_mask"), &OccluderInstance3D::get_bake_mask);
ClassDB::bind_method(D_METHOD("set_bake_mask_bit", "layer", "enabled"), &OccluderInstance3D::set_bake_mask_bit);
ClassDB::bind_method(D_METHOD("get_bake_mask_bit", "layer"), &OccluderInstance3D::get_bake_mask_bit);
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");
}
OccluderInstance3D::OccluderInstance3D() {
}
OccluderInstance3D::~OccluderInstance3D() {
}