Sphere occluders - self occlusion and improvements

Sphere occluders are now tested for self occlusion. Spheres that are behind another sphere in the current view are superfluous so can be removed, cutting down on the runtime calculations.

AABBs are now maintained for Occluders as well as individual spheres, meaning a bunch of occluder spheres can be frustum rejected as a block.
This commit is contained in:
lawnjelly 2021-09-14 11:07:34 +01:00
parent f85ad007c0
commit d878fe7b90
5 changed files with 95 additions and 19 deletions

View File

@ -33,7 +33,7 @@
#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, const Plane *p_near_plane) {
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;
@ -65,22 +65,22 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
// 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, p_near_plane)) {
if (is_sphere_culled(occluder_sphere.pos, occluder_sphere.radius, p_planes)) {
continue;
}
real_t dist = (occluder_sphere.pos - pt_camera).length();
// keep a record of the closest sphere for quick rejects
if (dist < _sphere_closest_dist) {
_sphere_closest_dist = dist;
}
// 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);
@ -98,6 +98,11 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
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
@ -106,6 +111,11 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
_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++) {
@ -123,9 +133,26 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
// 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) const {
bool PortalOcclusionCuller::cull_sphere(const Vector3 &p_occludee_center, real_t p_occludee_radius, int p_ignore_sphere) const {
if (!_num_spheres) {
return false;
}
@ -169,7 +196,7 @@ bool PortalOcclusionCuller::cull_sphere(const Vector3 &p_occludee_center, real_t
real_t dist;
if (occluder_sphere.intersect_ray(_pt_camera, ray_dir, dist, occluder_radius)) {
if (dist < dist_to_occludee) {
if ((dist < dist_to_occludee) && (s != p_ignore_sphere)) {
// occluded
return true;
}

View File

@ -42,10 +42,25 @@ class PortalOcclusionCuller {
public:
PortalOcclusionCuller();
void prepare(PortalRenderer &p_portal_renderer, const VSRoom &p_room, const Vector3 &pt_camera, const LocalVector<Plane> &p_planes, const Plane *p_near_plane) {
prepare_generic(p_portal_renderer, p_room._occluder_pool_ids, pt_camera, p_planes, p_near_plane);
if (p_near_plane) {
static LocalVector<Plane> local_planes;
int size_wanted = p_planes.size() + 1;
if ((int)local_planes.size() != size_wanted) {
local_planes.resize(size_wanted);
}
for (int n = 0; n < (int)p_planes.size(); n++) {
local_planes[n] = p_planes[n];
}
local_planes[size_wanted - 1] = *p_near_plane;
prepare_generic(p_portal_renderer, p_room._occluder_pool_ids, pt_camera, local_planes);
} else {
prepare_generic(p_portal_renderer, p_room._occluder_pool_ids, pt_camera, p_planes);
}
}
void 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, const Plane *p_near_plane);
void 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);
bool cull_aabb(const AABB &p_aabb) const {
if (!_num_spheres) {
return false;
@ -53,21 +68,34 @@ public:
return cull_sphere(p_aabb.get_center(), p_aabb.size.length() * 0.5);
}
bool cull_sphere(const Vector3 &p_occludee_center, real_t p_occludee_radius) const;
bool cull_sphere(const Vector3 &p_occludee_center, real_t p_occludee_radius, int p_ignore_sphere = -1) const;
private:
// if a sphere is entirely in front of any of the culling planes, it can't be seen so returns false
bool is_sphere_culled(const Vector3 &p_pos, real_t p_radius, const LocalVector<Plane> &p_planes, const Plane *p_near_plane) const {
if (p_near_plane) {
real_t dist = p_near_plane->distance_to(p_pos);
bool is_sphere_culled(const Vector3 &p_pos, real_t p_radius, const LocalVector<Plane> &p_planes) const {
for (unsigned int p = 0; p < p_planes.size(); p++) {
real_t dist = p_planes[p].distance_to(p_pos);
if (dist > p_radius) {
return true;
}
}
for (unsigned int p = 0; p < p_planes.size(); p++) {
real_t dist = p_planes[p].distance_to(p_pos);
if (dist > p_radius) {
return false;
}
bool is_aabb_culled(const AABB &p_aabb, const LocalVector<Plane> &p_planes) const {
const Vector3 &size = p_aabb.size;
Vector3 half_extents = size * 0.5;
Vector3 ofs = p_aabb.position + half_extents;
for (unsigned int i = 0; i < p_planes.size(); i++) {
const Plane &p = p_planes[i];
Vector3 point(
(p.normal.x > 0) ? -half_extents.x : half_extents.x,
(p.normal.y > 0) ? -half_extents.y : half_extents.y,
(p.normal.z > 0) ? -half_extents.z : half_extents.z);
point += ofs;
if (p.is_point_over(point)) {
return true;
}
}

View File

@ -335,6 +335,10 @@ inline void PortalRenderer::occluder_ensure_up_to_date_sphere(VSOccluder &r_occl
Vector3 scale3 = tr.basis.get_scale_abs();
real_t scale = (scale3.x + scale3.y + scale3.z) / 3.0;
// update the AABB
Vector3 bb_min = Vector3(FLT_MAX, FLT_MAX, FLT_MAX);
Vector3 bb_max = Vector3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
// transform spheres
for (int n = 0; n < r_occluder.list_ids.size(); n++) {
uint32_t pool_id = r_occluder.list_ids[n];
@ -343,7 +347,21 @@ inline void PortalRenderer::occluder_ensure_up_to_date_sphere(VSOccluder &r_occl
// transform position and radius
osphere.world.pos = tr.xform(osphere.local.pos);
osphere.world.radius = osphere.local.radius * scale;
Vector3 bradius = Vector3(osphere.world.radius, osphere.world.radius, osphere.world.radius);
Vector3 bmin = osphere.world.pos - bradius;
Vector3 bmax = osphere.world.pos + bradius;
bb_min.x = MIN(bb_min.x, bmin.x);
bb_min.y = MIN(bb_min.y, bmin.y);
bb_min.z = MIN(bb_min.z, bmin.z);
bb_max.x = MAX(bb_max.x, bmax.x);
bb_max.y = MAX(bb_max.y, bmax.y);
bb_max.z = MAX(bb_max.z, bmax.z);
}
r_occluder.aabb.position = bb_min;
r_occluder.aabb.size = bb_max - bb_min;
}
#endif

View File

@ -544,7 +544,7 @@ int PortalTracer::occlusion_cull(PortalRenderer &p_portal_renderer, const Vector
local_planes[n] = p_convex[n];
}
_occlusion_culler.prepare_generic(p_portal_renderer, p_portal_renderer.get_occluders_active_list(), p_point, local_planes, nullptr);
_occlusion_culler.prepare_generic(p_portal_renderer, p_portal_renderer.get_occluders_active_list(), p_point, local_planes);
// cull each instance
int count = p_num_results;

View File

@ -400,6 +400,9 @@ struct VSOccluder {
// location for finding the room
Vector3 pt_center;
// world space aabb, only updated when dirty
AABB aabb;
// global xform
Transform xform;