diff --git a/engine/include/XCEngine/Rendering/RenderSceneExtractor.h b/engine/include/XCEngine/Rendering/RenderSceneExtractor.h index 52212be7..620c21df 100644 --- a/engine/include/XCEngine/Rendering/RenderSceneExtractor.h +++ b/engine/include/XCEngine/Rendering/RenderSceneExtractor.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -29,6 +30,24 @@ struct RenderDirectionalLightData { Math::Color color = Math::Color::White(); }; +enum class RenderLightType : uint32_t { + Directional = 0, + Point = 1, + Spot = 2 +}; + +struct RenderAdditionalLightData { + RenderLightType type = RenderLightType::Point; + bool enabled = false; + bool castsShadows = false; + Math::Color color = Math::Color::White(); + float intensity = 1.0f; + Math::Vector3 position = Math::Vector3::Zero(); + Math::Vector3 direction = Math::Vector3::Back(); + float range = 0.0f; + float spotAngle = 0.0f; +}; + struct RenderDirectionalShadowData { bool enabled = false; Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity(); @@ -41,8 +60,12 @@ struct RenderDirectionalShadowData { }; struct RenderLightingData { + static constexpr uint32_t kMaxAdditionalLightCount = 8u; + RenderDirectionalLightData mainDirectionalLight; RenderDirectionalShadowData mainDirectionalShadow; + std::array additionalLights = {}; + uint32_t additionalLightCount = 0u; bool HasMainDirectionalLight() const { return mainDirectionalLight.enabled; @@ -51,6 +74,10 @@ struct RenderLightingData { bool HasMainDirectionalShadow() const { return mainDirectionalShadow.IsValid(); } + + bool HasAdditionalLights() const { + return additionalLightCount > 0u; + } }; struct RenderSceneData { @@ -81,6 +108,8 @@ public: private: void ExtractLighting( const Components::Scene& scene, + const Math::Vector3& cameraPosition, + uint32_t cullingMask, RenderLightingData& lightingData) const; void ExtractVisibleItems( Components::GameObject* gameObject, diff --git a/engine/include/XCEngine/Rendering/RenderSceneUtility.h b/engine/include/XCEngine/Rendering/RenderSceneUtility.h index 5831c9ed..ac9a90b7 100644 --- a/engine/include/XCEngine/Rendering/RenderSceneUtility.h +++ b/engine/include/XCEngine/Rendering/RenderSceneUtility.h @@ -11,6 +11,7 @@ namespace XCEngine { namespace Components { class CameraComponent; class GameObject; +class LightComponent; class Scene; } // namespace Components @@ -31,5 +32,18 @@ std::vector CollectRenderItemsForEntityIds( const std::vector& entityIds, const Math::Vector3& cameraPosition); +bool IsUsableLight(const Components::LightComponent* light); + +bool IsLightVisibleForCullingMask( + const Components::LightComponent& light, + uint32_t cullingMask); + +Math::Vector3 BuildRenderLightDirection( + const Components::LightComponent& light); + +Components::LightComponent* FindMainDirectionalLight( + const Components::Scene& scene, + uint32_t cullingMask = 0xFFFFFFFFu); + } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/RenderSceneExtractor.cpp b/engine/src/Rendering/RenderSceneExtractor.cpp index 1b38332d..3bda52d5 100644 --- a/engine/src/Rendering/RenderSceneExtractor.cpp +++ b/engine/src/Rendering/RenderSceneExtractor.cpp @@ -21,11 +21,95 @@ bool IsUsableCamera(const Components::CameraComponent* camera) { camera->GetGameObject()->IsActiveInHierarchy(); } -bool IsUsableLight(const Components::LightComponent* light) { - return light != nullptr && - light->IsEnabled() && - light->GetGameObject() != nullptr && - light->GetGameObject()->IsActiveInHierarchy(); +RenderLightType ToRenderLightType(Components::LightType lightType) { + switch (lightType) { + case Components::LightType::Directional: + return RenderLightType::Directional; + case Components::LightType::Point: + return RenderLightType::Point; + case Components::LightType::Spot: + return RenderLightType::Spot; + } + + return RenderLightType::Point; +} + +RenderDirectionalLightData BuildDirectionalLightData( + const Components::LightComponent& light) { + RenderDirectionalLightData lightData; + lightData.enabled = true; + lightData.castsShadows = light.GetCastsShadows(); + lightData.intensity = light.GetIntensity(); + lightData.color = light.GetColor(); + lightData.direction = BuildRenderLightDirection(light); + return lightData; +} + +RenderAdditionalLightData BuildAdditionalLightData( + const Components::LightComponent& light) { + RenderAdditionalLightData lightData; + lightData.type = ToRenderLightType(light.GetLightType()); + lightData.enabled = true; + lightData.castsShadows = light.GetCastsShadows(); + lightData.color = light.GetColor(); + lightData.intensity = light.GetIntensity(); + + switch (light.GetLightType()) { + case Components::LightType::Directional: + lightData.direction = BuildRenderLightDirection(light); + break; + case Components::LightType::Point: + lightData.position = light.transform().GetPosition(); + lightData.range = light.GetRange(); + break; + case Components::LightType::Spot: + lightData.position = light.transform().GetPosition(); + lightData.direction = BuildRenderLightDirection(light); + lightData.range = light.GetRange(); + lightData.spotAngle = light.GetSpotAngle(); + break; + } + + return lightData; +} + +float ComputeAdditionalLightInfluence( + const Components::LightComponent& light, + const Math::Vector3& cameraPosition) { + if (light.GetIntensity() <= Math::EPSILON) { + return 0.0f; + } + + if (light.GetLightType() == Components::LightType::Directional) { + return light.GetIntensity(); + } + + const float range = std::max(light.GetRange(), 0.001f); + const float rangeSq = range * range; + const Math::Vector3 lightPosition = light.transform().GetPosition(); + const float distanceSq = (lightPosition - cameraPosition).SqrMagnitude(); + if (distanceSq >= rangeSq) { + return 0.0f; + } + + return light.GetIntensity() / std::max(distanceSq, Math::EPSILON); +} + +uint32_t GetAdditionalLightGroupRank(const Components::LightComponent& light) { + return light.GetLightType() == Components::LightType::Directional ? 0u : 1u; +} + +uint32_t GetAdditionalLightTypeRank(const Components::LightComponent& light) { + switch (light.GetLightType()) { + case Components::LightType::Directional: + return 0u; + case Components::LightType::Point: + return 1u; + case Components::LightType::Spot: + return 2u; + } + + return 3u; } bool CompareVisibleItems(const VisibleRenderItem& lhs, const VisibleRenderItem& rhs) { @@ -73,7 +157,7 @@ RenderSceneData RenderSceneExtractor::Extract( sceneData.visibleItems.begin(), sceneData.visibleItems.end(), CompareVisibleItems); - ExtractLighting(scene, sceneData.lighting); + ExtractLighting(scene, cameraPosition, cullingMask, sceneData.lighting); return sceneData; } @@ -102,7 +186,7 @@ RenderSceneData RenderSceneExtractor::ExtractForCamera( sceneData.visibleItems.begin(), sceneData.visibleItems.end(), CompareVisibleItems); - ExtractLighting(scene, sceneData.lighting); + ExtractLighting(scene, cameraPosition, cullingMask, sceneData.lighting); return sceneData; } @@ -144,43 +228,73 @@ Components::CameraComponent* RenderSceneExtractor::SelectCamera( void RenderSceneExtractor::ExtractLighting( const Components::Scene& scene, + const Math::Vector3& cameraPosition, + uint32_t cullingMask, RenderLightingData& lightingData) const { + lightingData = {}; + const std::vector lights = scene.FindObjectsOfType(); - Components::LightComponent* mainDirectionalLight = nullptr; + Components::LightComponent* mainDirectionalLight = + FindMainDirectionalLight(scene, cullingMask); + if (mainDirectionalLight != nullptr) { + lightingData.mainDirectionalLight = BuildDirectionalLightData(*mainDirectionalLight); + } + + struct AdditionalLightCandidate { + RenderAdditionalLightData data = {}; + float influence = 0.0f; + uint32_t groupRank = 0u; + uint32_t typeRank = 0u; + Components::GameObject::ID gameObjectId = Components::GameObject::INVALID_ID; + }; + + std::vector candidates; + candidates.reserve(lights.size()); for (Components::LightComponent* light : lights) { - if (!IsUsableLight(light) || - light->GetLightType() != Components::LightType::Directional) { + if (light == nullptr || + !IsLightVisibleForCullingMask(*light, cullingMask) || + light == mainDirectionalLight) { continue; } - if (mainDirectionalLight == nullptr || - light->GetIntensity() > mainDirectionalLight->GetIntensity()) { - mainDirectionalLight = light; - } + AdditionalLightCandidate candidate; + candidate.data = BuildAdditionalLightData(*light); + candidate.influence = ComputeAdditionalLightInfluence(*light, cameraPosition); + candidate.groupRank = GetAdditionalLightGroupRank(*light); + candidate.typeRank = GetAdditionalLightTypeRank(*light); + candidate.gameObjectId = light->GetGameObject() != nullptr + ? light->GetGameObject()->GetID() + : Components::GameObject::INVALID_ID; + candidates.push_back(candidate); } - if (mainDirectionalLight == nullptr) { - lightingData = {}; - return; + std::sort( + candidates.begin(), + candidates.end(), + [](const AdditionalLightCandidate& lhs, const AdditionalLightCandidate& rhs) { + if (lhs.groupRank != rhs.groupRank) { + return lhs.groupRank < rhs.groupRank; + } + + if (lhs.influence != rhs.influence) { + return lhs.influence > rhs.influence; + } + + if (lhs.typeRank != rhs.typeRank) { + return lhs.typeRank < rhs.typeRank; + } + + return lhs.gameObjectId < rhs.gameObjectId; + }); + + const uint32_t additionalLightCount = static_cast( + std::min(candidates.size(), static_cast(RenderLightingData::kMaxAdditionalLightCount))); + lightingData.additionalLightCount = additionalLightCount; + for (uint32_t index = 0; index < additionalLightCount; ++index) { + lightingData.additionalLights[index] = candidates[index].data; } - - RenderDirectionalLightData lightData; - lightData.enabled = true; - lightData.castsShadows = mainDirectionalLight->GetCastsShadows(); - lightData.intensity = mainDirectionalLight->GetIntensity(); - lightData.color = mainDirectionalLight->GetColor(); - - Math::Vector3 direction = mainDirectionalLight->transform().GetForward() * -1.0f; - if (direction.SqrMagnitude() <= Math::EPSILON) { - direction = Math::Vector3::Back(); - } else { - direction = direction.Normalized(); - } - lightData.direction = direction; - - lightingData.mainDirectionalLight = lightData; } void RenderSceneExtractor::ExtractVisibleItems( diff --git a/engine/src/Rendering/RenderSceneUtility.cpp b/engine/src/Rendering/RenderSceneUtility.cpp index e0e9a017..598a86e5 100644 --- a/engine/src/Rendering/RenderSceneUtility.cpp +++ b/engine/src/Rendering/RenderSceneUtility.cpp @@ -2,6 +2,7 @@ #include "Components/CameraComponent.h" #include "Components/GameObject.h" +#include "Components/LightComponent.h" #include "Components/MeshFilterComponent.h" #include "Components/MeshRendererComponent.h" #include "Components/TransformComponent.h" @@ -139,5 +140,57 @@ std::vector CollectRenderItemsForEntityIds( return visibleItems; } +bool IsUsableLight(const Components::LightComponent* light) { + return light != nullptr && + light->IsEnabled() && + light->GetGameObject() != nullptr && + light->GetGameObject()->IsActiveInHierarchy(); +} + +bool IsLightVisibleForCullingMask( + const Components::LightComponent& light, + uint32_t cullingMask) { + if (!IsUsableLight(&light)) { + return false; + } + + const Components::GameObject* gameObject = light.GetGameObject(); + const uint32_t gameObjectLayerMask = 1u << gameObject->GetLayer(); + return (cullingMask & gameObjectLayerMask) != 0u; +} + +Math::Vector3 BuildRenderLightDirection( + const Components::LightComponent& light) { + Math::Vector3 direction = light.transform().GetForward() * -1.0f; + if (direction.SqrMagnitude() <= Math::EPSILON) { + return Math::Vector3::Back(); + } + + return direction.Normalized(); +} + +Components::LightComponent* FindMainDirectionalLight( + const Components::Scene& scene, + uint32_t cullingMask) { + const std::vector lights = + scene.FindObjectsOfType(); + + Components::LightComponent* mainDirectionalLight = nullptr; + for (Components::LightComponent* light : lights) { + if (light == nullptr || + !IsLightVisibleForCullingMask(*light, cullingMask) || + light->GetLightType() != Components::LightType::Directional) { + continue; + } + + if (mainDirectionalLight == nullptr || + light->GetIntensity() > mainDirectionalLight->GetIntensity()) { + mainDirectionalLight = light; + } + } + + return mainDirectionalLight; +} + } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/SceneRenderRequestPlanner.cpp b/engine/src/Rendering/SceneRenderRequestPlanner.cpp index 8a2558e8..7ec4b8ea 100644 --- a/engine/src/Rendering/SceneRenderRequestPlanner.cpp +++ b/engine/src/Rendering/SceneRenderRequestPlanner.cpp @@ -1,12 +1,19 @@ #include "Rendering/SceneRenderRequestPlanner.h" +#include "Components/GameObject.h" #include "Components/LightComponent.h" +#include "Components/MeshFilterComponent.h" +#include "Components/MeshRendererComponent.h" #include "Core/Math/Matrix4.h" +#include "Core/Math/Quaternion.h" +#include "Rendering/RenderSceneUtility.h" #include "Rendering/SceneRenderRequestUtils.h" #include "Scene/Scene.h" +#include #include +#include namespace XCEngine { namespace Rendering { @@ -15,42 +22,12 @@ namespace { constexpr uint32_t kDirectionalShadowMapDimension = 1024; constexpr float kMinShadowFocusDistance = 5.0f; -constexpr float kMaxShadowFocusDistance = 25.0f; -constexpr float kPerspectiveShadowFocusFactor = 0.25f; +constexpr float kMaxShadowFocusDistance = 64.0f; +constexpr float kPerspectiveShadowFocusFactor = 1.0f; constexpr float kOrthographicShadowFocusFactor = 2.0f; -constexpr float kMinShadowHalfExtent = 10.0f; -constexpr float kMaxShadowHalfExtent = 40.0f; -constexpr float kPerspectiveShadowCoverageFactor = 0.20f; -constexpr float kOrthographicShadowCoverageFactor = 2.0f; -constexpr float kShadowNearClipPlane = 0.1f; constexpr float kMinShadowDepthRange = 20.0f; - -bool IsUsableDirectionalLight(const Components::LightComponent* light) { - return light != nullptr && - light->IsEnabled() && - light->GetGameObject() != nullptr && - light->GetGameObject()->IsActiveInHierarchy() && - light->GetLightType() == Components::LightType::Directional; -} - -Components::LightComponent* FindMainDirectionalLight(const Components::Scene& scene) { - const std::vector lights = - scene.FindObjectsOfType(); - - Components::LightComponent* mainDirectionalLight = nullptr; - for (Components::LightComponent* light : lights) { - if (!IsUsableDirectionalLight(light)) { - continue; - } - - if (mainDirectionalLight == nullptr || - light->GetIntensity() > mainDirectionalLight->GetIntensity()) { - mainDirectionalLight = light; - } - } - - return mainDirectionalLight; -} +constexpr float kShadowBoundsPadding = 1.0f; +constexpr float kMinShadowDepthPadding = 2.0f; bool ShouldPlanDirectionalShadowForCamera( const Components::CameraComponent& camera, @@ -64,9 +41,68 @@ bool ShouldPlanDirectionalShadowForCamera( return true; } +std::array BuildBoundsCorners(const Math::Bounds& bounds) { + const Math::Vector3 min = bounds.GetMin(); + const Math::Vector3 max = bounds.GetMax(); + return { + min, + Math::Vector3(max.x, min.y, min.z), + Math::Vector3(min.x, max.y, min.z), + Math::Vector3(min.x, min.y, max.z), + max, + Math::Vector3(min.x, max.y, max.z), + Math::Vector3(max.x, min.y, max.z), + Math::Vector3(max.x, max.y, min.z) + }; +} + +Math::Bounds TransformBoundsToWorldSpace( + const Math::Bounds& localBounds, + const Math::Matrix4x4& localToWorld, + std::array* outWorldCorners = nullptr) { + const std::array localCorners = BuildBoundsCorners(localBounds); + std::array worldCorners = {}; + for (size_t index = 0; index < localCorners.size(); ++index) { + worldCorners[index] = localToWorld.MultiplyPoint(localCorners[index]); + } + + Math::Bounds worldBounds(worldCorners[0], Math::Vector3::Zero()); + for (size_t index = 1; index < worldCorners.size(); ++index) { + worldBounds.Encapsulate(worldCorners[index]); + } + + if (outWorldCorners != nullptr) { + *outWorldCorners = worldCorners; + } + + return worldBounds; +} + +void ExpandLightSpaceBounds( + const Math::Matrix4x4& view, + const std::array& worldCorners, + float& minX, + float& maxX, + float& minY, + float& maxY, + float& minZ, + float& maxZ) { + for (const Math::Vector3& corner : worldCorners) { + const Math::Vector3 cornerLS = view.MultiplyPoint(corner); + minX = std::min(minX, cornerLS.x); + maxX = std::max(maxX, cornerLS.x); + minY = std::min(minY, cornerLS.y); + maxY = std::max(maxY, cornerLS.y); + minZ = std::min(minZ, cornerLS.z); + maxZ = std::max(maxZ, cornerLS.z); + } +} + DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( + const Components::Scene& scene, const Components::CameraComponent& camera, - const Components::LightComponent& light) { + const Components::LightComponent& light, + float viewportAspect) { DirectionalShadowRenderPlan plan = {}; if (!light.GetCastsShadows()) { return plan; @@ -82,9 +118,16 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( const Math::Vector3 viewForward = camera.transform().GetForward().SqrMagnitude() <= Math::EPSILON ? Math::Vector3::Forward() : camera.transform().GetForward().Normalized(); + const Math::Vector3 viewRight = camera.transform().GetRight().SqrMagnitude() <= Math::EPSILON + ? Math::Vector3::Right() + : camera.transform().GetRight().Normalized(); + const Math::Vector3 viewUp = camera.transform().GetUp().SqrMagnitude() <= Math::EPSILON + ? Math::Vector3::Up() + : camera.transform().GetUp().Normalized(); const Math::Vector3 cameraPosition = camera.transform().GetPosition(); + const float aspect = viewportAspect > Math::EPSILON ? viewportAspect : 1.0f; - const float focusDistance = camera.GetProjectionType() == Components::CameraProjectionType::Perspective + const float shadowDistance = camera.GetProjectionType() == Components::CameraProjectionType::Perspective ? std::clamp( camera.GetFarClipPlane() * kPerspectiveShadowFocusFactor, kMinShadowFocusDistance, @@ -93,42 +136,151 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( camera.GetOrthographicSize() * kOrthographicShadowFocusFactor, kMinShadowFocusDistance, kMaxShadowFocusDistance); - const Math::Vector3 focusPoint = cameraPosition + viewForward * focusDistance; + const float sliceNear = std::max(camera.GetNearClipPlane(), 0.1f); + const float sliceFar = std::max(sliceNear + 0.1f, shadowDistance); - const float shadowHalfExtent = camera.GetProjectionType() == Components::CameraProjectionType::Perspective - ? std::clamp( - camera.GetFarClipPlane() * kPerspectiveShadowCoverageFactor, - kMinShadowHalfExtent, - kMaxShadowHalfExtent) - : std::clamp( - camera.GetOrthographicSize() * kOrthographicShadowCoverageFactor, - kMinShadowHalfExtent, - kMaxShadowHalfExtent); - const float shadowDepthRange = std::max(shadowHalfExtent * 4.0f, kMinShadowDepthRange); + std::array frustumCorners = {}; + if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) { + const float tanHalfFov = std::tan(camera.GetFieldOfView() * Math::DEG_TO_RAD * 0.5f); + const float nearHalfHeight = tanHalfFov * sliceNear; + const float nearHalfWidth = nearHalfHeight * aspect; + const float farHalfHeight = tanHalfFov * sliceFar; + const float farHalfWidth = farHalfHeight * aspect; + + const Math::Vector3 nearCenter = cameraPosition + viewForward * sliceNear; + const Math::Vector3 farCenter = cameraPosition + viewForward * sliceFar; + + frustumCorners[0] = nearCenter - viewRight * nearHalfWidth - viewUp * nearHalfHeight; + frustumCorners[1] = nearCenter + viewRight * nearHalfWidth - viewUp * nearHalfHeight; + frustumCorners[2] = nearCenter - viewRight * nearHalfWidth + viewUp * nearHalfHeight; + frustumCorners[3] = nearCenter + viewRight * nearHalfWidth + viewUp * nearHalfHeight; + frustumCorners[4] = farCenter - viewRight * farHalfWidth - viewUp * farHalfHeight; + frustumCorners[5] = farCenter + viewRight * farHalfWidth - viewUp * farHalfHeight; + frustumCorners[6] = farCenter - viewRight * farHalfWidth + viewUp * farHalfHeight; + frustumCorners[7] = farCenter + viewRight * farHalfWidth + viewUp * farHalfHeight; + } else { + const float orthoHalfHeight = camera.GetOrthographicSize(); + const float orthoHalfWidth = orthoHalfHeight * aspect; + const Math::Vector3 nearCenter = cameraPosition + viewForward * sliceNear; + const Math::Vector3 farCenter = cameraPosition + viewForward * sliceFar; + + frustumCorners[0] = nearCenter - viewRight * orthoHalfWidth - viewUp * orthoHalfHeight; + frustumCorners[1] = nearCenter + viewRight * orthoHalfWidth - viewUp * orthoHalfHeight; + frustumCorners[2] = nearCenter - viewRight * orthoHalfWidth + viewUp * orthoHalfHeight; + frustumCorners[3] = nearCenter + viewRight * orthoHalfWidth + viewUp * orthoHalfHeight; + frustumCorners[4] = farCenter - viewRight * orthoHalfWidth - viewUp * orthoHalfHeight; + frustumCorners[5] = farCenter + viewRight * orthoHalfWidth - viewUp * orthoHalfHeight; + frustumCorners[6] = farCenter - viewRight * orthoHalfWidth + viewUp * orthoHalfHeight; + frustumCorners[7] = farCenter + viewRight * orthoHalfWidth + viewUp * orthoHalfHeight; + } + + Math::Vector3 focusPoint = Math::Vector3::Zero(); + for (const Math::Vector3& corner : frustumCorners) { + focusPoint += corner; + } + focusPoint /= static_cast(frustumCorners.size()); + + Math::Bounds frustumWorldBounds(frustumCorners[0], Math::Vector3::Zero()); + for (size_t index = 1; index < frustumCorners.size(); ++index) { + frustumWorldBounds.Encapsulate(frustumCorners[index]); + } + + const float shadowViewDistance = std::max(sliceFar, kMinShadowDepthRange * 0.5f); const Math::Vector3 shadowWorldPosition = - focusPoint - lightDirection * (shadowDepthRange * 0.5f); + focusPoint + lightDirection * shadowViewDistance; Math::Vector3 shadowUp = Math::Vector3::Up(); if (std::abs(Math::Vector3::Dot(lightDirection, shadowUp)) > 0.98f) { shadowUp = Math::Vector3::Forward(); } - const Math::Matrix4x4 view = - Math::Matrix4x4::LookAt(shadowWorldPosition, focusPoint, shadowUp); + const Math::Vector3 shadowForward = (focusPoint - shadowWorldPosition).SqrMagnitude() <= Math::EPSILON + ? (lightDirection * -1.0f) + : (focusPoint - shadowWorldPosition).Normalized(); + const Math::Quaternion shadowRotation = Math::Quaternion::LookRotation(shadowForward, shadowUp); + const Math::Matrix4x4 view = Math::Matrix4x4::TRS( + shadowWorldPosition, + shadowRotation, + Math::Vector3::One()).Inverse(); + + float minX = std::numeric_limits::max(); + float maxX = std::numeric_limits::lowest(); + float minY = std::numeric_limits::max(); + float maxY = std::numeric_limits::lowest(); + float minZ = std::numeric_limits::max(); + float maxZ = std::numeric_limits::lowest(); + ExpandLightSpaceBounds(view, frustumCorners, minX, maxX, minY, maxY, minZ, maxZ); + + const uint32_t cullingMask = camera.GetCullingMask(); + const std::vector meshFilters = + scene.FindObjectsOfType(); + for (Components::MeshFilterComponent* meshFilter : meshFilters) { + if (meshFilter == nullptr || + !meshFilter->IsEnabled() || + meshFilter->GetGameObject() == nullptr) { + continue; + } + + Components::GameObject* gameObject = meshFilter->GetGameObject(); + if (!gameObject->IsActiveInHierarchy()) { + continue; + } + + const uint32_t gameObjectLayerMask = 1u << gameObject->GetLayer(); + if ((cullingMask & gameObjectLayerMask) == 0u) { + continue; + } + + Components::MeshRendererComponent* meshRenderer = + gameObject->GetComponent(); + if (meshRenderer == nullptr || + !meshRenderer->IsEnabled() || + (!meshRenderer->GetCastShadows() && !meshRenderer->GetReceiveShadows())) { + continue; + } + + Resources::Mesh* mesh = meshFilter->GetMesh(); + if (mesh == nullptr || !mesh->IsValid()) { + continue; + } + + std::array worldCorners = {}; + const Math::Bounds worldBounds = TransformBoundsToWorldSpace( + mesh->GetBounds(), + gameObject->GetTransform()->GetLocalToWorldMatrix(), + &worldCorners); + if (!frustumWorldBounds.Intersects(worldBounds)) { + continue; + } + + ExpandLightSpaceBounds(view, worldCorners, minX, maxX, minY, maxY, minZ, maxZ); + } + + minX -= kShadowBoundsPadding; + maxX += kShadowBoundsPadding; + minY -= kShadowBoundsPadding; + maxY += kShadowBoundsPadding; + minZ -= kMinShadowDepthPadding; + maxZ += kMinShadowDepthPadding; + + const float shadowHalfExtent = std::max( + std::max(std::abs(minX), std::abs(maxX)), + std::max(std::abs(minY), std::abs(maxY))); + const float shadowDepthRange = std::max(maxZ - minZ, kMinShadowDepthRange); const Math::Matrix4x4 projection = Math::Matrix4x4::Orthographic( - -shadowHalfExtent, - shadowHalfExtent, - -shadowHalfExtent, - shadowHalfExtent, - kShadowNearClipPlane, - shadowDepthRange); + minX, + maxX, + minY, + maxY, + minZ, + maxZ); plan.enabled = true; plan.lightDirection = lightDirection; plan.focusPoint = focusPoint; plan.orthographicHalfExtent = shadowHalfExtent; - plan.nearClipPlane = kShadowNearClipPlane; - plan.farClipPlane = shadowDepthRange; + plan.nearClipPlane = minZ; + plan.farClipPlane = maxZ; plan.mapWidth = kDirectionalShadowMapDimension; plan.mapHeight = kDirectionalShadowMapDimension; plan.cameraData.view = view.Transpose(); @@ -195,11 +347,16 @@ std::vector SceneRenderRequestPlanner::BuildRequests( *camera, renderedBaseCameraCount, requests.size())) { - if (Components::LightComponent* mainDirectionalLight = FindMainDirectionalLight(scene); + if (Components::LightComponent* mainDirectionalLight = + FindMainDirectionalLight(scene, camera->GetCullingMask()); mainDirectionalLight != nullptr && mainDirectionalLight->GetCastsShadows()) { + const float viewportAspect = surface.GetRenderAreaHeight() > 0 + ? static_cast(surface.GetRenderAreaWidth()) / + static_cast(surface.GetRenderAreaHeight()) + : 1.0f; request.directionalShadow = - BuildDirectionalShadowRenderPlan(*camera, *mainDirectionalLight); + BuildDirectionalShadowRenderPlan(scene, *camera, *mainDirectionalLight, viewportAspect); if (request.directionalShadow.IsValid()) { request.shadowCaster.clearFlags = RenderClearFlags::Depth; request.shadowCaster.hasCameraDataOverride = true; diff --git a/tests/Rendering/unit/test_render_scene_extractor.cpp b/tests/Rendering/unit/test_render_scene_extractor.cpp index bdc7af64..ea698731 100644 --- a/tests/Rendering/unit/test_render_scene_extractor.cpp +++ b/tests/Rendering/unit/test_render_scene_extractor.cpp @@ -182,6 +182,101 @@ TEST(RenderSceneExtractor_Test, ExtractsBrightestDirectionalLightAsMainLight) { EXPECT_EQ( sceneData.lighting.mainDirectionalLight.direction, mainLightObject->GetTransform()->GetForward().Normalized() * -1.0f); + + ASSERT_EQ(sceneData.lighting.additionalLightCount, 2u); + EXPECT_EQ(sceneData.lighting.additionalLights[0].type, RenderLightType::Directional); + EXPECT_FLOAT_EQ(sceneData.lighting.additionalLights[0].intensity, 0.5f); + EXPECT_EQ(sceneData.lighting.additionalLights[1].type, RenderLightType::Point); + EXPECT_FLOAT_EQ(sceneData.lighting.additionalLights[1].intensity, 10.0f); +} + +TEST(RenderSceneExtractor_Test, ExtractsAdditionalLightsWithoutMainDirectionalAndFiltersByLightCullingMask) { + Scene scene("AdditionalLightsScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetCullingMask(1u << 3); + + GameObject* hiddenSpotObject = scene.CreateGameObject("HiddenSpot"); + hiddenSpotObject->SetLayer(0); + auto* hiddenSpot = hiddenSpotObject->AddComponent(); + hiddenSpot->SetLightType(LightType::Spot); + hiddenSpot->SetIntensity(100.0f); + hiddenSpot->SetRange(50.0f); + hiddenSpot->SetSpotAngle(60.0f); + hiddenSpotObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, 1.0f)); + + GameObject* visiblePointObject = scene.CreateGameObject("VisiblePoint"); + visiblePointObject->SetLayer(3); + auto* visiblePoint = visiblePointObject->AddComponent(); + visiblePoint->SetLightType(LightType::Point); + visiblePoint->SetColor(Color(0.9f, 0.2f, 0.1f, 1.0f)); + visiblePoint->SetIntensity(4.0f); + visiblePoint->SetRange(12.0f); + visiblePointObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, 4.0f)); + + GameObject* visibleSpotObject = scene.CreateGameObject("VisibleSpot"); + visibleSpotObject->SetLayer(3); + auto* visibleSpot = visibleSpotObject->AddComponent(); + visibleSpot->SetLightType(LightType::Spot); + visibleSpot->SetColor(Color(0.1f, 0.8f, 0.4f, 1.0f)); + visibleSpot->SetIntensity(3.0f); + visibleSpot->SetRange(8.0f); + visibleSpot->SetSpotAngle(48.0f); + visibleSpotObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, 2.0f)); + visibleSpotObject->GetTransform()->SetLocalRotation( + Quaternion::LookRotation(Vector3(0.0f, 0.0f, -1.0f))); + + RenderSceneExtractor extractor; + const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600); + + EXPECT_FALSE(sceneData.lighting.HasMainDirectionalLight()); + ASSERT_TRUE(sceneData.lighting.HasAdditionalLights()); + ASSERT_EQ(sceneData.lighting.additionalLightCount, 2u); + + const RenderAdditionalLightData& spotLight = sceneData.lighting.additionalLights[0]; + EXPECT_EQ(spotLight.type, RenderLightType::Spot); + EXPECT_EQ(spotLight.color.g, 0.8f); + EXPECT_EQ(spotLight.position, Vector3(0.0f, 0.0f, 2.0f)); + EXPECT_EQ(spotLight.direction, Vector3(0.0f, 0.0f, 1.0f)); + EXPECT_FLOAT_EQ(spotLight.range, 8.0f); + EXPECT_FLOAT_EQ(spotLight.spotAngle, 48.0f); + + const RenderAdditionalLightData& pointLightData = sceneData.lighting.additionalLights[1]; + EXPECT_EQ(pointLightData.type, RenderLightType::Point); + EXPECT_EQ(pointLightData.color.r, 0.9f); + EXPECT_EQ(pointLightData.position, Vector3(0.0f, 0.0f, 4.0f)); + EXPECT_FLOAT_EQ(pointLightData.range, 12.0f); + EXPECT_FLOAT_EQ(pointLightData.spotAngle, 0.0f); +} + +TEST(RenderSceneExtractor_Test, LimitsAdditionalLightsToBoundedCountUsingStableTieBreakOrder) { + Scene scene("AdditionalLightCapScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + + for (uint32_t index = 0; index < RenderLightingData::kMaxAdditionalLightCount + 2u; ++index) { + GameObject* lightObject = scene.CreateGameObject("PointLight" + std::to_string(index)); + auto* light = lightObject->AddComponent(); + light->SetLightType(LightType::Point); + light->SetIntensity(1.0f); + light->SetRange(16.0f); + light->SetColor(Color(static_cast(index), 0.0f, 0.0f, 1.0f)); + lightObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, 4.0f)); + } + + RenderSceneExtractor extractor; + const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600); + + ASSERT_FALSE(sceneData.lighting.HasMainDirectionalLight()); + ASSERT_EQ(sceneData.lighting.additionalLightCount, RenderLightingData::kMaxAdditionalLightCount); + for (uint32_t index = 0; index < RenderLightingData::kMaxAdditionalLightCount; ++index) { + EXPECT_EQ(sceneData.lighting.additionalLights[index].type, RenderLightType::Point); + EXPECT_FLOAT_EQ(sceneData.lighting.additionalLights[index].color.r, static_cast(index)); + } } TEST(RenderSceneExtractor_Test, FiltersVisibleItemsByCameraCullingMask) {