Browse Source

Merge pull request #52661 from lawnjelly/portals_sphere_improvements

Sphere occluders - self occlusion and improvements
Rémi Verschelde 4 years ago
parent
commit
555108d378

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