Browse Source

Added frustum culling

Marko Pintera 11 years ago
parent
commit
ac3ed8de93

+ 2 - 0
BansheeCore/Include/BsCameraProxy.h

@@ -2,6 +2,7 @@
 
 #include "BsCorePrerequisites.h"
 #include "BsViewport.h"
+#include "BsConvexVolume.h"
 #include "BsMatrix4.h"
 
 namespace BansheeEngine
@@ -16,6 +17,7 @@ namespace BansheeEngine
 		INT32 priority;
 		UINT64 layer;
 		bool ignoreSceneRenderables;
+		ConvexVolume frustum;
 
 		RenderQueuePtr renderQueue;
 	};

+ 4 - 36
BansheeEngine/Include/BsCamera.h

@@ -12,6 +12,7 @@
 #include "BsRay.h"
 #include "BsComponent.h"
 #include "BsCameraProxy.h"
+#include "BsConvexVolume.h"
 
 namespace BansheeEngine 
 {
@@ -179,42 +180,9 @@ namespace BansheeEngine
 		virtual bool isCustomProjectionMatrixEnabled() const { return mCustomProjMatrix; }
 
 		/** 
-		 * @brief	Returns all frustum planes in an array.
-		 *
-		 * @note	Planes will be in order as specified by FrustumPlane enum.
-         */
-        virtual const Plane* getFrustumPlanes() const;
-
-		/** 
-		 * @brief	Returns one of the planes forming the camera frustum in world space.
-		 *
-		 * @param	plane	Index of the plane to retrieve from FrustumPlane enum.
-         */
-        virtual const Plane& getFrustumPlane(UINT16 plane) const;
-
-        /** 
-		 * @brief	Tests whether the given bounding box is visible in the frustum.
-		 *
-		 * @param	bound		Bounding box to check in world space.
-		 * @param	culledBy	(optional)	Exact frustum plane which culled the bounding box.
-         */
-        virtual bool isVisible(const AABox& bound, FrustumPlane* culledBy = 0) const;
-
-        /** 
-		 * @brief	Tests whether the given sphere is visible in the frustum.
-		 *
-		 * @param	bound		Sphere to check in world space.
-		 * @param	culledBy	(optional)	Exact frustum plane which culled the sphere.
-         */
-        virtual bool isVisible(const Sphere& bound, FrustumPlane* culledBy = 0) const;
-
-        /** 
-		 * @brief	Tests whether the given vertex is visible in the frustum.
-		 *
-		 * @param	vert		Vertex to check in world space.
-		 * @param	culledBy	(optional)	Exact frustum plane which culled the vertex.
+		 * @brief	Returns a convex volume representing the visible area of the camera.
          */
-        virtual bool isVisible(const Vector3& vert, FrustumPlane* culledBy = nullptr) const;
+        virtual const ConvexVolume& getFrustum() const;
 
 		/**
 		 * @brief	Returns the bounding of the frustum.
@@ -407,7 +375,7 @@ namespace BansheeEngine
 		mutable Matrix4 mProjMatrix; /**< Cached projection matrix that determines how are 3D points projected to a 2D viewport. */
 		mutable Matrix4 mViewMatrix; /**< Cached view matrix that determines camera position/orientation. */
 
-		mutable Plane mFrustumPlanes[6]; /**< Main clipping planes describing cameras visible area. */
+		mutable ConvexVolume mFrustum; /**< Main clipping planes describing cameras visible area. */
 		mutable bool mRecalcFrustum; /**< Should frustum be recalculated. */
 		mutable bool mRecalcFrustumPlanes; /**< Should frustum planes be recalculated. */
 		mutable float mLeft, mRight, mTop, mBottom; /**< Frustum extents. */

+ 38 - 124
BansheeEngine/Source/BsCamera.cpp

@@ -99,101 +99,12 @@ namespace BansheeEngine
 		return mViewMatrix;
 	}
 
-	const Plane* Camera::getFrustumPlanes() const
+	const ConvexVolume& Camera::getFrustum() const
 	{
 		// Make any pending updates to the calculated frustum planes
 		updateFrustumPlanes();
 
-		return mFrustumPlanes;
-	}
-
-	const Plane& Camera::getFrustumPlane(UINT16 plane) const
-	{
-		// Make any pending updates to the calculated frustum planes
-		updateFrustumPlanes();
-
-		return mFrustumPlanes[plane];
-	}
-
-	bool Camera::isVisible(const AABox& bound, FrustumPlane* culledBy) const
-	{
-		// Make any pending updates to the calculated frustum planes
-		updateFrustumPlanes();
-
-		// For each plane, see if all points are on the negative side
-		// If so, object is not visible
-		for (int plane = 0; plane < 6; ++plane)
-		{
-			// Skip far plane if infinite view frustum
-			if (plane == FRUSTUM_PLANE_FAR && mFarDist == 0)
-				continue;
-
-			Plane::Side side = mFrustumPlanes[plane].getSide(bound);
-			if (side == Plane::NEGATIVE_SIDE)
-			{
-				// ALL corners on negative side therefore out of view
-				if (culledBy)
-					*culledBy = (FrustumPlane)plane;
-
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	bool Camera::isVisible(const Vector3& vert, FrustumPlane* culledBy) const
-	{
-		// Make any pending updates to the calculated frustum planes
-		updateFrustumPlanes();
-
-		// For each plane, see if all points are on the negative side
-		// If so, object is not visible
-		for (int plane = 0; plane < 6; ++plane)
-		{
-			// Skip far plane if infinite view frustum
-			if (plane == FRUSTUM_PLANE_FAR && mFarDist == 0)
-				continue;
-
-			if (mFrustumPlanes[plane].getSide(vert) == Plane::NEGATIVE_SIDE)
-			{
-				// ALL corners on negative side therefore out of view
-				if (culledBy)
-					*culledBy = (FrustumPlane)plane;
-
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	bool Camera::isVisible(const Sphere& sphere, FrustumPlane* culledBy) const
-	{
-		// Make any pending updates to the calculated frustum planes
-		updateFrustumPlanes();
-
-		// For each plane, see if sphere is on negative side
-		// If so, object is not visible
-		for (int plane = 0; plane < 6; ++plane)
-		{
-			// Skip far plane if infinite view frustum
-			if (plane == FRUSTUM_PLANE_FAR && mFarDist == 0)
-				continue;
-
-			// If the distance from sphere center to plane is negative, and 'more negative' 
-			// than the radius of the sphere, sphere is outside frustum
-			if (mFrustumPlanes[plane].getDistance(sphere.getCenter()) < -sphere.getRadius())
-			{
-				// ALL corners on negative side therefore out of view
-				if (culledBy)
-					*culledBy = (FrustumPlane)plane;
-
-				return false;
-			}
-		}
-
-		return true;
+		return mFrustum;
 	}
 
 	void Camera::calcProjectionParameters(float& left, float& right, float& bottom, float& top) const
@@ -394,44 +305,46 @@ namespace BansheeEngine
 
 		if (mRecalcFrustumPlanes)
 		{
+			Vector<Plane> frustumPlanes(6);
 			Matrix4 combo = mProjMatrix * mViewMatrix;
 
-			mFrustumPlanes[FRUSTUM_PLANE_LEFT].normal.x = combo[3][0] + combo[0][0];
-			mFrustumPlanes[FRUSTUM_PLANE_LEFT].normal.y = combo[3][1] + combo[0][1];
-			mFrustumPlanes[FRUSTUM_PLANE_LEFT].normal.z = combo[3][2] + combo[0][2];
-			mFrustumPlanes[FRUSTUM_PLANE_LEFT].d = combo[3][3] + combo[0][3];
-
-			mFrustumPlanes[FRUSTUM_PLANE_RIGHT].normal.x = combo[3][0] - combo[0][0];
-			mFrustumPlanes[FRUSTUM_PLANE_RIGHT].normal.y = combo[3][1] - combo[0][1];
-			mFrustumPlanes[FRUSTUM_PLANE_RIGHT].normal.z = combo[3][2] - combo[0][2];
-			mFrustumPlanes[FRUSTUM_PLANE_RIGHT].d = combo[3][3] - combo[0][3];
-
-			mFrustumPlanes[FRUSTUM_PLANE_TOP].normal.x = combo[3][0] - combo[1][0];
-			mFrustumPlanes[FRUSTUM_PLANE_TOP].normal.y = combo[3][1] - combo[1][1];
-			mFrustumPlanes[FRUSTUM_PLANE_TOP].normal.z = combo[3][2] - combo[1][2];
-			mFrustumPlanes[FRUSTUM_PLANE_TOP].d = combo[3][3] - combo[1][3];
-
-			mFrustumPlanes[FRUSTUM_PLANE_BOTTOM].normal.x = combo[3][0] + combo[1][0];
-			mFrustumPlanes[FRUSTUM_PLANE_BOTTOM].normal.y = combo[3][1] + combo[1][1];
-			mFrustumPlanes[FRUSTUM_PLANE_BOTTOM].normal.z = combo[3][2] + combo[1][2];
-			mFrustumPlanes[FRUSTUM_PLANE_BOTTOM].d = combo[3][3] + combo[1][3];
-
-			mFrustumPlanes[FRUSTUM_PLANE_NEAR].normal.x = combo[3][0] + combo[2][0];
-			mFrustumPlanes[FRUSTUM_PLANE_NEAR].normal.y = combo[3][1] + combo[2][1];
-			mFrustumPlanes[FRUSTUM_PLANE_NEAR].normal.z = combo[3][2] + combo[2][2];
-			mFrustumPlanes[FRUSTUM_PLANE_NEAR].d = combo[3][3] + combo[2][3];
-
-			mFrustumPlanes[FRUSTUM_PLANE_FAR].normal.x = combo[3][0] - combo[2][0];
-			mFrustumPlanes[FRUSTUM_PLANE_FAR].normal.y = combo[3][1] - combo[2][1];
-			mFrustumPlanes[FRUSTUM_PLANE_FAR].normal.z = combo[3][2] - combo[2][2];
-			mFrustumPlanes[FRUSTUM_PLANE_FAR].d = combo[3][3] - combo[2][3];
-
-			for(int i=0; i<6; i++ ) 
+			frustumPlanes[FRUSTUM_PLANE_LEFT].normal.x = combo[3][0] + combo[0][0];
+			frustumPlanes[FRUSTUM_PLANE_LEFT].normal.y = combo[3][1] + combo[0][1];
+			frustumPlanes[FRUSTUM_PLANE_LEFT].normal.z = combo[3][2] + combo[0][2];
+			frustumPlanes[FRUSTUM_PLANE_LEFT].d = combo[3][3] + combo[0][3];
+
+			frustumPlanes[FRUSTUM_PLANE_RIGHT].normal.x = combo[3][0] - combo[0][0];
+			frustumPlanes[FRUSTUM_PLANE_RIGHT].normal.y = combo[3][1] - combo[0][1];
+			frustumPlanes[FRUSTUM_PLANE_RIGHT].normal.z = combo[3][2] - combo[0][2];
+			frustumPlanes[FRUSTUM_PLANE_RIGHT].d = combo[3][3] - combo[0][3];
+
+			frustumPlanes[FRUSTUM_PLANE_TOP].normal.x = combo[3][0] - combo[1][0];
+			frustumPlanes[FRUSTUM_PLANE_TOP].normal.y = combo[3][1] - combo[1][1];
+			frustumPlanes[FRUSTUM_PLANE_TOP].normal.z = combo[3][2] - combo[1][2];
+			frustumPlanes[FRUSTUM_PLANE_TOP].d = combo[3][3] - combo[1][3];
+
+			frustumPlanes[FRUSTUM_PLANE_BOTTOM].normal.x = combo[3][0] + combo[1][0];
+			frustumPlanes[FRUSTUM_PLANE_BOTTOM].normal.y = combo[3][1] + combo[1][1];
+			frustumPlanes[FRUSTUM_PLANE_BOTTOM].normal.z = combo[3][2] + combo[1][2];
+			frustumPlanes[FRUSTUM_PLANE_BOTTOM].d = combo[3][3] + combo[1][3];
+
+			frustumPlanes[FRUSTUM_PLANE_NEAR].normal.x = combo[3][0] + combo[2][0];
+			frustumPlanes[FRUSTUM_PLANE_NEAR].normal.y = combo[3][1] + combo[2][1];
+			frustumPlanes[FRUSTUM_PLANE_NEAR].normal.z = combo[3][2] + combo[2][2];
+			frustumPlanes[FRUSTUM_PLANE_NEAR].d = combo[3][3] + combo[2][3];
+
+			frustumPlanes[FRUSTUM_PLANE_FAR].normal.x = combo[3][0] - combo[2][0];
+			frustumPlanes[FRUSTUM_PLANE_FAR].normal.y = combo[3][1] - combo[2][1];
+			frustumPlanes[FRUSTUM_PLANE_FAR].normal.z = combo[3][2] - combo[2][2];
+			frustumPlanes[FRUSTUM_PLANE_FAR].d = combo[3][3] - combo[2][3];
+
+			for(UINT32 i = 0; i < 6; i++) 
 			{
-				float length = mFrustumPlanes[i].normal.normalize();
-				mFrustumPlanes[i].d /= length;
+				float length = frustumPlanes[i].normal.normalize();
+				frustumPlanes[i].d /= length;
 			}
 
+			mFrustum = ConvexVolume(frustumPlanes);
 			mRecalcFrustumPlanes = false;
 		}
 	}
@@ -569,6 +482,7 @@ namespace BansheeEngine
 		proxy->projMatrix = getProjectionMatrix();
 		proxy->viewMatrix = getViewMatrix();
 		proxy->viewport = mViewport->clone();
+		proxy->frustum = mFrustum;
 		proxy->ignoreSceneRenderables = mIgnoreSceneRenderables;
 
 		return proxy;

+ 24 - 14
BansheeRenderer/Source/BsBansheeRenderer.cpp

@@ -425,25 +425,35 @@ namespace BansheeEngine
 		if (!cameraProxy.ignoreSceneRenderables)
 		{
 			// Update per-object param buffers and queue render elements
-				for (auto& renderElem : mRenderableElements)
+			for (auto& renderElem : mRenderableElements)
+			{
+				if (renderElem->handler != nullptr)
+					renderElem->handler->bindPerObjectBuffers(renderElem);
+
+				if (renderElem->renderableType == RenType_LitTextured)
 				{
-					if (renderElem->handler != nullptr)
-						renderElem->handler->bindPerObjectBuffers(renderElem);
+					Matrix4 worldViewProjMatrix = viewProjMatrix * mWorldTransforms[renderElem->id];;
+					mLitTexHandler->updatePerObjectBuffers(renderElem, worldViewProjMatrix);
+				}
 
-					if (renderElem->renderableType == RenType_LitTextured)
-					{
-						Matrix4 worldViewProjMatrix = viewProjMatrix * mWorldTransforms[renderElem->id];;
-						mLitTexHandler->updatePerObjectBuffers(renderElem, worldViewProjMatrix);
-					}
+				for (auto& param : renderElem->material->params)
+				{
+					param->updateHardwareBuffers();
+				}
 
-					for (auto& param : renderElem->material->params)
-					{
-						param->updateHardwareBuffers();
-					}
+				// Do frustum culling
+				// TODO - This is bound to be a bottleneck at some point. When it is ensure that intersect
+				// methods use vector operations, as it is trivial to update them.
+				const Sphere& boundingSphere = mWorldBounds[renderElem->id].getSphere();
+				if (cameraProxy.frustum.intersects(boundingSphere))
+				{
+					// More precise with the box
+					const AABox& boundingBox = mWorldBounds[renderElem->id].getBox();
 
-					// TODO - Do frustum culling
-					renderQueue->add(renderElem, mWorldBounds[renderElem->id].getSphere().getCenter());
+					if (cameraProxy.frustum.intersects(boundingBox))
+						renderQueue->add(renderElem, boundingSphere.getCenter());
 				}
+			}
 		}
 
 		renderQueue->sort();

+ 2 - 0
BansheeUtility/BansheeUtility.vcxproj

@@ -246,6 +246,7 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsBounds.cpp" />
+    <ClCompile Include="Source\BsConvexVolume.cpp" />
     <ClCompile Include="Source\BsTaskScheduler.cpp" />
     <ClCompile Include="Source\BsThreadPool.cpp" />
     <ClCompile Include="Source\BsAABox.cpp" />
@@ -270,6 +271,7 @@
     <ClCompile Include="Source\Win32\BsTimer.cpp" />
     <ClInclude Include="Include\BsAny.h" />
     <ClInclude Include="Include\BsBounds.h" />
+    <ClInclude Include="Include\BsConvexVolume.h" />
     <ClInclude Include="Include\BsEvent.h" />
     <ClInclude Include="Include\BsSpinLock.h" />
     <ClInclude Include="Include\BsTaskScheduler.h" />

+ 6 - 0
BansheeUtility/BansheeUtility.vcxproj.filters

@@ -243,6 +243,9 @@
     <ClInclude Include="Include\BsBounds.h">
       <Filter>Header Files\Math</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsConvexVolume.h">
+      <Filter>Header Files\Math</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsThreadPool.cpp">
@@ -383,5 +386,8 @@
     <ClCompile Include="Source\BsBounds.cpp">
       <Filter>Source Files\Math</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsConvexVolume.cpp">
+      <Filter>Source Files\Math</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 32 - 0
BansheeUtility/Include/BsConvexVolume.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include "BsPrerequisitesUtil.h"
+#include "BsPlane.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Represents a convex volume defined by planes representing
+	 *			the volume border.
+	 */
+	class BS_UTILITY_EXPORT ConvexVolume
+	{
+	public:
+		ConvexVolume(const Vector<Plane>& planes);
+
+		/**
+		 * @brief	Checks does the volume intersects the provided axis aligned box.
+		 *			This will return true if the box is fully inside the volume.
+		 */
+		bool intersects(const AABox& box) const;
+
+		/**
+		 * @brief	Checks does the volume intersects the provided sphere.
+		 *			This will return true if the sphere is fully inside the volume.
+		 */
+		bool intersects(const Sphere& sphere) const;
+
+	private:
+		Vector<Plane> mPlanes;
+	};
+}

+ 52 - 0
BansheeUtility/Source/BsConvexVolume.cpp

@@ -0,0 +1,52 @@
+#include "BsConvexVolume.h"
+#include "BsAABox.h"
+#include "BsSphere.h"
+#include "BsPlane.h"
+
+namespace BansheeEngine
+{
+	ConvexVolume::ConvexVolume(const Vector<Plane>& planes)
+		:mPlanes(planes)
+	{ }
+
+	bool ConvexVolume::intersects(const AABox& box) const
+	{
+		Vector3 center = box.getCenter();
+		Vector3 extents = box.getHalfSize();
+		Vector3 absExtents(Math::abs(extents.x), Math::abs(extents.y), Math::abs(extents.z));
+
+		for (auto& plane : mPlanes)
+		{
+			float dist = center.x * plane.normal.x;
+			dist += center.y * plane.normal.y;
+			dist += center.z * plane.normal.z;
+			dist = dist - plane.d;
+
+			float pushOut = absExtents.x * Math::abs(plane.normal.x);
+			pushOut += absExtents.y * Math::abs(plane.normal.y);
+			pushOut += absExtents.z * Math::abs(plane.normal.z);
+
+			if (dist > pushOut)
+				return false;
+		}
+	}
+
+	bool ConvexVolume::intersects(const Sphere& sphere) const
+	{
+		Vector3 center = sphere.getCenter();
+		float radius = sphere.getRadius();
+
+		for (auto& plane : mPlanes)
+		{
+			float dist = center.x * plane.normal.x;
+			dist += center.y * plane.normal.y;
+			dist += center.z * plane.normal.z;
+			dist = dist - plane.d;
+
+			if (dist > radius)
+				return false;
+		}
+
+		return true;
+	}
+}

+ 0 - 1
Polish.txt

@@ -1,5 +1,4 @@
 Polish TODO:
- - Add frustum culling
  - Implement RenderQueue sorting with support for sort type, priority and separable pass
     - Use a hash list(containing type, queue, layer, etc.) for faster sorting
  - Finalize example with resolution settings and proper GUI