From 0cd1f20d39493ab6bf37285f1a37a5de95b8835a Mon Sep 17 00:00:00 2001 From: Benjamin Jillich <43751992+amzn-jillich@users.noreply.github.com> Date: Wed, 6 Oct 2021 09:35:55 +0200 Subject: [PATCH] EMotion FX: Simple LOD Component to use Atom LOD override (#4464) The LOD level for skinned meshes is determined in the render phase, which is after the animation update and is also specific to the camera/target Atom renders to, which means that different render targets could end up using different LOD levels depending on how much screen space the character's bounding box takes. In order to make sure that all transformations are calculated by EMotion FX that the skinned mesh Atom wants to render are available, the Simple LOD Component takes ownership of the LOD level and uses the Atom LOD override. That way Atom adapts to the LOD level determined by the Simple LOD component and makes sure the skeletal matches with the geometry LOD levels. Signed-off-by: Benjamin Jillich --- Gems/EMotionFX/Code/CMakeLists.txt | 1 + .../Components/SimpleLODComponent.cpp | 42 +++++++++++++++++-- .../Components/SimpleLODComponent.h | 5 ++- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/Gems/EMotionFX/Code/CMakeLists.txt b/Gems/EMotionFX/Code/CMakeLists.txt index ed5447ba25..dace02b2ae 100644 --- a/Gems/EMotionFX/Code/CMakeLists.txt +++ b/Gems/EMotionFX/Code/CMakeLists.txt @@ -36,6 +36,7 @@ ly_add_target( PUBLIC AZ::AtomCore Gem::Atom_RPI.Public + Gem::AtomLyIntegration_CommonFeatures.Static Gem::LmbrCentral COMPILE_DEFINITIONS PUBLIC diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.cpp index 5db0fae842..aa17058adf 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.cpp @@ -142,12 +142,31 @@ namespace EMotionFX { ActorComponentNotificationBus::Handler::BusConnect(GetEntityId()); AZ::TickBus::Handler::BusConnect(); + + // Remember the lod type and level so that we can set it back to the previous one on deactivation of the component. + AZ::Render::MeshComponentRequestBus::EventResult(m_previousLodType, + GetEntityId(), + &AZ::Render::MeshComponentRequestBus::Events::GetLodType); + + if (m_actorInstance) + { + m_previousLodLevel = m_actorInstance->GetLODLevel(); + } } void SimpleLODComponent::Deactivate() { AZ::TickBus::Handler::BusDisconnect(); ActorComponentNotificationBus::Handler::BusDisconnect(); + + AZ::Render::MeshComponentRequestBus::Event(GetEntityId(), + &AZ::Render::MeshComponentRequestBus::Events::SetLodType, + m_previousLodType); + + if (m_actorInstance) + { + m_actorInstance->SetLODLevel(m_previousLodLevel); + } } void SimpleLODComponent::OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) @@ -183,7 +202,7 @@ namespace EMotionFX return max - 1; } - void SimpleLODComponent::UpdateLodLevelByDistance(EMotionFX::ActorInstance * actorInstance, const Configuration& configuration, AZ::EntityId entityId) + void SimpleLODComponent::UpdateLodLevelByDistance(EMotionFX::ActorInstance* actorInstance, const Configuration& configuration, AZ::EntityId entityId) { if (actorInstance) { @@ -201,15 +220,30 @@ namespace EMotionFX AZ::RPI::ViewportContextPtr defaultViewportContext = viewportContextManager->GetViewportContextByName(viewportContextManager->GetDefaultViewportContextName()); const float distance = worldPos.GetDistance(defaultViewportContext->GetCameraTransform().GetTranslation()); - const size_t lodByDistance = GetLodByDistance(configuration.m_lodDistances, distance); - actorInstance->SetLODLevel(lodByDistance); + const size_t requestedLod = GetLodByDistance(configuration.m_lodDistances, distance); + actorInstance->SetLODLevel(requestedLod); if (configuration.m_enableLodSampling) { - const float animGraphSampleRate = configuration.m_lodSampleRates[lodByDistance]; + const float animGraphSampleRate = configuration.m_lodSampleRates[requestedLod]; const float updateRateInSeconds = animGraphSampleRate > 0.0f ? 1.0f / animGraphSampleRate : 0.0f; actorInstance->SetMotionSamplingRate(updateRateInSeconds); } + + // Disable the automatic mesh LOD level adjustment based on screen space in case a simple LOD component is present. + // The simple LOD component overrides the mesh LOD level and syncs the skeleton with the mesh LOD level. + AZ::Render::MeshComponentRequestBus::Event(entityId, + &AZ::Render::MeshComponentRequestBus::Events::SetLodType, + AZ::RPI::Cullable::LodType::SpecificLod); + + // When setting the actor instance LOD level, a change is just requested and with the next update it will get applied. + // This means that the current LOD level might differ from the requested one. We need to sync the Atom LOD level with the + // current LOD level of the actor instance to avoid skinning artifacts. The requested LOD level will be present and applied + // the following frame. + const size_t currentLod = actorInstance->GetLODLevel(); + AZ::Render::MeshComponentRequestBus::Event(entityId, + &AZ::Render::MeshComponentRequestBus::Events::SetLodOverride, + static_cast(currentLod)); } } } // namespace integration diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.h b/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.h index 96bebdc660..a00918e7c0 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.h +++ b/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.h @@ -17,7 +17,7 @@ #include #include - +#include namespace EMotionFX { @@ -93,6 +93,9 @@ namespace EMotionFX Configuration m_configuration; // Component configuration. EMotionFX::ActorInstance* m_actorInstance; // Associated actor instance (retrieved from Actor Component). + + AZ::RPI::Cullable::LodType m_previousLodType = AZ::RPI::Cullable::LodType::Default; + size_t m_previousLodLevel = 0; }; } // namespace Integration