2023-01-05 13:25:55 +01:00
|
|
|
/**************************************************************************/
|
|
|
|
/* renderer_scene_occlusion_cull.h */
|
|
|
|
/**************************************************************************/
|
|
|
|
/* This file is part of: */
|
|
|
|
/* GODOT ENGINE */
|
|
|
|
/* https://godotengine.org */
|
|
|
|
/**************************************************************************/
|
|
|
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
|
|
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
|
|
|
/* */
|
|
|
|
/* 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. */
|
|
|
|
/**************************************************************************/
|
2021-04-20 18:40:24 +02:00
|
|
|
|
|
|
|
#ifndef RENDERER_SCENE_OCCLUSION_CULL_H
|
|
|
|
#define RENDERER_SCENE_OCCLUSION_CULL_H
|
|
|
|
|
Implement Vector4, Vector4i, Projection
Implement built-in classes Vector4, Vector4i and Projection.
* Two versions of Vector4 (float and integer).
* A Projection class, which is a 4x4 matrix specialized in projection types.
These types have been requested for a long time, but given they were very corner case they were not added before.
Because in Godot 4, reimplementing parts of the rendering engine is now possible, access to these types (heavily used by the rendering code) becomes a necessity.
**Q**: Why Projection and not Matrix4?
**A**: Godot does not use Matrix2, Matrix3, Matrix4x3, etc. naming convention because, within the engine, these types always have a *purpose*. As such, Godot names them: Transform2D, Transform3D or Basis. In this case, this 4x4 matrix is _always_ used as a _Projection_, hence the naming.
2022-07-20 01:11:13 +02:00
|
|
|
#include "core/math/projection.h"
|
2021-04-20 18:40:24 +02:00
|
|
|
#include "core/templates/local_vector.h"
|
|
|
|
#include "servers/rendering_server.h"
|
|
|
|
|
|
|
|
class RendererSceneOcclusionCull {
|
|
|
|
protected:
|
|
|
|
static RendererSceneOcclusionCull *singleton;
|
|
|
|
|
|
|
|
public:
|
|
|
|
class HZBuffer {
|
|
|
|
protected:
|
|
|
|
static const Vector3 corners[8];
|
|
|
|
|
|
|
|
LocalVector<float> data;
|
|
|
|
LocalVector<Size2i> sizes;
|
|
|
|
LocalVector<float *> mips;
|
|
|
|
|
|
|
|
RID debug_texture;
|
|
|
|
Ref<Image> debug_image;
|
|
|
|
PackedByteArray debug_data;
|
|
|
|
float debug_tex_range = 0.0f;
|
|
|
|
|
2023-12-13 14:45:57 +01:00
|
|
|
uint64_t occlusion_frame = 0;
|
|
|
|
Size2i occlusion_buffer_size;
|
2021-04-20 18:40:24 +02:00
|
|
|
|
2023-12-13 14:45:57 +01:00
|
|
|
_FORCE_INLINE_ bool _is_occluded(const real_t p_bounds[6], const Vector3 &p_cam_position, const Transform3D &p_cam_inv_transform, const Projection &p_cam_projection, real_t p_near) const {
|
2021-04-20 18:40:24 +02:00
|
|
|
if (is_empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-03-03 12:49:08 +01:00
|
|
|
Vector3 closest_point = p_cam_position.clamp(Vector3(p_bounds[0], p_bounds[1], p_bounds[2]), Vector3(p_bounds[3], p_bounds[4], p_bounds[5]));
|
2021-04-20 18:40:24 +02:00
|
|
|
|
|
|
|
if (closest_point == p_cam_position) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector3 closest_point_view = p_cam_inv_transform.xform(closest_point);
|
|
|
|
if (closest_point_view.z > -p_near) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-09-10 18:15:10 +02:00
|
|
|
float min_depth = -closest_point_view.z * 0.95f;
|
2021-04-20 18:40:24 +02:00
|
|
|
|
|
|
|
Vector2 rect_min = Vector2(FLT_MAX, FLT_MAX);
|
|
|
|
Vector2 rect_max = Vector2(FLT_MIN, FLT_MIN);
|
|
|
|
|
|
|
|
for (int j = 0; j < 8; j++) {
|
2021-09-10 18:15:10 +02:00
|
|
|
const Vector3 &c = RendererSceneOcclusionCull::HZBuffer::corners[j];
|
2021-04-20 18:40:24 +02:00
|
|
|
Vector3 nc = Vector3(1, 1, 1) - c;
|
|
|
|
Vector3 corner = Vector3(p_bounds[0] * c.x + p_bounds[3] * nc.x, p_bounds[1] * c.y + p_bounds[4] * nc.y, p_bounds[2] * c.z + p_bounds[5] * nc.z);
|
|
|
|
Vector3 view = p_cam_inv_transform.xform(corner);
|
|
|
|
|
2021-09-10 18:15:10 +02:00
|
|
|
Plane vp = Plane(view, 1.0);
|
|
|
|
Plane projected = p_cam_projection.xform4(vp);
|
|
|
|
|
|
|
|
float w = projected.d;
|
|
|
|
if (w < 1.0) {
|
|
|
|
rect_min = Vector2(0.0f, 0.0f);
|
|
|
|
rect_max = Vector2(1.0f, 1.0f);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector2 normalized = Vector2(projected.normal.x / w * 0.5f + 0.5f, projected.normal.y / w * 0.5f + 0.5f);
|
2021-04-20 18:40:24 +02:00
|
|
|
rect_min = rect_min.min(normalized);
|
|
|
|
rect_max = rect_max.max(normalized);
|
|
|
|
}
|
|
|
|
|
2024-03-03 14:37:52 +01:00
|
|
|
rect_max = rect_max.minf(1);
|
|
|
|
rect_min = rect_min.maxf(0);
|
2021-04-20 18:40:24 +02:00
|
|
|
|
|
|
|
int mip_count = mips.size();
|
|
|
|
|
|
|
|
Vector2 screen_diagonal = (rect_max - rect_min) * sizes[0];
|
|
|
|
float size = MAX(screen_diagonal.x, screen_diagonal.y);
|
|
|
|
float l = Math::ceil(Math::log2(size));
|
|
|
|
int lod = CLAMP(l, 0, mip_count - 1);
|
|
|
|
|
|
|
|
const int max_samples = 512;
|
|
|
|
int sample_count = 0;
|
|
|
|
bool visible = true;
|
|
|
|
|
|
|
|
for (; lod >= 0; lod--) {
|
|
|
|
int w = sizes[lod].x;
|
|
|
|
int h = sizes[lod].y;
|
|
|
|
|
|
|
|
int minx = CLAMP(rect_min.x * w - 1, 0, w - 1);
|
|
|
|
int maxx = CLAMP(rect_max.x * w + 1, 0, w - 1);
|
|
|
|
|
|
|
|
int miny = CLAMP(rect_min.y * h - 1, 0, h - 1);
|
|
|
|
int maxy = CLAMP(rect_max.y * h + 1, 0, h - 1);
|
|
|
|
|
|
|
|
sample_count += (maxx - minx + 1) * (maxy - miny + 1);
|
|
|
|
|
|
|
|
if (sample_count > max_samples) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
visible = false;
|
|
|
|
for (int y = miny; y <= maxy; y++) {
|
|
|
|
for (int x = minx; x <= maxx; x++) {
|
|
|
|
float depth = mips[lod][y * w + x];
|
|
|
|
if (depth > min_depth) {
|
|
|
|
visible = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (visible) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!visible) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return !visible;
|
|
|
|
}
|
|
|
|
|
2023-12-13 14:45:57 +01:00
|
|
|
public:
|
|
|
|
static bool occlusion_jitter_enabled;
|
|
|
|
|
|
|
|
bool is_empty() const;
|
|
|
|
virtual void clear();
|
|
|
|
virtual void resize(const Size2i &p_size);
|
|
|
|
|
|
|
|
void update_mips();
|
|
|
|
|
|
|
|
// Thin wrapper around _is_occluded(),
|
|
|
|
// allowing occlusion timers to delay the disappearance
|
|
|
|
// of objects to prevent flickering when using jittering.
|
|
|
|
_FORCE_INLINE_ bool is_occluded(const real_t p_bounds[6], const Vector3 &p_cam_position, const Transform3D &p_cam_inv_transform, const Projection &p_cam_projection, real_t p_near, uint64_t &r_occlusion_timeout) const {
|
|
|
|
bool occluded = _is_occluded(p_bounds, p_cam_position, p_cam_inv_transform, p_cam_projection, p_near);
|
|
|
|
|
|
|
|
// Special case, temporal jitter disabled,
|
|
|
|
// so we don't use occlusion timers.
|
|
|
|
if (!occlusion_jitter_enabled) {
|
|
|
|
return occluded;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!occluded) {
|
|
|
|
//#define DEBUG_RASTER_OCCLUSION_JITTER
|
|
|
|
#ifdef DEBUG_RASTER_OCCLUSION_JITTER
|
|
|
|
r_occlusion_timeout = occlusion_frame + 1;
|
|
|
|
#else
|
|
|
|
r_occlusion_timeout = occlusion_frame + 9;
|
|
|
|
#endif
|
|
|
|
} else if (r_occlusion_timeout) {
|
|
|
|
// Regular timeout, allow occlusion culling
|
|
|
|
// to proceed as normal after the delay.
|
|
|
|
if (occlusion_frame >= r_occlusion_timeout) {
|
|
|
|
r_occlusion_timeout = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return occluded && !r_occlusion_timeout;
|
|
|
|
}
|
|
|
|
|
2021-04-20 18:40:24 +02:00
|
|
|
RID get_debug_texture();
|
2023-12-13 14:45:57 +01:00
|
|
|
const Size2i &get_occlusion_buffer_size() const { return occlusion_buffer_size; }
|
2021-04-20 18:40:24 +02:00
|
|
|
|
|
|
|
virtual ~HZBuffer(){};
|
|
|
|
};
|
|
|
|
|
|
|
|
static RendererSceneOcclusionCull *get_singleton() { return singleton; }
|
|
|
|
|
2022-05-23 09:34:28 +02:00
|
|
|
void _print_warning() {
|
|
|
|
WARN_PRINT_ONCE("Occlusion culling is disabled at build-time.");
|
2021-04-20 18:40:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool is_occluder(RID p_rid) { return false; }
|
|
|
|
virtual RID occluder_allocate() { return RID(); }
|
|
|
|
virtual void occluder_initialize(RID p_occluder) {}
|
2022-05-23 09:34:28 +02:00
|
|
|
virtual void free_occluder(RID p_occluder) { _print_warning(); }
|
|
|
|
virtual void occluder_set_mesh(RID p_occluder, const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices) { _print_warning(); }
|
2021-04-20 18:40:24 +02:00
|
|
|
|
|
|
|
virtual void add_scenario(RID p_scenario) {}
|
|
|
|
virtual void remove_scenario(RID p_scenario) {}
|
2022-05-23 09:34:28 +02:00
|
|
|
virtual void scenario_set_instance(RID p_scenario, RID p_instance, RID p_occluder, const Transform3D &p_xform, bool p_enabled) { _print_warning(); }
|
|
|
|
virtual void scenario_remove_instance(RID p_scenario, RID p_instance) { _print_warning(); }
|
2021-04-20 18:40:24 +02:00
|
|
|
|
2022-05-23 09:34:28 +02:00
|
|
|
virtual void add_buffer(RID p_buffer) { _print_warning(); }
|
|
|
|
virtual void remove_buffer(RID p_buffer) { _print_warning(); }
|
2021-04-20 18:40:24 +02:00
|
|
|
virtual HZBuffer *buffer_get_ptr(RID p_buffer) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2022-05-23 09:34:28 +02:00
|
|
|
virtual void buffer_set_scenario(RID p_buffer, RID p_scenario) { _print_warning(); }
|
|
|
|
virtual void buffer_set_size(RID p_buffer, const Vector2i &p_size) { _print_warning(); }
|
2022-07-23 19:12:41 +02:00
|
|
|
virtual void buffer_update(RID p_buffer, const Transform3D &p_cam_transform, const Projection &p_cam_projection, bool p_cam_orthogonal) {}
|
|
|
|
|
2021-04-20 18:40:24 +02:00
|
|
|
virtual RID buffer_get_debug_texture(RID p_buffer) {
|
2022-05-23 09:34:28 +02:00
|
|
|
_print_warning();
|
2021-04-20 18:40:24 +02:00
|
|
|
return RID();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void set_build_quality(RS::ViewportOcclusionCullingBuildQuality p_quality) {}
|
|
|
|
|
|
|
|
RendererSceneOcclusionCull() {
|
|
|
|
singleton = this;
|
|
|
|
};
|
|
|
|
|
|
|
|
virtual ~RendererSceneOcclusionCull() {
|
|
|
|
singleton = nullptr;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-07-23 23:41:51 +02:00
|
|
|
#endif // RENDERER_SCENE_OCCLUSION_CULL_H
|