summaryrefslogtreecommitdiff
path: root/Runtime/Camera/LightCulling.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Camera/LightCulling.cpp')
-rw-r--r--Runtime/Camera/LightCulling.cpp669
1 files changed, 669 insertions, 0 deletions
diff --git a/Runtime/Camera/LightCulling.cpp b/Runtime/Camera/LightCulling.cpp
new file mode 100644
index 0000000..771ebc3
--- /dev/null
+++ b/Runtime/Camera/LightCulling.cpp
@@ -0,0 +1,669 @@
+#include "UnityPrefix.h"
+#include "LightManager.h"
+#include "CullResults.h"
+#include "Light.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Geometry/Sphere.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Geometry/BoundingUtils.h"
+#include "Runtime/Math/Simd/math.h"
+#include "ShadowCulling.h"
+#include "Runtime/Profiler/Profiler.h"
+
+#include "External/Umbra/builds/interface/runtime/umbraTome.hpp"
+#include "External/Umbra/builds/interface/runtime/umbraQuery.hpp"
+
+PROFILER_INFORMATION(gCullLightConnectivity, "CullLightConnectivity", kProfilerRender);
+PROFILER_INFORMATION(gOcclusionCullLight, "OcclusionCullLight", kProfilerRender);
+
+struct LocalLightCullingParameters
+{
+ Plane eyePlane;
+ float farDistance;
+ bool enableShadows;
+ UInt32 cullingMask;
+};
+
+
+
+static float CalculateIntensityForMainLight (const Light& source)
+{
+ DebugAssert(source.GetType() == kLightDirectional);
+ if (source.GetRenderMode() == Light::kRenderNotImportant || source.GetLightmappingForRender() == Light::kLightmappingBakedOnly)
+ return 0.0f;
+
+ float lum = source.GetColor().GreyScaleValue() * source.GetIntensity();
+ if (source.GetShadows() != kShadowNone)
+ lum *= 16.0f;
+ return lum;
+}
+
+static void OcclusionCullLocalLights (const SceneCullingParameters& cullingParams, const Vector4f* lightBSpheres, IndexList& visible)
+{
+ // Umbra light culling is not supported. Just output
+ const Umbra::Visibility* umbraVisibility = cullingParams.sceneVisbilityForShadowCulling->umbraVisibility;
+ if (!cullingParams.useLightOcclusionCulling)
+ return;
+
+ PROFILER_BEGIN(gOcclusionCullLight, NULL)
+
+ Umbra::OcclusionBuffer* occlusionBuffer = umbraVisibility->getOutputBuffer();
+
+ // Mark lights visible and add them to lightVisibleBits
+ size_t visibleCount = 0;
+ for (int l = 0; l < visible.size; l++)
+ {
+ int lightNdx = visible[l];
+
+ const float* lightFloatParams = reinterpret_cast<const float*> (lightBSpheres + lightNdx);
+
+ float r = lightFloatParams[3];
+ Vector3f r3(r, r, r);
+ Vector3f lc = Vector3f(lightFloatParams[0], lightFloatParams[1], lightFloatParams[2]);
+
+ Vector3f mn = lc - r3;
+ Vector3f mx = lc + r3;
+
+ bool isLightVisible = occlusionBuffer->isAABBVisible((const Umbra::Vector3&)mn, (const Umbra::Vector3&)mx);
+ if (isLightVisible)
+ visible.indices[visibleCount++] = lightNdx;
+ }
+ visible.size = visibleCount;
+
+ PROFILER_END
+
+ PROFILER_BEGIN(gCullLightConnectivity, NULL)
+
+
+ ///@TODO: This doesn't make sense for non-shadowed lights...
+
+ // Use connectivity data to detect which lights are can touch any visible geometry
+ Umbra::IndexList visibleLightList(visible.indices, visible.size, visible.size);
+ Umbra::QueryExt* umbraQuery = (Umbra::QueryExt*)cullingParams.umbraQuery;
+
+ umbraQuery->queryLocalLights(
+ visibleLightList,
+ 0,
+ (const Umbra::SphereLight*)lightBSpheres,
+ visible.reservedSize,
+ *umbraVisibility->getOutputClusters(),
+ &visibleLightList);
+
+ visible.size = visibleLightList.getSize();
+
+ PROFILER_END
+}
+
+static void FrustumCullLocalLights (const CullingParameters& cullingParameters, const Vector4f* lightBSpheres, IndexList& visibleLights, IndexList& offScreenLights, float* visibilityFades)
+{
+ int visibleLightCount = 0;
+ int offScreenLightCount = 0;
+
+ for (int i=0;i<visibleLights.reservedSize;i++)
+ {
+ float distance = PointDistanceToFrustum(lightBSpheres[i], cullingParameters.cullingPlanes, cullingParameters.cullingPlaneCount);
+
+ // Light is inside or intersecting the frustum
+ if (distance < lightBSpheres[i].w)
+ {
+ // lights that intersect or are inside the frustum
+ DebugAssert(visibleLightCount < visibleLights.reservedSize);
+ visibleLights.indices[visibleLightCount++] = i;
+ }
+ // Light is outside of the frustum and must be faded out
+ else if (distance < lightBSpheres[i].w + lightBSpheres[i].w)
+ {
+ //off screen lights whose distance from frustum is less than light radius
+ DebugAssert(offScreenLightCount < offScreenLights.reservedSize);
+
+ offScreenLights.indices[offScreenLightCount] = i;
+
+ distance -= lightBSpheres[i].w;
+
+ distance = distance / lightBSpheres[i].w;
+ visibilityFades[offScreenLightCount++] = 1.0F - distance;
+ DebugAssert(distance > 0.0f);
+ DebugAssert(distance < 1.0f);
+ }
+ }
+ visibleLights.size = visibleLightCount;
+ offScreenLights.size = offScreenLightCount;
+}
+
+static bool IsValidRenderingLight (const Light& light, LightType lightType, UInt32 cullingMask)
+{
+ const Light::Lightmapping lightmappingMode = light.GetLightmappingForRender();
+ // If light is lightmap only - just skip it
+ if (lightmappingMode == Light::kLightmappingBakedOnly)
+ return false;
+
+ // If light not visible in camera's culling mask - just skip it
+ if ((light.GetCullingMask() & cullingMask) == 0)
+ return false;
+
+ // Light with zero intensity - just skip it
+ if (light.GetIntensity() < 0.01f)
+ return false;
+
+ // Check if light has valid properties
+ return light.IsValidToRender();
+}
+
+static void SetupActiveDirectionalLight (const Light& light, ActiveLight& outLight)
+{
+ const Light::Lightmapping lightmappingMode = light.GetLightmappingForRender();
+
+ outLight.light = const_cast<Light*> (&light);
+#if ENABLE_SHADOWS
+ outLight.insideShadowRange = true;
+#endif
+ outLight.boundingBox = AABB(Vector3f::zero, Vector3f::infinityVec);
+
+ outLight.lightmappingForRender = lightmappingMode;
+ outLight.isVisibleInPrepass = true;
+ outLight.screenRect = Rectf(0,0,1,1);
+ outLight.cullingMask = light.GetCullingMask();
+ outLight.hasCookie = light.GetCookie() ? true : false;
+ outLight.lightRenderMode = light.GetRenderMode();
+ outLight.lightType = light.GetType();
+ outLight.isOffscreenVertexLight = false;
+ outLight.visibilityFade = 1.0;
+}
+
+static int FindBestMainDirectionalLight (const Light** lights, size_t count)
+{
+ // Find main directional light based on intensity
+ int mainLightIndex = -1;
+ float bestMainLightIntensity = 0.0f;
+
+ for (int i=0;i<count;i++)
+ {
+ const Light& light = *lights[i];
+ float mainLightIntensity = CalculateIntensityForMainLight(light);
+ if (mainLightIntensity > bestMainLightIntensity)
+ {
+ mainLightIndex = i;
+ bestMainLightIntensity = mainLightIntensity;
+ }
+ }
+
+ return mainLightIndex;
+}
+
+static void AddDirectionalLights (const Light** lights, size_t count, ActiveLights& outLights)
+{
+ Assert(outLights.lights.size() == 0);
+
+ // Add main light as the first light!
+ int mainLightIndex = FindBestMainDirectionalLight(lights, count);
+ if (mainLightIndex != -1)
+ {
+ SetupActiveDirectionalLight (*lights[mainLightIndex], outLights.lights.push_back());
+ outLights.hasMainLight = true;
+ }
+ else
+ outLights.hasMainLight = false;
+
+ // Add any other lights
+ for (int i=0;i<count;i++)
+ {
+ if (i == mainLightIndex)
+ continue;
+
+ SetupActiveDirectionalLight (*lights[i], outLights.lights.push_back());
+ }
+ outLights.numDirLights = outLights.lights.size();
+}
+
+static void SetupActiveLocalLight (const LocalLightCullingParameters& params, const ShadowCullData& shadowCullData, const Light& light, const Vector4f& lightBSpheres, const Rectf lightScreenRectangle, bool isVisible, float visibilityFade, ActiveLight& outLight)
+{
+ const Transform& trans = light.GetComponent(Transform);
+ Matrix4x4f lightMatrix = trans.GetLocalToWorldMatrixNoScale();
+ float radius = lightBSpheres.w;
+ Vector3f center = Vector3f(lightBSpheres.x, lightBSpheres.y, lightBSpheres.z);
+ float nearDistanceFudged = shadowCullData.camera->GetNear() * 1.001f;
+ float farDistanceFudged = shadowCullData.camera->GetFar() * 0.999f;
+
+ // Add to spot or point lights
+ outLight.light = const_cast<Light*> (&light);
+ float viewDistance = params.eyePlane.GetDistanceToPoint(center);
+ float closestDistance = std::numeric_limits<float>::infinity();
+ float farthestDistance = -closestDistance;
+
+ outLight.isVisibleInPrepass = isVisible;
+ outLight.screenRect = lightScreenRectangle;
+ outLight.visibilityFade = visibilityFade;
+
+ // If light survived from culling, but is not visible
+ outLight.isOffscreenVertexLight = !isVisible;
+
+ // Baked-only lights are already rejected, so lightmaps are either off or auto
+ const Light::Lightmapping lightmappingMode = light.GetLightmappingForRender();
+ outLight.lightmappingForRender = lightmappingMode;
+
+ // Keep cached copy of culling mask for efficiency
+ outLight.cullingMask = light.GetCullingMask();
+ outLight.hasCookie = light.GetCookie() ? true : false;
+ outLight.lightRenderMode = light.GetRenderMode();
+
+ LightType lightType = light.GetType();
+ outLight.lightType = lightType;
+
+ if (lightType == kLightSpot)
+ {
+ // Find nearest point
+ SpotLightBounds spotBounds;
+ CalculateSpotLightBounds (light.GetRange(), light.GetCotanHalfSpotAngle(), lightMatrix, spotBounds);
+ const Vector3f* points = spotBounds.points;
+
+ for (int i = 0; i < SpotLightBounds::kPointCount; i++)
+ {
+ float dist = params.eyePlane.GetDistanceToPoint(points[i]);
+ closestDistance = std::min (closestDistance, dist);
+ farthestDistance = std::max (farthestDistance, dist);
+ }
+ outLight.intersectsNear = closestDistance <= nearDistanceFudged;
+ outLight.intersectsFar = farthestDistance >= farDistanceFudged;
+
+ // Nearest point is also bounded by light radius (cull by far plane distance)
+ float dist = viewDistance - radius;
+ closestDistance = std::max(closestDistance, dist);
+ if (closestDistance > params.farDistance)
+ {
+ outLight.isVisibleInPrepass = false;
+ outLight.screenRect = Rectf(0,0,0,0);
+ }
+
+ // Compute bounding box
+ MinMaxAABB bounds(points[0], points[0]);
+ for (int i = 1; i < SpotLightBounds::kPointCount; i++)
+ bounds.Encapsulate(points[i]);
+ outLight.boundingBox = AABB(bounds);
+ }
+ else
+ {
+ DebugAssert(lightType == kLightPoint);
+ closestDistance = viewDistance - radius;
+ Vector3f boxSize(radius, radius, radius);
+ outLight.boundingBox = AABB(center, boxSize);
+
+ #if GFX_USE_SPHERE_FOR_POINT_LIGHT
+ // If we're drawing an icosphere or icosahedron, check for the radius of a sphere
+ // circumscribed on an icosahedron, which was circumscribed on a unit sphere.
+ const float proxyMeshSize = 1.27f;
+ #else
+ // If we're drawing a bounding cube, check if the farthest corner would not cross the near plane
+ const float proxyMeshSize = 1.7321f;
+ #endif
+ const float intersectionRadius = radius * proxyMeshSize;
+ outLight.intersectsNear = (viewDistance - intersectionRadius) <= nearDistanceFudged;
+ outLight.intersectsFar = (viewDistance + intersectionRadius) >= farDistanceFudged;
+ }
+
+#if ENABLE_SHADOWS
+ // TODO: tighter shadow culling for spot lights
+ outLight.insideShadowRange = (closestDistance < shadowCullData.shadowDistance) && params.enableShadows;
+ if (outLight.insideShadowRange && shadowCullData.useSphereCulling)
+ {
+ float sumRadii = shadowCullData.shadowCullRadius + radius;
+ if (SqrMagnitude(center - shadowCullData.shadowCullCenter) > Sqr(sumRadii))
+ outLight.insideShadowRange = false;
+ else if (!IsObjectWithinShadowRange(shadowCullData, outLight.boundingBox))
+ outLight.insideShadowRange = false;
+ }
+
+ // If light is auto but behind shadow distance (so dual lightmaps normally) - just skip it
+ if ((lightmappingMode == Light::kLightmappingAuto) && !outLight.insideShadowRange)
+ {
+ outLight.isVisibleInPrepass = false;
+ outLight.screenRect = Rectf(0,0,0,0);
+ }
+#endif
+}
+
+// Function returns true if screen rectangle (outRect) is inside camera viewport
+// Returned rectangle is 0..1 coordinates
+bool CalculateLightScreenBounds (const Matrix4x4f& cameraWorldToClip, const Light& light, const Matrix4x4f& lightMatrix, Rectf& outRect)
+{
+ Assert ( light.GetType() != kLightDirectional );
+
+ // Compute the hull of light's bounds
+ Vector3f lightPos = lightMatrix.GetPosition();
+
+ UInt8 hullFaces;
+ UInt8 hullCounts[6]; // 6 faces
+ Vector3f hullPoints[24]; // this input hull has maximum of 6 faces x 4 points hence 24 vectors
+
+ switch( light.GetType() )
+ {
+ case kLightSpot:
+ // Spot light's hull is the light position and four points on the plane at Range
+ {
+ SpotLightBounds spotBounds;
+ CalculateSpotLightBounds(light.GetRange(), light.GetCotanHalfSpotAngle(), lightMatrix, spotBounds);
+ const Vector3f* points = spotBounds.points;
+
+ hullFaces = 5;
+ hullCounts[0] = 4;
+ hullCounts[1] = hullCounts[2] = hullCounts[3] = hullCounts[4] = 3;
+
+ // far plane
+ hullPoints[0] = points[4]; hullPoints[1] = points[3]; hullPoints[2] = points[2]; hullPoints[3] = points[1];
+
+ // sides
+ hullPoints[ 4] = points[0]; hullPoints[ 5] = points[1]; hullPoints[ 6] = points[2];
+ hullPoints[ 7] = points[0]; hullPoints[ 8] = points[2]; hullPoints[ 9] = points[3];
+ hullPoints[10] = points[0]; hullPoints[11] = points[3]; hullPoints[12] = points[4];
+ hullPoints[13] = points[0]; hullPoints[14] = points[4]; hullPoints[15] = points[1];
+ }
+ break;
+
+ case kLightPoint:
+ // Point light's hull is the cube at position with half-size Range
+ {
+ float r = light.GetRange();
+ Vector3f points[8];
+ points[0].Set( lightPos.x - r, lightPos.y - r, lightPos.z - r );
+ points[1].Set( lightPos.x + r, lightPos.y - r, lightPos.z - r );
+ points[2].Set( lightPos.x + r, lightPos.y + r, lightPos.z - r );
+ points[3].Set( lightPos.x - r, lightPos.y + r, lightPos.z - r );
+ points[4].Set( lightPos.x - r, lightPos.y - r, lightPos.z + r );
+ points[5].Set( lightPos.x + r, lightPos.y - r, lightPos.z + r );
+ points[6].Set( lightPos.x + r, lightPos.y + r, lightPos.z + r );
+ points[7].Set( lightPos.x - r, lightPos.y + r, lightPos.z + r );
+
+ hullFaces = 6;
+ hullCounts[0] = hullCounts[1] = hullCounts[2] = hullCounts[3] = hullCounts[4] = hullCounts[5] = 4;
+
+ hullPoints[ 0] = points[0]; hullPoints[ 1] = points[1]; hullPoints[ 2] = points[2]; hullPoints[ 3] = points[3];
+ hullPoints[ 4] = points[7]; hullPoints[ 5] = points[6]; hullPoints[ 6] = points[5]; hullPoints[ 7] = points[4];
+ hullPoints[ 8] = points[0]; hullPoints[ 9] = points[3]; hullPoints[10] = points[7]; hullPoints[11] = points[4];
+ hullPoints[12] = points[1]; hullPoints[13] = points[5]; hullPoints[14] = points[6]; hullPoints[15] = points[2];
+ hullPoints[16] = points[4]; hullPoints[17] = points[5]; hullPoints[18] = points[1]; hullPoints[19] = points[0];
+ hullPoints[20] = points[6]; hullPoints[21] = points[7]; hullPoints[22] = points[3]; hullPoints[23] = points[2];
+
+ }
+ break;
+
+ default:
+ hullFaces = 0;
+ AssertString( "Unknown light type" );
+ break;
+ }
+
+ // Clip hull by camera's near plane - needed because point behind near plane don't have
+ // proper projection on the screen.
+ Plane nearPlane;
+ ExtractProjectionNearPlane( cameraWorldToClip, &nearPlane );
+ // Push near plane forward a bit, by a small number proportional to plane's distance from
+ // the origin (precision gets worse at larger numbers).
+ nearPlane.d() = nearPlane.d() - Abs(nearPlane.d())*0.0001f;
+ DebugAssertIf(!IsNormalized(nearPlane.GetNormal()));
+
+ MinMaxAABB aabb;
+ CalcHullBounds(hullPoints, hullCounts, hullFaces, nearPlane, cameraWorldToClip, aabb);
+ outRect.Set (
+ (aabb.m_Min.x + 1.0f) * 0.5f,
+ (aabb.m_Min.y + 1.0f) * 0.5f,
+ (aabb.m_Max.x - aabb.m_Min.x) * 0.5f,
+ (aabb.m_Max.y - aabb.m_Min.y) * 0.5f
+ );
+
+ // Is screen rect inside viewport [0,1]
+ return ((aabb.m_Max.x > aabb.m_Min.x) || (aabb.m_Max.y > aabb.m_Min.y)) ? true : false;
+}
+
+void AddActiveLocalLights (const LocalLightCullingParameters& params, const ShadowCullData& shadowCullData, const Vector4f* lightBSpheres, const Light** lights, const IndexList& visibleLocalLights, float* visibilityFades, IndexList& offScreenLocalLights, ActiveLights& outLights)
+{
+ int offScreenLightCount = offScreenLocalLights.size;
+
+ //Add spot lights first, and point lights second
+ int lightTypes[2] = {kLightSpot, kLightPoint};
+ int lightCount[2] = {0, 0};
+ for (int j=0; j<2; j++)
+ for (int i=0;i<visibleLocalLights.size;i++)
+ {
+ int lightIndex = visibleLocalLights[i];
+ const Light &light = *lights[lightIndex];
+ if (light.GetType() == lightTypes[j])
+ {
+ // Calculate local light screen rectangle
+ const Transform& trans = lights[lightIndex]->GetComponent(Transform);
+ Matrix4x4f lightMatrix = trans.GetLocalToWorldMatrixNoScale();
+
+ Rectf lightScreenRect;
+ bool isInside = CalculateLightScreenBounds (shadowCullData.cameraWorldToClip, *lights[lightIndex], lightMatrix, lightScreenRect);
+
+ // Setup visible local light if it is inside camera viewport and has a valid screen rectangle
+ if (isInside && !lightScreenRect.IsEmpty())
+ {
+ SetupActiveLocalLight (params, shadowCullData, *lights[lightIndex], lightBSpheres[lightIndex], lightScreenRect, true, 1.0f, outLights.lights.push_back());
+ lightCount[j]++;
+ }
+ else if (!isInside) // change visible light to off screen light if it is outside camera viewport
+ {
+ visibilityFades[offScreenLightCount] = 1.0f; // don't fade as the local light is close to the frustum
+ offScreenLocalLights[offScreenLightCount++] = lightIndex;
+ }
+ }
+ }
+
+ outLights.numSpotLights = lightCount[0];
+ outLights.numPointLights = lightCount[1];
+
+ //Add off screen spot lights third, and off screen point lights fourth
+ lightCount[0] = lightCount[1] = 0;
+ for (int j=0; j<2; j++)
+ for (int i=0;i<offScreenLightCount;i++)
+ {
+ int lightIndex = offScreenLocalLights[i];
+ const Light &light = *lights[lightIndex];
+ if (light.GetType() == lightTypes[j])
+ {
+ SetupActiveLocalLight (params, shadowCullData, *lights[lightIndex], lightBSpheres[lightIndex], Rectf(0,0,0,0), false, visibilityFades[i], outLights.lights.push_back());
+ lightCount[j]++;
+ }
+ }
+
+ outLights.numOffScreenSpotLights = lightCount[0];
+ outLights.numOffScreenPointLights = lightCount[1];
+}
+
+void FindAndCullActiveLights (const SceneCullingParameters& sceneCullParameters, const ShadowCullData& cullData, ActiveLights& outLights)
+{
+ const List<Light>& allLights = GetLightManager().GetAllLights();
+
+ LocalLightCullingParameters localLightCullParameters;
+ localLightCullParameters.eyePlane.SetNormalAndPosition(cullData.viewDir, cullData.eyePos);
+ localLightCullParameters.farDistance = cullData.camera->GetFar();
+ localLightCullParameters.enableShadows = cullData.shadowDistance > cullData.camera->GetNear();
+ localLightCullParameters.cullingMask = cullData.camera->GetCullingMask();
+
+ size_t lightCount = allLights.size_slow();
+
+ dynamic_array<Vector4f> localLightBSpheres (kMemTempAlloc);
+ dynamic_array<const Light*> localLights (kMemTempAlloc);
+ dynamic_array<const Light*> directionalLights (kMemTempAlloc);
+ dynamic_array<float> offScreenLocalLightvisibilityFades (kMemTempAlloc);
+
+ localLightBSpheres.reserve(lightCount);
+ localLights.reserve(lightCount);
+ directionalLights.reserve(lightCount);
+ offScreenLocalLightvisibilityFades.reserve(lightCount);
+
+ LightManager::Lights::const_iterator it, itEnd = allLights.end();
+ for( it = allLights.begin(); it != itEnd; ++it)
+ {
+ const Light& light = *it;
+ LightType lightType = light.GetType();
+
+ if (!IsValidRenderingLight (light, lightType, localLightCullParameters.cullingMask))
+ continue;
+
+ // Add directional light
+ if (lightType == kLightDirectional)
+ {
+ directionalLights.push_back(&light);
+ }
+ // Setup data necessary for culling point / spot lights
+ else if (lightType == kLightPoint || lightType == kLightSpot)
+ {
+ float radius = light.GetRange();
+
+ if (lightType == kLightSpot)
+ radius *= light.GetInvCosHalfSpotAngle();
+
+ // Set radius to negative by default, use it to skip lights in the next loop
+ Vector3f lightPos = light.GetWorldPosition();
+ localLightBSpheres.push_back ( Vector4f(lightPos.x, lightPos.y, lightPos.z, radius) );
+ localLights.push_back (&light);
+ }
+ else
+ {
+ ErrorStringObject("Unsupported light type", &light);
+ }
+ }
+
+ IndexList visibleLocalLightIndices;
+ InitIndexList(visibleLocalLightIndices, localLightBSpheres.size());
+ IndexList offScreenLocalLightIndices;
+ InitIndexList(offScreenLocalLightIndices, localLightBSpheres.size());
+
+ // 1x and 2x light radius frustum culling for local lights
+ FrustumCullLocalLights (sceneCullParameters, localLightBSpheres.begin(), visibleLocalLightIndices, offScreenLocalLightIndices, offScreenLocalLightvisibilityFades.begin());
+
+ // Occlusion cull 1x local lights
+ OcclusionCullLocalLights (sceneCullParameters, localLightBSpheres.begin(), visibleLocalLightIndices);
+
+ // Reserve memory for lights...
+ outLights.lights.reserve (visibleLocalLightIndices.size + offScreenLocalLightIndices.size + directionalLights.size());
+
+ // Add directional lights to the outLights...
+ AddDirectionalLights (directionalLights.begin(), directionalLights.size(), outLights);
+
+ // Add visible local lights and off screen local lights to the outlights...
+ AddActiveLocalLights (localLightCullParameters, cullData, localLightBSpheres.begin(), localLights.begin(), visibleLocalLightIndices, offScreenLocalLightvisibilityFades.begin(), offScreenLocalLightIndices, outLights);
+
+ DestroyIndexList(visibleLocalLightIndices);
+ DestroyIndexList(offScreenLocalLightIndices);
+}
+
+
+static bool IsLightCulled (const ActiveLight& light, int layerMask, bool lightmappedObject, bool dualLightmapsMode)
+{
+ // Skip light if has lightmap for object
+ if ( (lightmappedObject && light.lightmappingForRender == Light::kLightmappingAuto) && (dualLightmapsMode == false) )
+ return true;
+
+ // Cull by layer mask
+ if ((layerMask & light.cullingMask) == 0)
+ return true;
+
+ return false;
+}
+
+static bool IsDirectionalLightCulled (const ActiveLight& light, int layerMask, bool lightmappedObject, bool dualLightmapsMode)
+{
+ DebugAssert(light.light->GetType() == kLightDirectional);
+
+ return IsLightCulled (light, layerMask, lightmappedObject, dualLightmapsMode);
+}
+
+static bool IsSpotLightCulled (const ActiveLight& light, int layerMask, bool lightmappedObject, bool dualLightmapsMode, const AABB& globalObjectAABB, const AABB& localObjectAABB, const Matrix4x4f& objectTransform)
+{
+ DebugAssert(light.light->GetType() == kLightSpot);
+
+ // Common code
+ if (IsLightCulled (light, layerMask, lightmappedObject, dualLightmapsMode))
+ return true;
+
+ // AABB vs AABB
+ if (!IntersectAABBAABB (globalObjectAABB, light.boundingBox))
+ return true;
+
+ const Light& source = *light.light;
+
+ // Detailed culling: frustum vs local AABB
+ Plane planes[6];
+ Matrix4x4f zscale, objectToLightMatrix, projectionMatrix;
+ zscale.SetScale (Vector3f (1.0F, 1.0F, -1.0F));
+
+ const float minNearDist = 0.0001F;
+ const float minNearFarRatio = 0.00001F;
+ float nearDist = std::max(minNearDist, source.GetRange() * minNearFarRatio);
+ projectionMatrix.SetPerspectiveCotan( source.GetCotanHalfSpotAngle(), nearDist, source.GetRange() );
+
+ // objectToLightMatrix = zscale * GetWorldToLocalMatrix * objectTransform
+ Matrix4x4f temp;
+ MultiplyMatrices4x4 (&zscale, &source.GetWorldToLocalMatrix(), &temp);
+ MultiplyMatrices4x4 (&temp, &objectTransform, &objectToLightMatrix);
+
+ // finalProjMatrix = projectionMatrix * objectToLightMatrix
+ Matrix4x4f finalProjMatrix;
+ MultiplyMatrices4x4 (&projectionMatrix, &objectToLightMatrix, &finalProjMatrix);
+ ExtractProjectionPlanes (finalProjMatrix, planes);
+
+ if (!IntersectAABBFrustumFull (localObjectAABB, planes))
+ return true;
+
+ return false;
+}
+
+static bool IsPointLightCulled (const ActiveLight& light, int layerMask, bool lightmappedObject, bool dualLightmapsMode, const AABB& globalObjectAABB, const AABB& localObjectAABB, const Matrix4x4f& objectTransform, float invScale)
+{
+ DebugAssert(light.light->GetType() == kLightPoint);
+
+ // Common code
+ if (IsLightCulled (light, layerMask, lightmappedObject, dualLightmapsMode))
+ return true;
+
+ // AABB vs AABB
+ if (!IntersectAABBAABB (globalObjectAABB, light.boundingBox))
+ return true;
+
+ const Light& source = *light.light;
+
+ // Detailed culling
+ // Test light sphere transformed into the object's local space
+ // against local aabb of the object
+ Vector3f objectRelativeLightPos = objectTransform.InverseMultiplyPoint3Affine (source.GetWorldPosition()) * invScale;
+ Sphere objectRelLightSphere (objectRelativeLightPos * invScale, source.GetRange () * invScale);
+
+ if (!IntersectAABBSphere (localObjectAABB, objectRelLightSphere))
+ return true;
+
+ return false;
+}
+
+void CullPerObjectLights (const ActiveLights& activeLights, const AABB& globalObjectAABB, const AABB& localObjectAABB, const Matrix4x4f& objectTransform, float invScale, int layerMask, bool lightmappedObject, bool dualLightmapsMode, ObjectLightIndices& outIndices)
+{
+ size_t index = 0;
+ size_t endIndex = activeLights.numDirLights;
+ for ( ; index < endIndex; index++)
+ if (!IsDirectionalLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode))
+ outIndices.push_back (index);
+
+ endIndex += activeLights.numSpotLights;
+ for ( ; index < endIndex; index++)
+ if (!IsSpotLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode, globalObjectAABB, localObjectAABB, objectTransform))
+ outIndices.push_back (index);
+
+ endIndex += activeLights.numPointLights;
+ for ( ; index < endIndex; index++)
+ if (!IsPointLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode, globalObjectAABB, localObjectAABB, objectTransform, invScale))
+ outIndices.push_back (index);
+
+ endIndex += activeLights.numOffScreenSpotLights;
+ for ( ; index < endIndex; index++)
+ if (!IsSpotLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode, globalObjectAABB, localObjectAABB, objectTransform))
+ outIndices.push_back (index);
+
+ endIndex += activeLights.numOffScreenPointLights;
+ for ( ; index < endIndex; index++)
+ if (!IsPointLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode, globalObjectAABB, localObjectAABB, objectTransform, invScale))
+ outIndices.push_back (index);
+} \ No newline at end of file