Browse Source

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.
lawnjelly 4 years ago
parent
commit
d878fe7b90

+ 36 - 9
servers/visual/portals/portal_occlusion_culler.cpp

@@ -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;
 				}

+ 37 - 9
servers/visual/portals/portal_occlusion_culler.h

@@ -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;
 			}
 		}

+ 18 - 0
servers/visual/portals/portal_renderer.h

@@ -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

+ 1 - 1
servers/visual/portals/portal_tracer.cpp

@@ -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;

+ 3 - 0
servers/visual/portals/portal_types.h

@@ -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;