From 99ee0b1bfe08472e360260d41488c64d1b07f8f8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Jul 2023 09:49:23 +0200 Subject: [PATCH] Add `MeshDeformation` utility for generic meshes vertices morphing (eg. via Blend Shapes or Cloth) --- Flax.sln.DotSettings | 2 + .../Utilities/ViewportIconsRenderer.cpp | 2 + Source/Engine/Content/Assets/SkinnedModel.cpp | 2 + Source/Engine/Foliage/Foliage.cpp | 1 + Source/Engine/Graphics/DynamicBuffer.cpp | 6 +- Source/Engine/Graphics/Models/BlendShape.cpp | 180 ------------ Source/Engine/Graphics/Models/BlendShape.h | 63 ----- Source/Engine/Graphics/Models/Mesh.cpp | 11 + Source/Engine/Graphics/Models/MeshBase.h | 14 +- .../Graphics/Models/MeshDeformation.cpp | 166 +++++++++++ .../Engine/Graphics/Models/MeshDeformation.h | 58 ++++ Source/Engine/Graphics/Models/ModelLOD.cpp | 20 +- Source/Engine/Graphics/Models/ModelLOD.h | 3 +- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 37 +-- .../Graphics/Models/SkinnedModelLOD.cpp | 28 +- .../Engine/Graphics/Models/SkinnedModelLOD.h | 23 +- Source/Engine/Graphics/Models/Types.h | 4 + Source/Engine/Level/Actors/AnimatedModel.cpp | 257 +++++++++++++++--- Source/Engine/Level/Actors/AnimatedModel.h | 22 +- Source/Engine/Level/Actors/Camera.cpp | 1 + .../Level/Actors/ModelInstanceActor.cpp | 8 + .../Engine/Level/Actors/ModelInstanceActor.h | 19 +- Source/Engine/Level/Actors/SplineModel.cpp | 13 +- Source/Engine/Level/Actors/SplineModel.h | 2 +- Source/Engine/Level/Actors/StaticModel.cpp | 21 +- Source/Engine/Level/Actors/StaticModel.h | 5 +- 26 files changed, 600 insertions(+), 368 deletions(-) delete mode 100644 Source/Engine/Graphics/Models/BlendShape.cpp create mode 100644 Source/Engine/Graphics/Models/MeshDeformation.cpp create mode 100644 Source/Engine/Graphics/Models/MeshDeformation.h diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index fe1d1463e..9d82c0e65 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -255,6 +255,8 @@ True True True + True + True True True True diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index a5ed0356f..d16a31c63 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -127,6 +127,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene Matrix m1, m2, world; GeometryDrawStateData drawState; draw.DrawState = &drawState; + draw.Deformation = nullptr; draw.World = &world; AssetReference texture; for (Actor* icon : icons) @@ -205,6 +206,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor // Draw icon GeometryDrawStateData drawState; draw.DrawState = &drawState; + draw.Deformation = nullptr; // Support custom icons through types, but not onces that were added through actors, // since they cant register while in prefab view anyway diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index a2e687131..5871087d9 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -852,6 +852,7 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) { auto& lod = LODs[lodIndex]; lod._model = this; + lod._lodIndex = lodIndex; lod.ScreenSize = 1.0f; const int32 meshesCount = meshesCountPerLod[lodIndex]; if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES) @@ -1112,6 +1113,7 @@ Asset::LoadResult SkinnedModel::load() { auto& lod = LODs[lodIndex]; lod._model = this; + lod._lodIndex = lodIndex; // Screen Size stream->ReadFloat(&lod.ScreenSize); diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 23a31ad89..82765f151 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -1180,6 +1180,7 @@ void Foliage::Draw(RenderContext& renderContext) draw.Buffer = &type.Entries; draw.World = &world; draw.DrawState = &instance.DrawState; + draw.Deformation = nullptr; draw.Bounds = instance.Bounds; draw.PerInstanceRandom = instance.Random; draw.DrawModes = type.DrawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode); diff --git a/Source/Engine/Graphics/DynamicBuffer.cpp b/Source/Engine/Graphics/DynamicBuffer.cpp index 66b259027..23251418a 100644 --- a/Source/Engine/Graphics/DynamicBuffer.cpp +++ b/Source/Engine/Graphics/DynamicBuffer.cpp @@ -4,10 +4,10 @@ #include "GPUContext.h" #include "PixelFormatExtensions.h" #include "GPUDevice.h" +#include "RenderTask.h" #include "Engine/Core/Log.h" #include "Engine/Core/Utilities.h" #include "Engine/Core/Math/Math.h" -#include "Engine/Threading/Threading.h" DynamicBuffer::DynamicBuffer(uint32 initialCapacity, uint32 stride, const String& name) : _buffer(nullptr) @@ -46,9 +46,11 @@ void DynamicBuffer::Flush() } // Upload data to the buffer - if (IsInMainThread() && GPUDevice::Instance->IsRendering()) + if (GPUDevice::Instance->IsRendering()) { + RenderContext::GPULocker.Lock(); GPUDevice::Instance->GetMainContext()->UpdateBuffer(_buffer, Data.Get(), size); + RenderContext::GPULocker.Unlock(); } else { diff --git a/Source/Engine/Graphics/Models/BlendShape.cpp b/Source/Engine/Graphics/Models/BlendShape.cpp deleted file mode 100644 index 15b63d1f4..000000000 --- a/Source/Engine/Graphics/Models/BlendShape.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. - -#include "BlendShape.h" -#include "Engine/Content/Assets/SkinnedModel.h" -#include "Engine/Graphics/GPUDevice.h" -#include "Engine/Profiler/ProfilerCPU.h" - -BlendShapesInstance::MeshInstance::MeshInstance() - : IsUsed(false) - , IsDirty(false) - , DirtyMinVertexIndex(0) - , DirtyMaxVertexIndex(MAX_uint32 - 1) - , VertexBuffer(0, sizeof(VB0SkinnedElementType), TEXT("Skinned Mesh Blend Shape")) -{ -} - -BlendShapesInstance::MeshInstance::~MeshInstance() -{ -} - -BlendShapesInstance::~BlendShapesInstance() -{ - Meshes.ClearDelete(); -} - -void BlendShapesInstance::Update(SkinnedModel* skinnedModel) -{ - if (!WeightsDirty) - return; - WeightsDirty = false; - ASSERT(skinnedModel); - if (skinnedModel->IsVirtual()) - { - // Blend shapes are not supported for virtual skinned models - return; - } - PROFILE_CPU_NAMED("Update Blend Shapes"); - - // Collect used meshes - for (auto& e : Meshes) - { - MeshInstance* instance = e.Value; - instance->IsUsed = false; - instance->IsDirty = false; - } - for (auto& e : Weights) - { - for (auto& lod : skinnedModel->LODs) - { - for (auto& mesh : lod.Meshes) - { - for (auto& blendShape : mesh.BlendShapes) - { - if (blendShape.Name != e.First) - continue; - - // Setup mesh instance and mark it as used for blending - MeshInstance* instance; - if (!Meshes.TryGet(&mesh, instance)) - { - instance = New(); - Meshes.Add(&mesh, instance); - } - instance->IsUsed = true; - instance->IsDirty = true; - - // One blend shape of that name per mesh - break; - } - } - } - } - - // Update all used meshes - for (auto& e : Meshes) - { - MeshInstance& instance = *e.Value; - if (!instance.IsUsed) - continue; - const SkinnedMesh* mesh = e.Key; - - // Get skinned mesh vertex buffer data (original, cached on CPU) - BytesContainer vertexBuffer; - int32 vertexCount; - if (mesh->DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffer, vertexCount)) - { - // Don't use this mesh if failed to get it's vertices data - instance.IsUsed = false; - continue; - } - - // Estimate the range of the vertices to modify by the currently active blend shapes - uint32 minVertexIndex = MAX_uint32, maxVertexIndex = 0; - bool useNormals = false; - Array, InlinedAllocation<32>> blendShapes; - for (const BlendShape& blendShape : mesh->BlendShapes) - { - for (auto& q : Weights) - { - if (q.First == blendShape.Name) - { - const float weight = q.Second * blendShape.Weight; - blendShapes.Add(Pair(blendShape, weight)); - minVertexIndex = Math::Min(minVertexIndex, blendShape.MinVertexIndex); - maxVertexIndex = Math::Max(maxVertexIndex, blendShape.MaxVertexIndex); - useNormals |= blendShape.UseNormals; - break; - } - } - } - - // Initialize the dynamic vertex buffer data (use the dirty range from the previous update to be cleared with initial data) - instance.VertexBuffer.Data.Resize(vertexBuffer.Length()); - const uint32 dirtyVertexDataStart = instance.DirtyMinVertexIndex * sizeof(VB0SkinnedElementType); - const uint32 dirtyVertexDataLength = Math::Min(instance.DirtyMaxVertexIndex - instance.DirtyMinVertexIndex + 1, vertexCount) * sizeof(VB0SkinnedElementType); - Platform::MemoryCopy(instance.VertexBuffer.Data.Get() + dirtyVertexDataStart, vertexBuffer.Get() + dirtyVertexDataStart, dirtyVertexDataLength); - - // Blend all blend shapes - auto data = (VB0SkinnedElementType*)instance.VertexBuffer.Data.Get(); - for (const auto& q : blendShapes) - { - // TODO: use SIMD - if (useNormals) - { - for (int32 i = 0; i < q.First.Vertices.Count(); i++) - { - const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; - ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < (uint32)vertexCount); - VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); - vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; - Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta; - vertex.Normal = normal * 0.5f + 0.5f; - } - } - else - { - for (int32 i = 0; i < q.First.Vertices.Count(); i++) - { - const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; - ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < (uint32)vertexCount); - VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); - vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; - } - } - } - - if (useNormals) - { - // Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range) - // TODO: use SIMD - for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++) - { - VB0SkinnedElementType& vertex = *(data + vertexIndex); - - Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f; - normal.Normalize(); - vertex.Normal = normal * 0.5f + 0.5f; - - Float3 tangent = vertex.Tangent.ToFloat3() * 2.0f - 1.0f; - tangent = tangent - ((tangent | normal) * normal); - tangent.Normalize(); - const auto tangentSign = vertex.Tangent.W; - vertex.Tangent = tangent * 0.5f + 0.5f; - vertex.Tangent.W = tangentSign; - } - } - - // Mark as dirty to be flushed before next rendering - instance.IsDirty = true; - instance.DirtyMinVertexIndex = minVertexIndex; - instance.DirtyMaxVertexIndex = maxVertexIndex; - } -} - -void BlendShapesInstance::Clear() -{ - Meshes.ClearDelete(); - Weights.Clear(); - WeightsDirty = false; -} diff --git a/Source/Engine/Graphics/Models/BlendShape.h b/Source/Engine/Graphics/Models/BlendShape.h index 10dc3b0a7..e2bfd82bc 100644 --- a/Source/Engine/Graphics/Models/BlendShape.h +++ b/Source/Engine/Graphics/Models/BlendShape.h @@ -3,15 +3,8 @@ #pragma once #include "Engine/Core/Types/String.h" -#include "Engine/Core/Types/Pair.h" #include "Engine/Core/Collections/Array.h" -#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Math/Vector3.h" -#include "Engine/Graphics/DynamicBuffer.h" - -class SkinnedMesh; -class SkinnedModel; -class GPUBuffer; /// /// The blend shape vertex data optimized for runtime meshes morphing. @@ -76,59 +69,3 @@ public: /// Array Vertices; }; - -/// -/// The blend shapes runtime instance data. Handles blend shapes updating, blending and preparing for skinned mesh rendering. -/// -class BlendShapesInstance -{ -public: - /// - /// The runtime data for blend shapes used for on a mesh. - /// - class MeshInstance - { - public: - bool IsUsed; - bool IsDirty; - uint32 DirtyMinVertexIndex; - uint32 DirtyMaxVertexIndex; - DynamicVertexBuffer VertexBuffer; - - MeshInstance(); - ~MeshInstance(); - }; - -public: - /// - /// The blend shapes weights (pair of blend shape name and the weight). - /// - Array> Weights; - - /// - /// Flag that marks if blend shapes weights has been modified. - /// - bool WeightsDirty = false; - - /// - /// The blend shapes meshes data. - /// - Dictionary Meshes; - -public: - /// - /// Finalizes an instance of the class. - /// - ~BlendShapesInstance(); - - /// - /// Updates the instanced meshes. Performs the blend shapes blending (only if weights were modified). - /// - /// The skinned model used for blend shapes. - void Update(SkinnedModel* skinnedModel); - - /// - /// Clears the runtime data. - /// - void Clear(); -}; diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index e5ba5488d..a2d962b64 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Mesh.h" +#include "MeshDeformation.h" #include "ModelInstanceEntry.h" #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Assets/Model.h" @@ -480,6 +481,11 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; + if (info.Deformation) + { + info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); + info.Deformation->RunDeformers(this, MeshBufferType::Vertex1, drawCall.Geometry.VertexBuffers[1]); + } if (info.VertexColors && info.VertexColors[_lodIndex]) { // TODO: cache vertexOffset within the model LOD per-mesh @@ -540,6 +546,11 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; + if (info.Deformation) + { + info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); + info.Deformation->RunDeformers(this, MeshBufferType::Vertex1, drawCall.Geometry.VertexBuffers[1]); + } if (info.VertexColors && info.VertexColors[_lodIndex]) { // TODO: cache vertexOffset within the model LOD per-mesh diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index 0d07ec4ec..441267436 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -159,7 +159,7 @@ public: struct DrawInfo { /// - /// The instance buffer to use during model rendering + /// The instance buffer to use during model rendering. /// ModelInstanceEntries* Buffer; @@ -169,10 +169,15 @@ public: Matrix* World; /// - /// The instance drawing state data container. Used for LOD transition handling and previous world transformation matrix updating. + /// The instance drawing state data container. Used for LOD transition handling and previous world transformation matrix updating. /// GeometryDrawStateData* DrawState; + /// + /// The instance deformation utility. + /// + MeshDeformation* Deformation; + union { struct @@ -181,11 +186,6 @@ public: /// The skinning. /// SkinnedMeshDrawData* Skinning; - - /// - /// The blend shapes. - /// - BlendShapesInstance* BlendShapes; }; struct diff --git a/Source/Engine/Graphics/Models/MeshDeformation.cpp b/Source/Engine/Graphics/Models/MeshDeformation.cpp new file mode 100644 index 000000000..5a654459b --- /dev/null +++ b/Source/Engine/Graphics/Models/MeshDeformation.cpp @@ -0,0 +1,166 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "MeshDeformation.h" +#include "Engine/Graphics/Models/MeshBase.h" +#include "Engine/Profiler/ProfilerCPU.h" + +struct Key +{ + union + { + struct + { + uint8 Lod; + uint8 BufferType; + uint16 MeshIndex; + }; + + uint32 Value; + }; +}; + +FORCE_INLINE static uint32 GetKey(int32 lodIndex, int32 meshIndex, MeshBufferType type) +{ + Key key; + key.Value = 0; + key.Lod = (uint8)lodIndex; + key.BufferType = (uint8)type; + key.MeshIndex = (uint16)meshIndex; + return key.Value; +} + +void MeshDeformation::GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const +{ + const auto key = GetKey(lodIndex, meshIndex, MeshBufferType::Vertex0); + for (MeshDeformationData* deformation : _deformations) + { + if (deformation->Key == key) + { + bounds = deformation->Bounds; + break; + } + } +} + +void MeshDeformation::Clear() +{ + for (MeshDeformationData* e : _deformations) + Delete(e); + _deformations.Clear(); +} + +void MeshDeformation::Dirty() +{ + for (MeshDeformationData* deformation : _deformations) + deformation->Dirty = true; +} + +void MeshDeformation::Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type) +{ + const auto key = GetKey(lodIndex, meshIndex, type); + for (MeshDeformationData* deformation : _deformations) + { + if (deformation->Key == key) + { + deformation->Dirty = true; + break; + } + } +} + +void MeshDeformation::Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type, const BoundingBox& bounds) +{ + const auto key = GetKey(lodIndex, meshIndex, type); + for (MeshDeformationData* deformation : _deformations) + { + if (deformation->Key == key) + { + deformation->Dirty = true; + deformation->Bounds = bounds; + break; + } + } +} + +void MeshDeformation::AddDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function& deformer) +{ + const auto key = GetKey(lodIndex, meshIndex, type); + _deformers[key].Bind(deformer); + Dirty(lodIndex, meshIndex, type); +} + +void MeshDeformation::RemoveDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function& deformer) +{ + const auto key = GetKey(lodIndex, meshIndex, type); + _deformers[key].Unbind(deformer); + Dirty(lodIndex, meshIndex, type); +} + +void MeshDeformation::RunDeformers(const MeshBase* mesh, MeshBufferType type, GPUBuffer*& vertexBuffer) +{ + const auto key = GetKey(mesh->GetLODIndex(), mesh->GetIndex(), type); + if (const auto* e = _deformers.TryGet(key)) + { + PROFILE_CPU(); + const int32 vertexStride = vertexBuffer->GetStride(); + + // Get mesh deformation container + MeshDeformationData* deformation = nullptr; + for (int32 i = 0; i < _deformations.Count(); i++) + { + if (_deformations[i]->Key == key) + { + deformation = _deformations[i]; + break; + } + } + if (!e->IsBinded()) + { + // Auto-recycle unused deformations + if (deformation) + { + _deformations.Remove(deformation); + Delete(deformation); + } + _deformers.Remove(key); + return; + } + if (!deformation) + { + deformation = New(key, vertexStride); + deformation->VertexBuffer.Data.Resize(vertexBuffer->GetSize()); + deformation->Bounds = mesh->GetBox(); + _deformations.Add(deformation); + } + + if (deformation->Dirty) + { + // Get original mesh vertex buffer data (cached on CPU) + BytesContainer vertexData; + int32 vertexCount; + if (mesh->DownloadDataCPU(type, vertexData, vertexCount)) + return; + ASSERT(vertexData.Length() / vertexCount == vertexStride); + + // Init dirty range with valid data (use the dirty range from the previous update to be cleared with initial data) + deformation->VertexBuffer.Data.Resize(vertexData.Length()); + const uint32 dirtyDataStart = Math::Min(deformation->DirtyMinIndex, vertexCount - 1) * vertexStride; + const uint32 dirtyDataLength = Math::Min(deformation->DirtyMaxIndex - deformation->DirtyMinIndex + 1, vertexCount) * vertexStride; + Platform::MemoryCopy(deformation->VertexBuffer.Data.Get() + dirtyDataStart, vertexData.Get() + dirtyDataStart, dirtyDataLength); + + // Reset dirty state + deformation->DirtyMinIndex = MAX_uint32 - 1; + deformation->DirtyMaxIndex = 0; + deformation->Dirty = false; + + // Run deformers + (*e)(mesh, *deformation); + + // Upload modified vertex data to the GPU + deformation->VertexBuffer.Flush(); + } + + // Override vertex buffer for draw call + vertexBuffer = deformation->VertexBuffer.GetBuffer(); + } +} diff --git a/Source/Engine/Graphics/Models/MeshDeformation.h b/Source/Engine/Graphics/Models/MeshDeformation.h new file mode 100644 index 000000000..c33025926 --- /dev/null +++ b/Source/Engine/Graphics/Models/MeshDeformation.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Graphics/Models/Types.h" +#include "Engine/Graphics/DynamicBuffer.h" + +/// +/// The mesh deformation data container. +/// +struct MeshDeformationData +{ + uint64 Key; + uint32 DirtyMinIndex = 0; + uint32 DirtyMaxIndex = MAX_uint32 - 1; + bool Dirty = true; + BoundingBox Bounds; + DynamicVertexBuffer VertexBuffer; + + MeshDeformationData(uint64 key, uint32 stride) + : Key(key) + , VertexBuffer(0, stride, TEXT("MeshDeformation")) + { + } + + NON_COPYABLE(MeshDeformationData); + + ~MeshDeformationData() + { + } +}; + +/// +/// The mesh deformation utility for editing or morphing models dynamically at runtime (eg. via Blend Shapes or Cloth). +/// +class FLAXENGINE_API MeshDeformation +{ +private: + Dictionary> _deformers; + Array _deformations; + +public: + ~MeshDeformation() + { + Clear(); + } + + void GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const; + void Clear(); + void Dirty(); + void Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type); + void Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type, const BoundingBox& bounds); + void AddDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function& deformer); + void RemoveDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function& deformer); + void RunDeformers(const MeshBase* mesh, MeshBufferType type, GPUBuffer*& vertexBuffer); +}; diff --git a/Source/Engine/Graphics/Models/ModelLOD.cpp b/Source/Engine/Graphics/Models/ModelLOD.cpp index 31216dea8..ccd99902e 100644 --- a/Source/Engine/Graphics/Models/ModelLOD.cpp +++ b/Source/Engine/Graphics/Models/ModelLOD.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "ModelLOD.h" +#include "MeshDeformation.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Transform.h" #include "Engine/Graphics/GPUDevice.h" @@ -108,9 +109,9 @@ BoundingBox ModelLOD::GetBox(const Matrix& world) const { Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) { - const auto& mesh = Meshes[j]; + const auto& mesh = Meshes[meshIndex]; mesh.GetBox().GetCorners(corners); for (int32 i = 0; i < 8; i++) { @@ -122,14 +123,17 @@ BoundingBox ModelLOD::GetBox(const Matrix& world) const return BoundingBox(min, max); } -BoundingBox ModelLOD::GetBox(const Transform& transform) const +BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const { Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) { - const auto& mesh = Meshes[j]; - mesh.GetBox().GetCorners(corners); + const auto& mesh = Meshes[meshIndex]; + BoundingBox box = mesh.GetBox(); + if (deformation) + deformation->GetBounds(_lodIndex, meshIndex, box); + box.GetCorners(corners); for (int32 i = 0; i < 8; i++) { transform.LocalToWorld(corners[i], tmp); @@ -144,9 +148,9 @@ BoundingBox ModelLOD::GetBox() const { Vector3 min = Vector3::Maximum, max = Vector3::Minimum; Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) { - Meshes[j].GetBox().GetCorners(corners); + Meshes[meshIndex].GetBox().GetCorners(corners); for (int32 i = 0; i < 8; i++) { min = Vector3::Min(min, corners[i]); diff --git a/Source/Engine/Graphics/Models/ModelLOD.h b/Source/Engine/Graphics/Models/ModelLOD.h index c6cd33976..3a99cbb44 100644 --- a/Source/Engine/Graphics/Models/ModelLOD.h +++ b/Source/Engine/Graphics/Models/ModelLOD.h @@ -109,8 +109,9 @@ public: /// Get model bounding box in transformed world. /// /// The instance transformation. + /// The meshes deformation container (optional). /// Bounding box - BoundingBox GetBox(const Transform& transform) const; + BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; /// /// Gets the bounding box combined for all meshes in this model LOD. diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index b6c966508..8bc938684 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "SkinnedMesh.h" +#include "MeshDeformation.h" #include "ModelInstanceEntry.h" #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Assets/SkinnedModel.h" @@ -172,26 +173,14 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; - BlendShapesInstance::MeshInstance* blendShapeMeshInstance; - if (info.BlendShapes && info.BlendShapes->Meshes.TryGet(this, blendShapeMeshInstance) && blendShapeMeshInstance->IsUsed) - { - // Use modified vertex buffer from the blend shapes - if (blendShapeMeshInstance->IsDirty) - { - blendShapeMeshInstance->VertexBuffer.Flush(); - blendShapeMeshInstance->IsDirty = false; - } - drawCall.Geometry.VertexBuffers[0] = blendShapeMeshInstance->VertexBuffer.GetBuffer(); - } - else - { - drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; - } + drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; drawCall.Geometry.VertexBuffers[1] = nullptr; drawCall.Geometry.VertexBuffers[2] = nullptr; drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; + if (info.Deformation) + info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); drawCall.Draw.StartIndex = 0; drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; @@ -232,26 +221,14 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; - BlendShapesInstance::MeshInstance* blendShapeMeshInstance; - if (info.BlendShapes && info.BlendShapes->Meshes.TryGet(this, blendShapeMeshInstance) && blendShapeMeshInstance->IsUsed) - { - // Use modified vertex buffer from the blend shapes - if (blendShapeMeshInstance->IsDirty) - { - blendShapeMeshInstance->VertexBuffer.Flush(); - blendShapeMeshInstance->IsDirty = false; - } - drawCall.Geometry.VertexBuffers[0] = blendShapeMeshInstance->VertexBuffer.GetBuffer(); - } - else - { - drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; - } + drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; drawCall.Geometry.VertexBuffers[1] = nullptr; drawCall.Geometry.VertexBuffers[2] = nullptr; drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; + if (info.Deformation) + info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); drawCall.Draw.StartIndex = 0; drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp index d0c87608c..0e2b60aea 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp @@ -1,17 +1,12 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "SkinnedModelLOD.h" +#include "MeshDeformation.h" #include "Engine/Core/Log.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Serialization/MemoryReadStream.h" -bool SkinnedModelLOD::HasAnyMeshInitialized() const -{ - // Note: we initialize all meshes at once so the last one can be used to check it. - return Meshes.HasItems() && Meshes.Last().IsInitialized(); -} - bool SkinnedModelLOD::Load(MemoryReadStream& stream) { // Load LOD for each mesh @@ -147,6 +142,27 @@ BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const return BoundingBox(min, max); } +BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const +{ + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) + { + const auto& mesh = Meshes[meshIndex]; + BoundingBox box = mesh.GetBox(); + if (deformation) + deformation->GetBounds(_lodIndex, meshIndex, box); + box.GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + transform.LocalToWorld(corners[i], tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + return BoundingBox(min, max); +} + BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const { // Find minimum and maximum points of the mesh diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.h b/Source/Engine/Graphics/Models/SkinnedModelLOD.h index 41bb754c0..c5a312f4a 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.h +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.h @@ -16,6 +16,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ScriptingObject friend SkinnedModel; private: SkinnedModel* _model = nullptr; + int32 _lodIndex = 0; public: /// @@ -28,10 +29,22 @@ public: /// API_FIELD(ReadOnly) Array Meshes; + /// + /// Gets the model LOD index. + /// + API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const + { + return _lodIndex; + } + /// /// Determines whether any mesh has been initialized. /// - bool HasAnyMeshInitialized() const; + bool HasAnyMeshInitialized() const + { + // Note: we initialize all meshes at once so the last one can be used to check it. + return Meshes.HasItems() && Meshes.Last().IsInitialized(); + } public: /// @@ -81,6 +94,14 @@ public: /// Bounding box BoundingBox GetBox(const Matrix& world) const; + /// + /// Get model bounding box in transformed world. + /// + /// The instance transformation. + /// The meshes deformation container (optional). + /// Bounding box + BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; + /// /// Get model bounding box in transformed world for given instance buffer for only one mesh /// diff --git a/Source/Engine/Graphics/Models/Types.h b/Source/Engine/Graphics/Models/Types.h index b03d4948e..446d76850 100644 --- a/Source/Engine/Graphics/Models/Types.h +++ b/Source/Engine/Graphics/Models/Types.h @@ -11,12 +11,16 @@ class Model; class SkinnedModel; +class ModelBase; class Mesh; class SkinnedMesh; +class MeshBase; class ModelData; class ModelInstanceEntries; class Model; class SkinnedModel; +class MeshDeformation; +class GPUContext; struct RenderView; /// diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 5f2d65917..728758736 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -12,6 +12,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Serialization/Serialization.h" @@ -34,6 +35,12 @@ AnimatedModel::AnimatedModel(const SpawnParams& params) AnimationGraph.Loaded.Bind(this); } +AnimatedModel::~AnimatedModel() +{ + if (_deformation) + Delete(_deformation); +} + void AnimatedModel::ResetAnimation() { GraphInstance.ClearState(); @@ -56,11 +63,6 @@ void AnimatedModel::UpdateAnimation() // Request an animation update Animations::AddToUpdate(this); } - else - { - // Allow to use blend shapes without animation graph assigned - _blendShapes.Update(SkinnedModel.Get()); - } } void AnimatedModel::SetupSkinningData() @@ -292,7 +294,7 @@ void AnimatedModel::SetParameterValue(const Guid& id, const Variant& value) float AnimatedModel::GetBlendShapeWeight(const StringView& name) { - for (auto& e : _blendShapes.Weights) + for (auto& e : _blendShapeWeights) { if (e.First == name) return e.Second; @@ -302,36 +304,130 @@ float AnimatedModel::GetBlendShapeWeight(const StringView& name) void AnimatedModel::SetBlendShapeWeight(const StringView& name, float value) { + const auto* model = SkinnedModel.Get(); + CHECK(model); + model->WaitForLoaded(); value = Math::Clamp(value, -1.0f, 1.0f); - for (int32 i = 0; i < _blendShapes.Weights.Count(); i++) + const bool isZero = Math::IsZero(value); + if (!_deformation && !isZero) + _deformation = New(); + Function deformer; + deformer.Bind(this); + for (int32 i = 0; i < _blendShapeWeights.Count(); i++) { - auto& e = _blendShapes.Weights[i]; + auto& e = _blendShapeWeights[i]; if (e.First == name) { - if (Math::IsZero(value)) + if (isZero) { - _blendShapes.WeightsDirty = true; - _blendShapes.Weights.RemoveAt(i); + _blendShapeWeights.RemoveAt(i); + + // Remove deformers for meshes using this blend shape + for (const auto& lod : model->LODs) + { + for (const auto& mesh : lod.Meshes) + { + for (const auto& blendShape : mesh.BlendShapes) + { + if (blendShape.Name == name) + { + for (int32 j = 0; j < _blendShapeMeshes.Count(); j++) + { + auto& blendShapeMesh = _blendShapeMeshes[j]; + if (blendShapeMesh.LODIndex == mesh.GetLODIndex() && blendShapeMesh.MeshIndex == mesh.GetIndex()) + { + blendShapeMesh.Usages--; + if (blendShapeMesh.Usages == 0) + { + _deformation->RemoveDeformer(blendShapeMesh.LODIndex, blendShapeMesh.MeshIndex, MeshBufferType::Vertex0, deformer); + _blendShapeMeshes.RemoveAt(j); + } + break; + } + } + break; + } + } + } + } } else if (Math::NotNearEqual(e.Second, value)) { - _blendShapes.WeightsDirty = true; + // Update blend shape weight e.Second = value; + + // Dirty deformers for meshes using this blend shape + for (const auto& lod : model->LODs) + { + for (const auto& mesh : lod.Meshes) + { + for (const auto& blendShape : mesh.BlendShapes) + { + if (blendShape.Name == name) + { + _deformation->Dirty(mesh.GetLODIndex(), mesh.GetIndex(), MeshBufferType::Vertex0); + break; + } + } + } + } } return; } } + if (!isZero) { - auto& e = _blendShapes.Weights.AddOne(); + // Add blend shape weight + auto& e = _blendShapeWeights.AddOne(); e.First = name; e.Second = value; - _blendShapes.WeightsDirty = true; + + // Add deformers for meshes using this blend shape + for (const auto& lod : model->LODs) + { + for (const auto& mesh : lod.Meshes) + { + for (const auto& blendShape : mesh.BlendShapes) + { + if (blendShape.Name == name) + { + int32 i = 0; + for (; i < _blendShapeMeshes.Count(); i++) + { + auto& blendShapeMesh = _blendShapeMeshes[i]; + if (blendShapeMesh.LODIndex == mesh.GetLODIndex() && blendShapeMesh.MeshIndex == mesh.GetIndex()) + { + blendShapeMesh.Usages++; + break; + } + } + if (i == _blendShapeMeshes.Count()) + { + auto& blendShapeMesh = _blendShapeMeshes.AddOne(); + blendShapeMesh.LODIndex = mesh.GetLODIndex(); + blendShapeMesh.MeshIndex = mesh.GetIndex(); + blendShapeMesh.Usages = 1; + _deformation->AddDeformer(blendShapeMesh.LODIndex, blendShapeMesh.MeshIndex, MeshBufferType::Vertex0, deformer); + } + break; + } + } + } + } } } void AnimatedModel::ClearBlendShapeWeights() { - _blendShapes.Clear(); + if (_deformation) + { + Function deformer; + deformer.Bind(this); + for (auto e : _blendShapeMeshes) + _deformation->RemoveDeformer(e.LODIndex, e.MeshIndex, MeshBufferType::Vertex0, deformer); + } + _blendShapeWeights.Clear(); + _blendShapeMeshes.Clear(); } void AnimatedModel::PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed, float blendInTime, float blendOutTime, int32 loopCount) @@ -469,6 +565,87 @@ void AnimatedModel::SyncParameters() } } +void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationData& deformation) +{ + PROFILE_CPU_NAMED("BlendShapes"); + auto* skinnedMesh = (const SkinnedMesh*)mesh; + + // Estimate the range of the vertices to modify by the currently active blend shapes + uint32 minVertexIndex = MAX_uint32, maxVertexIndex = 0; + bool useNormals = false; + Array, InlinedAllocation<32>> blendShapes; + for (const BlendShape& blendShape : skinnedMesh->BlendShapes) + { + for (auto& q : _blendShapeWeights) + { + if (q.First == blendShape.Name) + { + const float weight = q.Second * blendShape.Weight; + blendShapes.Add(Pair(blendShape, weight)); + minVertexIndex = Math::Min(minVertexIndex, blendShape.MinVertexIndex); + maxVertexIndex = Math::Max(maxVertexIndex, blendShape.MaxVertexIndex); + useNormals |= blendShape.UseNormals; + break; + } + } + } + + // Blend all blend shapes + auto vertexCount = (uint32)mesh->GetVertexCount(); + auto data = (VB0SkinnedElementType*)deformation.VertexBuffer.Data.Get(); + for (const auto& q : blendShapes) + { + // TODO: use SIMD + if (useNormals) + { + for (int32 i = 0; i < q.First.Vertices.Count(); i++) + { + const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; + ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount); + VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); + vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; + Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta; + vertex.Normal = normal * 0.5f + 0.5f; + } + } + else + { + for (int32 i = 0; i < q.First.Vertices.Count(); i++) + { + const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; + ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount); + VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); + vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; + } + } + } + + if (useNormals) + { + // Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range) + // TODO: use SIMD + for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++) + { + VB0SkinnedElementType& vertex = *(data + vertexIndex); + + Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f; + normal.Normalize(); + vertex.Normal = normal * 0.5f + 0.5f; + + Float3 tangent = vertex.Tangent.ToFloat3() * 2.0f - 1.0f; + tangent = tangent - ((tangent | normal) * normal); + tangent.Normalize(); + const auto tangentSign = vertex.Tangent.W; + vertex.Tangent = tangent * 0.5f + 0.5f; + vertex.Tangent.W = tangentSign; + } + } + + // Mark as dirty to be cleared before next rendering + deformation.DirtyMinIndex = Math::Min(minVertexIndex, deformation.DirtyMinIndex); + deformation.DirtyMaxIndex = Math::Max(maxVertexIndex, deformation.DirtyMaxIndex); +} + void AnimatedModel::BeginPlay(SceneBeginData* data) { if (SkinnedModel && SkinnedModel->IsLoaded()) @@ -513,31 +690,26 @@ void AnimatedModel::OnActiveInTreeChanged() void AnimatedModel::UpdateBounds() { + auto model = SkinnedModel.Get(); if (CustomBounds.GetSize().LengthSquared() > 0.01f) { BoundingBox::Transform(CustomBounds, _transform, _box); } - else if (SkinnedModel && SkinnedModel->IsLoaded()) + else if (model && model->IsLoaded()) { + BoundingBox box = model->LODs[0].GetBox(_transform, _deformation); if (GraphInstance.NodesPose.Count() != 0) { // Per-bone bounds estimated from positions - auto& skeleton = SkinnedModel->Skeleton; + auto& skeleton = model->Skeleton; const int32 bonesCount = skeleton.Bones.Count(); -#define GET_NODE_POS(i) _transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones[i].NodeIndex].GetTranslation()) - BoundingBox box(GET_NODE_POS(0)); - for (int32 boneIndex = 1; boneIndex < bonesCount; boneIndex++) - box.Merge(GET_NODE_POS(boneIndex)); - _box = box; -#undef GET_NODE_POS - } - else - { - _box = SkinnedModel->GetBox(_transform.GetWorld()); + for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) + box.Merge(_transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones.Get()[boneIndex].NodeIndex].GetTranslation())); } + _box = box; // Apply margin based on model dimensions - const Vector3 modelBoxSize = SkinnedModel->GetBox().GetSize(); + const Vector3 modelBoxSize = model->GetBox().GetSize(); const Vector3 center = _box.GetCenter(); const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f; _box = BoundingBox(center - sizeHalf, center + sizeHalf); @@ -595,7 +767,6 @@ void AnimatedModel::OnAnimationUpdated_Async() } UpdateBounds(); - _blendShapes.Update(SkinnedModel.Get()); } void AnimatedModel::OnAnimationUpdated_Sync() @@ -616,23 +787,20 @@ void AnimatedModel::OnAnimationUpdated() void AnimatedModel::OnSkinnedModelChanged() { Entries.Release(); - if (SkinnedModel && !SkinnedModel->IsLoaded()) { UpdateBounds(); GraphInstance.Invalidate(); } + if (_deformation) + _deformation->Clear(); GraphInstance.NodesSkeleton = SkinnedModel; } void AnimatedModel::OnSkinnedModelLoaded() { Entries.SetupIfInvalid(SkinnedModel); - GraphInstance.Invalidate(); - if (_blendShapes.Weights.HasItems()) - _blendShapes.WeightsDirty = true; - PreInitSkinningData(); } @@ -699,7 +867,7 @@ void AnimatedModel::Draw(RenderContext& renderContext) if (!SkinnedModel || !SkinnedModel->IsLoaded()) return; if (renderContext.View.Pass == DrawPass::GlobalSDF) - return; // TODO: Animated Model rendering to Global SDF + return; if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) return; // No supported Matrix world; @@ -721,9 +889,9 @@ void AnimatedModel::Draw(RenderContext& renderContext) SkinnedMesh::DrawInfo draw; draw.Buffer = &Entries; draw.Skinning = &_skinningData; - draw.BlendShapes = &_blendShapes; draw.World = &world; draw.DrawState = &_drawState; + draw.Deformation = _deformation; PRAGMA_DISABLE_DEPRECATION_WARNINGS draw.DrawModes = DrawModes & renderContext.View.GetShadowsDrawPassMask(ShadowsMode); PRAGMA_ENABLE_DEPRECATION_WARNINGS @@ -764,9 +932,9 @@ void AnimatedModel::Draw(RenderContextBatch& renderContextBatch) SkinnedMesh::DrawInfo draw; draw.Buffer = &Entries; draw.Skinning = &_skinningData; - draw.BlendShapes = &_blendShapes; draw.World = &world; draw.DrawState = &_drawState; + draw.Deformation = _deformation; draw.DrawModes = DrawModes; draw.Bounds = _sphere; draw.Bounds.Center -= renderContext.View.Origin; @@ -957,6 +1125,13 @@ bool AnimatedModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count); } +MeshDeformation* AnimatedModel::GetMeshDeformation() const +{ + if (!_deformation) + _deformation = New(); + return _deformation; +} + void AnimatedModel::OnDeleteObject() { // Ensure this object is no longer referenced for anim update @@ -965,14 +1140,6 @@ void AnimatedModel::OnDeleteObject() ModelInstanceActor::OnDeleteObject(); } -void AnimatedModel::OnTransformChanged() -{ - // Base - ModelInstanceActor::OnTransformChanged(); - - UpdateBounds(); -} - void AnimatedModel::WaitForModelLoad() { if (SkinnedModel) diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 904216356..d9623f77f 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -17,7 +17,7 @@ class FLAXENGINE_API AnimatedModel : public ModelInstanceActor { DECLARE_SCENE_OBJECT(AnimatedModel); friend class AnimationsSystem; -public: + /// /// Describes the animation graph updates frequency for the animated model. /// @@ -55,16 +55,27 @@ public: }; private: + struct BlendShapeMesh + { + uint16 LODIndex; + uint16 MeshIndex; + uint32 Usages; + }; + GeometryDrawStateData _drawState; SkinnedMeshDrawData _skinningData; AnimationUpdateMode _actualMode; uint32 _counter; Real _lastMinDstSqr; uint64 _lastUpdateFrame; - BlendShapesInstance _blendShapes; + mutable MeshDeformation* _deformation = nullptr; ScriptingObjectReference _masterPose; + Array> _blendShapeWeights; + Array _blendShapeMeshes; public: + ~AnimatedModel(); + /// /// The skinned model asset used for rendering. /// @@ -291,7 +302,7 @@ public: API_FUNCTION() void SetBlendShapeWeight(const StringView& name, float value); /// - /// Clears the weights of the blend shapes (disabled any used blend shapes). + /// Clears the weights of the blend shapes (disables any used blend shapes). /// API_FUNCTION() void ClearBlendShapeWeights(); @@ -346,9 +357,9 @@ public: private: void ApplyRootMotion(const Transform& rootMotionDelta); void SyncParameters(); + void RunBlendShapeDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation); void Update(); - void UpdateBounds(); void UpdateSockets(); void OnAnimationUpdated_Async(); void OnAnimationUpdated_Sync(); @@ -375,6 +386,8 @@ public: bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override; + void UpdateBounds() override; + MeshDeformation* GetMeshDeformation() const override; void OnDeleteObject() override; protected: @@ -384,6 +397,5 @@ protected: void OnEnable() override; void OnDisable() override; void OnActiveInTreeChanged() override; - void OnTransformChanged() override; void WaitForModelLoad() override; }; diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index f2625ca3b..72c7f4c54 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -332,6 +332,7 @@ void Camera::Draw(RenderContext& renderContext) draw.Buffer = &_previewModelBuffer; draw.World = &world; draw.DrawState = &drawState; + draw.Deformation = nullptr; draw.Lightmap = nullptr; draw.LightmapUVs = nullptr; draw.Flags = StaticFlags::Transform; diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index 0c1879d83..9209f6af0 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -59,6 +59,14 @@ void ModelInstanceActor::OnLayerChanged() GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } +void ModelInstanceActor::OnTransformChanged() +{ + // Base + Actor::OnTransformChanged(); + + UpdateBounds(); +} + void ModelInstanceActor::OnEnable() { GetSceneRendering()->AddActor(this, _sceneRenderingKey); diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index 850bc8715..e72d790d8 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -97,7 +97,7 @@ public: { return false; } - + /// /// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel). /// @@ -105,18 +105,33 @@ public: /// Buffer type /// The result data /// The amount of items inside the result buffer. - /// True if failed, otherwise false + /// True if failed, otherwise false. virtual bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const { return true; } + /// + /// Gets the mesh deformation utility for this model instance (optional). + /// + /// Model deformation utility or null if not supported. + virtual MeshDeformation* GetMeshDeformation() const + { + return nullptr; + } + + /// + /// Updates the model bounds (eg. when mesh has applied significant deformation). + /// + virtual void UpdateBounds() = 0; + protected: virtual void WaitForModelLoad(); public: // [Actor] void OnLayerChanged() override; + void OnTransformChanged() override; protected: // [Actor] diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 08dc5ddea..99c691ce9 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -341,6 +341,11 @@ void SplineModel::OnParentChanged() OnSplineUpdated(); } +void SplineModel::UpdateBounds() +{ + OnSplineUpdated(); +} + bool SplineModel::HasContentLoaded() const { return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded(); @@ -489,14 +494,6 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DrawModes |= DrawPass::GlobalSurfaceAtlas; } -void SplineModel::OnTransformChanged() -{ - // Base - ModelInstanceActor::OnTransformChanged(); - - OnSplineUpdated(); -} - void SplineModel::OnActiveInTreeChanged() { // Base diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h index 50864d7b7..312da07d6 100644 --- a/Source/Engine/Level/Actors/SplineModel.h +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -115,9 +115,9 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void OnParentChanged() override; + void UpdateBounds() override; protected: // [ModelInstanceActor] - void OnTransformChanged() override; void OnActiveInTreeChanged() override; }; diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 4e67d90d2..17d5ca805 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -7,6 +7,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scene/Scene.h" @@ -36,6 +37,8 @@ StaticModel::~StaticModel() { for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++) SAFE_DELETE_GPU_RESOURCE(_vertexColorsBuffer[lodIndex]); + if (_deformation) + Delete(_deformation); } float StaticModel::GetScaleInLightmap() const @@ -230,6 +233,8 @@ void StaticModel::OnModelChanged() Entries.Release(); if (Model && !Model->IsLoaded()) UpdateBounds(); + if (_deformation) + _deformation->Clear(); else if (!Model && _sceneRenderingKey != -1) GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); } @@ -265,11 +270,12 @@ void StaticModel::OnModelResidencyChanged() void StaticModel::UpdateBounds() { - if (Model && Model->IsLoaded()) + auto model = Model.Get(); + if (model && model->IsLoaded()) { Transform transform = _transform; transform.Scale *= _boundsScale; - _box = Model->GetBox(transform); + _box = model->LODs[0].GetBox(transform, _deformation); } else { @@ -339,6 +345,7 @@ void StaticModel::Draw(RenderContext& renderContext) draw.Buffer = &Entries; draw.World = &world; draw.DrawState = &_drawState; + draw.Deformation = _deformation; draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); draw.LightmapUVs = &Lightmap.UVsArea; draw.Flags = _staticFlags; @@ -372,6 +379,7 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch) draw.Buffer = &Entries; draw.World = &world; draw.DrawState = &_drawState; + draw.Deformation = _deformation; draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); draw.LightmapUVs = &Lightmap.UVsArea; draw.Flags = _staticFlags; @@ -600,12 +608,11 @@ bool StaticModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, By return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count); } -void StaticModel::OnTransformChanged() +MeshDeformation* StaticModel::GetMeshDeformation() const { - // Base - ModelInstanceActor::OnTransformChanged(); - - UpdateBounds(); + if (!_deformation) + _deformation = New(); + return _deformation; } void StaticModel::OnEnable() diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index ffb34e0a1..8ce945316 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -25,6 +25,7 @@ private: Array _vertexColorsData[MODEL_MAX_LODS]; GPUBuffer* _vertexColorsBuffer[MODEL_MAX_LODS]; Model* _residencyChangedModel = nullptr; + mutable MeshDeformation* _deformation = nullptr; public: /// @@ -155,7 +156,6 @@ private: void OnModelChanged(); void OnModelLoaded(); void OnModelResidencyChanged(); - void UpdateBounds(); void FlushVertexColors(); public: @@ -169,10 +169,11 @@ public: bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override; + MeshDeformation* GetMeshDeformation() const override; + void UpdateBounds() override; protected: // [ModelInstanceActor] - void OnTransformChanged() override; void OnEnable() override; void OnDisable() override; void WaitForModelLoad() override;