godot/servers/visual/portals/portal_occlusion_culler.cpp

213 lines
7.7 KiB
C++

/*************************************************************************/
/* portal_occlusion_culler.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 "portal_occlusion_culler.h"
#include "core/project_settings.h"
#include "portal_renderer.h"
void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, const LocalVector<uint32_t, uint32_t> &p_occluder_pool_ids, const Vector3 &pt_camera, const LocalVector<Plane> &p_planes) {
_num_spheres = 0;
_pt_camera = pt_camera;
real_t goodness_of_fit[MAX_SPHERES];
for (int n = 0; n < _max_spheres; n++) {
goodness_of_fit[n] = 0.0;
}
real_t weakest_fit = FLT_MAX;
int weakest_sphere = 0;
_sphere_closest_dist = FLT_MAX;
// TODO : occlusion cull spheres AGAINST themselves.
// i.e. a sphere that is occluded by another occluder is no
// use as an occluder...
// find sphere occluders
for (unsigned int o = 0; o < p_occluder_pool_ids.size(); o++) {
int id = p_occluder_pool_ids[o];
VSOccluder &occ = p_portal_renderer.get_pool_occluder(id);
// is it active?
// in the case of rooms, they will always be active, as inactive
// are removed from rooms. But for whole scene mode, some may be inactive.
if (!occ.active) {
continue;
}
if (occ.type == VSOccluder::OT_SPHERE) {
// make sure world space spheres are up to date
p_portal_renderer.occluder_ensure_up_to_date_sphere(occ);
// cull entire AABB
if (is_aabb_culled(occ.aabb, p_planes)) {
continue;
}
// multiple spheres
for (int n = 0; n < occ.list_ids.size(); n++) {
const Occlusion::Sphere &occluder_sphere = p_portal_renderer.get_pool_occluder_sphere(occ.list_ids[n]).world;
// is the occluder sphere culled?
if (is_sphere_culled(occluder_sphere.pos, occluder_sphere.radius, p_planes)) {
continue;
}
real_t dist = (occluder_sphere.pos - pt_camera).length();
// calculate the goodness of fit .. smaller distance better, and larger radius
// calculate adjusted radius at 100.0
real_t fit = 100 / MAX(dist, 0.01);
fit *= occluder_sphere.radius;
// until we reach the max, just keep recording, and keep track
// of the worst fit
if (_num_spheres < _max_spheres) {
_spheres[_num_spheres] = occluder_sphere;
_sphere_distances[_num_spheres] = dist;
goodness_of_fit[_num_spheres] = fit;
if (fit < weakest_fit) {
weakest_fit = fit;
weakest_sphere = _num_spheres;
}
// keep a record of the closest sphere for quick rejects
if (dist < _sphere_closest_dist) {
_sphere_closest_dist = dist;
}
_num_spheres++;
} else {
// must beat the weakest
if (fit > weakest_fit) {
_spheres[weakest_sphere] = occluder_sphere;
_sphere_distances[weakest_sphere] = dist;
goodness_of_fit[weakest_sphere] = fit;
// keep a record of the closest sphere for quick rejects
if (dist < _sphere_closest_dist) {
_sphere_closest_dist = dist;
}
// the weakest may have changed (this could be done more efficiently)
weakest_fit = FLT_MAX;
for (int s = 0; s < _max_spheres; s++) {
if (goodness_of_fit[s] < weakest_fit) {
weakest_fit = goodness_of_fit[s];
weakest_sphere = s;
}
}
}
}
}
} // sphere
} // for o
// force the sphere closest distance to above zero to prevent
// divide by zero in the quick reject
_sphere_closest_dist = MAX(_sphere_closest_dist, 0.001);
// sphere self occlusion.
// we could avoid testing the closest sphere, but the complexity isn't worth any speed benefit
for (int n = 0; n < _num_spheres; n++) {
const Occlusion::Sphere &sphere = _spheres[n];
// is it occluded by another sphere?
if (cull_sphere(sphere.pos, sphere.radius, n)) {
// yes, unordered remove
_num_spheres--;
_spheres[n] = _spheres[_num_spheres];
_sphere_distances[n] = _sphere_distances[_num_spheres];
// repeat this n
n--;
}
}
}
bool PortalOcclusionCuller::cull_sphere(const Vector3 &p_occludee_center, real_t p_occludee_radius, int p_ignore_sphere) const {
if (!_num_spheres) {
return false;
}
// ray from origin to the occludee
Vector3 ray_dir = p_occludee_center - _pt_camera;
real_t dist_to_occludee_raw = ray_dir.length();
// account for occludee radius
real_t dist_to_occludee = dist_to_occludee_raw - p_occludee_radius;
// prevent divide by zero, and the occludee cannot be occluded if we are WITHIN
// its bounding sphere... so no need to check
if (dist_to_occludee < _sphere_closest_dist) {
return false;
}
// normalize ray
// hopefully by this point, dist_to_occludee_raw cannot possibly be zero due to above check
ray_dir *= 1.0 / dist_to_occludee_raw;
// this can probably be done cheaper with dot products but the math might be a bit fiddly to get right
for (int s = 0; s < _num_spheres; s++) {
// first get the sphere distance
real_t occluder_dist_to_cam = _sphere_distances[s];
if (dist_to_occludee < occluder_dist_to_cam) {
// can't occlude
continue;
}
// the perspective adjusted occludee radius
real_t adjusted_occludee_radius = p_occludee_radius * (occluder_dist_to_cam / dist_to_occludee);
const Occlusion::Sphere &occluder_sphere = _spheres[s];
real_t occluder_radius = occluder_sphere.radius - adjusted_occludee_radius;
if (occluder_radius > 0.0) {
occluder_radius = occluder_radius * occluder_radius;
// distance to hit
real_t dist;
if (occluder_sphere.intersect_ray(_pt_camera, ray_dir, dist, occluder_radius)) {
if ((dist < dist_to_occludee) && (s != p_ignore_sphere)) {
// occluded
return true;
}
}
} // expanded occluder radius is more than 0
}
return false;
}
PortalOcclusionCuller::PortalOcclusionCuller() {
_max_spheres = GLOBAL_GET("rendering/misc/occlusion_culling/max_active_spheres");
}