From 104f60f3e7dc282cf1ff0e7d61f19a2c5ef62af2 Mon Sep 17 00:00:00 2001 From: SirLynix Date: Mon, 18 Apr 2022 19:10:34 +0200 Subject: [PATCH] Add support for GPU skinning (WIP) --- examples/Showcase/main.cpp | 343 ++++++++++++++++++ examples/Showcase/xmake.lua | 6 + include/Nazara/Graphics/ElementRenderer.hpp | 1 + include/Nazara/Graphics/Enums.hpp | 1 + include/Nazara/Graphics/MaterialPass.hpp | 11 +- include/Nazara/Graphics/MaterialPass.inl | 38 +- include/Nazara/Utility/Algorithm.hpp | 8 +- include/Nazara/Utility/Enums.hpp | 2 + .../Nazara/Utility/Formats/MD5AnimParser.hpp | 2 +- include/Nazara/Utility/VertexStruct.hpp | 2 - plugins/Assimp/Plugin.cpp | 94 +++-- src/Nazara/Graphics/BasicMaterial.cpp | 20 + src/Nazara/Graphics/GraphicalMesh.cpp | 2 - src/Nazara/Graphics/MaterialPass.cpp | 20 +- src/Nazara/Graphics/PhongLightingMaterial.cpp | 12 +- .../Resources/Shaders/BasicMaterial.nzsl | 39 +- .../Resources/Shaders/PhongMaterial.nzsl | 10 +- src/Nazara/Graphics/SubmeshRenderer.cpp | 17 + src/Nazara/Utility/AlgorithmUtility.cpp | 46 ++- src/Nazara/Utility/Formats/MD5AnimLoader.cpp | 27 +- src/Nazara/Utility/Formats/MD5MeshLoader.cpp | 94 +++-- src/Nazara/Utility/VertexDeclaration.cpp | 13 +- 22 files changed, 667 insertions(+), 141 deletions(-) create mode 100644 examples/Showcase/main.cpp create mode 100644 examples/Showcase/xmake.lua diff --git a/examples/Showcase/main.cpp b/examples/Showcase/main.cpp new file mode 100644 index 000000000..4f22de5c4 --- /dev/null +++ b/examples/Showcase/main.cpp @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +NAZARA_REQUEST_DEDICATED_GPU() + +int main() +{ + std::filesystem::path resourceDir = "resources"; + if (!std::filesystem::is_directory(resourceDir) && std::filesystem::is_directory(".." / resourceDir)) + resourceDir = ".." / resourceDir; + + Nz::Renderer::Config rendererConfig; + //rendererConfig.preferredAPI = Nz::RenderAPI::OpenGL; + + Nz::Modules nazara(rendererConfig); + + Nz::PluginManager::Mount(Nz::Plugin::Assimp); + + std::shared_ptr device = Nz::Graphics::Instance()->GetRenderDevice(); + + std::string windowTitle = "Skinning test"; + Nz::RenderWindow window; + if (!window.Create(device, Nz::VideoMode(1280, 720, 32), windowTitle)) + { + std::cout << "Failed to create Window" << std::endl; + return __LINE__; + } + + entt::registry registry; + + Nz::Physics3DSystem physSytem(registry); + Nz::RenderSystem renderSystem(registry); + + physSytem.GetPhysWorld().SetGravity({ 0.f, -9.81f, 0.f }); + + Nz::TextureParams texParams; + texParams.renderDevice = device; + + entt::entity playerEntity = registry.create(); + { + auto& playerNode = registry.emplace(playerEntity); + playerNode.SetPosition(0.f, 1.8f, 1.f); + playerNode.SetRotation(Nz::EulerAnglesf(-30.f, 0.f, 0.f)); + + auto& playerBody = registry.emplace(playerEntity, &physSytem.GetPhysWorld()); + playerBody.SetMass(0.f); + playerBody.SetGeom(std::make_shared(1.8f, 0.5f)); + + auto& playerComponent = registry.emplace(playerEntity, window.GetRenderTarget()); + playerComponent.UpdateZNear(0.2f); + playerComponent.UpdateRenderMask(1); + playerComponent.UpdateClearColor(Nz::Color(127, 127, 127)); + } + + Nz::FieldOffsets skeletalOffsets(Nz::StructLayout::Std140); + std::size_t arrayOffset = skeletalOffsets.AddMatrixArray(Nz::StructFieldType::Float1, 4, 4, true, 200); + + std::vector skeletalBufferMem(skeletalOffsets.GetAlignedSize()); + Nz::Matrix4f* matrices = Nz::AccessByOffset(skeletalBufferMem.data(), arrayOffset); + + std::shared_ptr bobAnim = Nz::Animation::LoadFromFile(resourceDir / "hellknight/idle2.md5anim"); + if (!bobAnim) + { + NazaraError("Failed to load bob anim"); + return __LINE__; + } + + Nz::MeshParams meshParams; + meshParams.animated = true; + //meshParams.center = true; + //meshParams.matrix = Nz::Matrix4f::Scale(Nz::Vector3f(10.f)); + meshParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_Normal_UV_Tangent_Skinning); + + std::shared_ptr bobMesh = Nz::Mesh::LoadFromFile(resourceDir / "hellknight/hellknight.md5mesh", meshParams); + if (!bobMesh) + { + NazaraError("Failed to load bob mesh"); + return __LINE__; + } + + Nz::Skeleton skeleton = *bobMesh->GetSkeleton(); + + bobAnim->AnimateSkeleton(&skeleton, 0, 1, 0.5f); + + /*for (std::size_t i = 0; i < bobMesh->GetSubMeshCount(); ++i) + { + Nz::VertexMapper mapper(*bobMesh->GetSubMesh(i)); + + Nz::SkinningData skinningData; + skinningData.joints = skeleton.GetJoints(); + skinningData.inputJointIndices = mapper.GetComponentPtr(Nz::VertexComponent::JointIndices); + skinningData.inputJointWeights = mapper.GetComponentPtr(Nz::VertexComponent::JointWeights); + skinningData.outputPositions = mapper.GetComponentPtr(Nz::VertexComponent::Position); + skinningData.inputPositions = skinningData.outputPositions; + + Nz::SkinPosition(skinningData, 0, mapper.GetVertexCount()); + }*/ + + for (std::size_t i = 0; i < skeleton.GetJointCount(); ++i) + matrices[i] = skeleton.GetJoint(i)->GetSkinningMatrix(); + + std::shared_ptr renderBuffer = device->InstantiateBuffer(Nz::BufferType::Uniform, skeletalBufferMem.size(), Nz::BufferUsage::Write, skeletalBufferMem.data()); + + entt::entity bobEntity = registry.create(); + { + const Nz::Boxf& bobAABB = bobMesh->GetAABB(); + std::shared_ptr bobGfxMesh = std::make_shared(*bobMesh); + + std::shared_ptr bobModel = std::make_shared(std::move(bobGfxMesh), bobAABB); + for (std::size_t i = 0; i < bobMesh->GetMaterialCount(); ++i) + { + std::string matPath; + bobMesh->GetMaterialData(i).GetStringParameter(Nz::MaterialData::FilePath, &matPath); + + std::shared_ptr bobMat = std::make_shared(); + + std::shared_ptr bobMatPass = std::make_shared(Nz::BasicMaterial::GetSettings()); + bobMatPass->SetSharedUniformBuffer(0, renderBuffer); + + bobMatPass->EnableDepthBuffer(true); + { + Nz::BasicMaterial basicMat(*bobMatPass); + basicMat.SetDiffuseMap(Nz::Texture::LoadFromFile(matPath + ".tga", texParams)); + } + if (i == 0 || i == 3) + bobMat->AddPass("ForwardPass", bobMatPass); + + bobModel->SetMaterial(i, bobMat); + } + + auto& bobNode = registry.emplace(bobEntity); + bobNode.SetPosition(Nz::Vector3f(0.f, bobAABB.height / 2.f, 0.f)); + bobNode.SetRotation(Nz::EulerAnglesf(-90.f, -90.f, 0.f)); + bobNode.SetScale(1.f / 40.f * 0.5f); + + auto& bobGfx = registry.emplace(bobEntity); + bobGfx.AttachRenderable(bobModel, 0xFFFFFFFF); + } + + entt::entity planeEntity = registry.create(); + { + Nz::MeshParams meshPrimitiveParams; + meshPrimitiveParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_Normal_UV); + + Nz::Vector2f planeSize(25.f, 25.f); + + Nz::Mesh planeMesh; + planeMesh.CreateStatic(); + planeMesh.BuildSubMesh(Nz::Primitive::Plane(planeSize, Nz::Vector2ui(0u), Nz::Matrix4f::Identity(), Nz::Rectf(0.f, 0.f, 10.f, 10.f)), meshPrimitiveParams); + planeMesh.SetMaterialCount(1); + + std::shared_ptr planeMeshGfx = std::make_shared(planeMesh); + + std::shared_ptr planeMat = std::make_shared(); + + std::shared_ptr planeMatPass = std::make_shared(Nz::BasicMaterial::GetSettings()); + planeMatPass->EnableDepthBuffer(true); + { + Nz::BasicMaterial basicMat(*planeMatPass); + basicMat.SetDiffuseMap(Nz::Texture::LoadFromFile(resourceDir / "dev_grey.png", texParams)); + + Nz::TextureSamplerInfo planeSampler; + planeSampler.anisotropyLevel = 16; + planeSampler.wrapModeU = Nz::SamplerWrap::Repeat; + planeSampler.wrapModeV = Nz::SamplerWrap::Repeat; + basicMat.SetDiffuseSampler(planeSampler); + } + planeMat->AddPass("ForwardPass", planeMatPass); + + std::shared_ptr planeModel = std::make_shared(std::move(planeMeshGfx), planeMesh.GetAABB()); + planeModel->SetMaterial(0, planeMat); + + auto& planeNode = registry.emplace(planeEntity); + + auto& planeBody = registry.emplace(planeEntity, &physSytem.GetPhysWorld()); + planeBody.SetGeom(std::make_shared(Nz::Vector3f(planeSize.x, 0.5f, planeSize.y), Nz::Vector3f(0.f, -0.25f, 0.f))); + + auto& planeGfx = registry.emplace(planeEntity); + planeGfx.AttachRenderable(planeModel, 0xFFFFFFFF); + } + + Nz::Clock fpsClock, updateClock; + float incr = 0.f; + unsigned int currentFrame = 0; + unsigned int nextFrame = 1; + Nz::UInt64 lastTime = Nz::GetElapsedMicroseconds(); + Nz::UInt64 fps = 0; + while (window.IsOpen()) + { + Nz::UInt64 now = Nz::GetElapsedMicroseconds(); + Nz::UInt64 elapsedTime = (now - lastTime) / 1'000'000.f; + lastTime = now; + + Nz::WindowEvent event; + while (window.PollEvent(&event)) + { + switch (event.type) + { + case Nz::WindowEventType::Quit: + window.Close(); + break; + + case Nz::WindowEventType::KeyPressed: + break; + + case Nz::WindowEventType::MouseMoved: + break; + + default: + break; + } + } + + if (updateClock.GetMilliseconds() > 1000 / 60) + { + float updateTime = updateClock.Restart() / 1'000'000.f; + + physSytem.Update(registry, 1.f / 60.f); + + auto& playerBody = registry.get(playerEntity); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Space)) + playerBody.AddForce(Nz::Vector3f(0.f, playerBody.GetMass() * 50.f, 0.f)); + + incr += 1.f / 60.f * 30.f; + if (incr >= 1.f) + { + incr -= 1.f; + + currentFrame = nextFrame; + nextFrame++; + if (nextFrame >= bobAnim->GetFrameCount()) + nextFrame = 0; + } + + bobAnim->AnimateSkeleton(&skeleton, currentFrame, nextFrame, incr); + for (std::size_t i = 0; i < skeleton.GetJointCount(); ++i) + matrices[i] = skeleton.GetJoint(i)->GetSkinningMatrix(); + + //renderBuffer->Fill(skeletalBufferMem.data(), 0, skeletalBufferMem.size()); + + /*auto spaceshipView = registry.view(); + for (auto&& [entity, node, _] : spaceshipView.each()) + { + if (entity == playerEntity) + continue; + + Nz::Vector3f spaceshipPos = node.GetPosition(Nz::CoordSys::Global); + if (spaceshipPos.GetSquaredLength() > Nz::IntegralPow(20.f, 2)) + registry.destroy(entity); + } + + Nz::RigidBody3DComponent& playerShipBody = registry.get(playerEntity); + Nz::Quaternionf currentRotation = playerShipBody.GetRotation(); + + Nz::Vector3f desiredHeading = registry.get(headingEntity).GetForward(); + Nz::Vector3f currentHeading = currentRotation * Nz::Vector3f::Forward(); + Nz::Vector3f headingError = currentHeading.CrossProduct(desiredHeading); + + Nz::Vector3f desiredUp = registry.get(headingEntity).GetUp(); + Nz::Vector3f currentUp = currentRotation * Nz::Vector3f::Up(); + Nz::Vector3f upError = currentUp.CrossProduct(desiredUp); + + playerShipBody.AddTorque(headingController.Update(headingError, elapsedTime) * 10.f); + playerShipBody.AddTorque(upController.Update(upError, elapsedTime) * 10.f); + + float mass = playerShipBody.GetMass(); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Up) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Z)) + playerShipBody.AddForce(Nz::Vector3f::Forward() * 2.5f * mass, Nz::CoordSys::Local); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Down) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::S)) + playerShipBody.AddForce(Nz::Vector3f::Backward() * 2.5f * mass, Nz::CoordSys::Local); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Left) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Q)) + playerShipBody.AddForce(Nz::Vector3f::Left() * 2.5f * mass, Nz::CoordSys::Local); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Right) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::D)) + playerShipBody.AddForce(Nz::Vector3f::Right() * 2.5f * mass, Nz::CoordSys::Local); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::LShift) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::RShift)) + playerShipBody.AddForce(Nz::Vector3f::Up() * 3.f * mass, Nz::CoordSys::Local); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::LControl) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::RControl)) + playerShipBody.AddForce(Nz::Vector3f::Down() * 3.f * mass, Nz::CoordSys::Local);*/ + } + + Nz::RenderFrame frame = window.AcquireFrame(); + if (!frame) + continue; + + frame.Execute([&](Nz::CommandBufferBuilder& builder) + { + builder.BeginDebugRegion("Skeletal UBO Update", Nz::Color::Yellow); + { + builder.PreTransferBarrier(); + + auto& skeletalAllocation = frame.GetUploadPool().Allocate(skeletalBufferMem.size()); + std::memcpy(skeletalAllocation.mappedPtr, skeletalBufferMem.data(), skeletalBufferMem.size()); + + builder.CopyBuffer(skeletalAllocation, Nz::RenderBufferView(renderBuffer.get())); + + builder.PostTransferBarrier(); + } + builder.EndDebugRegion(); + }, Nz::QueueType::Graphics); + + renderSystem.Render(registry, frame); + + frame.Present(); + + fps++; + + if (fpsClock.GetMilliseconds() >= 1000) + { + fpsClock.Restart(); + + window.SetTitle(windowTitle + " - " + Nz::NumberToString(fps) + " FPS" + " - " + Nz::NumberToString(registry.alive()) + " entities"); + + fps = 0; + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/Showcase/xmake.lua b/examples/Showcase/xmake.lua new file mode 100644 index 000000000..98593e3d6 --- /dev/null +++ b/examples/Showcase/xmake.lua @@ -0,0 +1,6 @@ +target("Showcase") + set_group("Examples") + set_kind("binary") + add_deps("NazaraAudio", "NazaraGraphics", "NazaraPhysics2D", "NazaraPhysics3D", "NazaraWidgets") + add_packages("entt") + add_files("main.cpp") diff --git a/include/Nazara/Graphics/ElementRenderer.hpp b/include/Nazara/Graphics/ElementRenderer.hpp index cc0bc674e..80b343134 100644 --- a/include/Nazara/Graphics/ElementRenderer.hpp +++ b/include/Nazara/Graphics/ElementRenderer.hpp @@ -41,6 +41,7 @@ namespace Nz struct RenderStates { RenderBufferView lightData; + RenderBufferView skeletalData; }; }; diff --git a/include/Nazara/Graphics/Enums.hpp b/include/Nazara/Graphics/Enums.hpp index fb2be51f0..2a796356b 100644 --- a/include/Nazara/Graphics/Enums.hpp +++ b/include/Nazara/Graphics/Enums.hpp @@ -67,6 +67,7 @@ namespace Nz InstanceDataUbo, LightDataUbo, OverlayTexture, + SkeletalDataUbo, ViewerDataUbo, Max = ViewerDataUbo diff --git a/include/Nazara/Graphics/MaterialPass.hpp b/include/Nazara/Graphics/MaterialPass.hpp index cfe0cf0ef..aef9c18b2 100644 --- a/include/Nazara/Graphics/MaterialPass.hpp +++ b/include/Nazara/Graphics/MaterialPass.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -101,9 +102,10 @@ namespace Nz inline void SetOptionValue(std::size_t optionIndex, nzsl::Ast::ConstantSingleValue value); inline void SetPointSize(float pointSize); inline void SetPrimitiveMode(PrimitiveMode mode); + inline void SetSharedUniformBuffer(std::size_t sharedUboIndex, std::shared_ptr uniformBuffer); + inline void SetSharedUniformBuffer(std::size_t sharedUboIndex, std::shared_ptr uniformBuffer, UInt64 offset, UInt64 size); inline void SetTexture(std::size_t textureIndex, std::shared_ptr texture); inline void SetTextureSampler(std::size_t textureIndex, TextureSamplerInfo samplerInfo); - inline void SetUniformBuffer(std::size_t bufferIndex, std::shared_ptr uniformBuffer); void Update(RenderFrame& renderFrame, CommandBufferBuilder& builder); @@ -135,6 +137,12 @@ namespace Nz NazaraSlot(UberShader, OnShaderUpdated, onShaderUpdated); }; + struct ShaderUniformBuffer + { + std::shared_ptr buffer; //< kept for ownership + RenderBufferView bufferView; + }; + struct UniformBuffer { std::shared_ptr buffer; @@ -146,6 +154,7 @@ namespace Nz std::shared_ptr m_settings; std::vector m_textures; std::vector m_shaders; + std::vector m_sharedUniformBuffers; std::vector m_uniformBuffers; mutable std::shared_ptr m_pipeline; mutable MaterialPipelineInfo m_pipelineInfo; diff --git a/include/Nazara/Graphics/MaterialPass.inl b/include/Nazara/Graphics/MaterialPass.inl index 985266636..e5e8a1697 100644 --- a/include/Nazara/Graphics/MaterialPass.inl +++ b/include/Nazara/Graphics/MaterialPass.inl @@ -602,6 +602,32 @@ namespace Nz InvalidatePipeline(); } + inline void MaterialPass::SetSharedUniformBuffer(std::size_t sharedUboIndex, std::shared_ptr uniformBuffer) + { + if (uniformBuffer) + { + UInt64 size = uniformBuffer->GetSize(); + return SetSharedUniformBuffer(sharedUboIndex, std::move(uniformBuffer), 0, size); + } + else + return SetSharedUniformBuffer(sharedUboIndex, std::move(uniformBuffer), 0, 0); + } + + inline void MaterialPass::SetSharedUniformBuffer(std::size_t sharedUboIndex, std::shared_ptr uniformBuffer, UInt64 offset, UInt64 size) + { + NazaraAssert(sharedUboIndex < m_sharedUniformBuffers.size(), "Invalid shared uniform buffer index"); + + RenderBufferView bufferView(uniformBuffer.get(), offset, size); + + if (m_sharedUniformBuffers[sharedUboIndex].bufferView != bufferView) + { + m_sharedUniformBuffers[sharedUboIndex].bufferView = bufferView; + m_sharedUniformBuffers[sharedUboIndex].buffer = std::move(uniformBuffer); + + InvalidateShaderBinding(); + } + } + inline void MaterialPass::SetTexture(std::size_t textureIndex, std::shared_ptr texture) { NazaraAssert(textureIndex < m_textures.size(), "Invalid texture index"); @@ -624,18 +650,6 @@ namespace Nz } } - inline void MaterialPass::SetUniformBuffer(std::size_t bufferIndex, std::shared_ptr uniformBuffer) - { - NazaraAssert(bufferIndex < m_uniformBuffers.size(), "Invalid shared uniform buffer index"); - if (m_uniformBuffers[bufferIndex].buffer != uniformBuffer) - { - m_uniformBuffers[bufferIndex].buffer = std::move(uniformBuffer); - m_uniformBuffers[bufferIndex].dataInvalidated = true; - - InvalidateShaderBinding(); - } - } - inline void MaterialPass::InvalidatePipeline() { m_pipelineUpdated = false; diff --git a/include/Nazara/Utility/Algorithm.hpp b/include/Nazara/Utility/Algorithm.hpp index 6a91d3fd9..b03afe8d4 100644 --- a/include/Nazara/Utility/Algorithm.hpp +++ b/include/Nazara/Utility/Algorithm.hpp @@ -30,8 +30,12 @@ namespace Nz struct SkinningData { const Joint* joints; - const SkeletalMeshVertex* inputVertex; - MeshVertex* outputVertex; + SparsePtr inputPositions; + SparsePtr inputJointIndices; + SparsePtr inputJointWeights; + SparsePtr inputUv; + SparsePtr outputPositions; + SparsePtr outputUv; }; struct VertexPointers diff --git a/include/Nazara/Utility/Enums.hpp b/include/Nazara/Utility/Enums.hpp index a31b3f5ed..f4339d0c5 100644 --- a/include/Nazara/Utility/Enums.hpp +++ b/include/Nazara/Utility/Enums.hpp @@ -385,6 +385,8 @@ namespace Nz Unused = -1, Color, + JointIndices, + JointWeights, Normal, Position, Tangent, diff --git a/include/Nazara/Utility/Formats/MD5AnimParser.hpp b/include/Nazara/Utility/Formats/MD5AnimParser.hpp index c2bf67ec8..6fbc35daf 100644 --- a/include/Nazara/Utility/Formats/MD5AnimParser.hpp +++ b/include/Nazara/Utility/Formats/MD5AnimParser.hpp @@ -33,9 +33,9 @@ namespace Nz struct Joint { + std::string name; Int32 parent; Quaternionf bindOrient; - std::string name; Vector3f bindPos; UInt32 flags; UInt32 index; diff --git a/include/Nazara/Utility/VertexStruct.hpp b/include/Nazara/Utility/VertexStruct.hpp index 212e0e648..9f0534aba 100644 --- a/include/Nazara/Utility/VertexStruct.hpp +++ b/include/Nazara/Utility/VertexStruct.hpp @@ -76,8 +76,6 @@ namespace Nz struct VertexStruct_XYZ_Normal_UV_Tangent_Skinning : VertexStruct_XYZ_Normal_UV_Tangent { - Int32 weightCount; - Vector4f weights; Vector4i32 jointIndexes; }; diff --git a/plugins/Assimp/Plugin.cpp b/plugins/Assimp/Plugin.cpp index e3e26389f..60d70124a 100644 --- a/plugins/Assimp/Plugin.cpp +++ b/plugins/Assimp/Plugin.cpp @@ -43,32 +43,29 @@ SOFTWARE. #include #include #include -#include +#include using namespace Nz; -void ProcessJoints(aiNode* node, Skeleton* skeleton, const std::set& joints) +void ProcessJoints(aiNode* node, Skeleton* skeleton, const std::unordered_set& joints) { - std::string jointName(node->mName.data, node->mName.length); + std::string_view jointName(node->mName.data, node->mName.length); if (joints.count(jointName)) { Joint* joint = skeleton->GetJoint(jointName); - if (joint) - { - if (node->mParent) - joint->SetParent(skeleton->GetJoint(node->mParent->mName.C_Str())); - Matrix4f transformMatrix(node->mTransformation.a1, node->mTransformation.a2, node->mTransformation.a3, node->mTransformation.a4, - node->mTransformation.b1, node->mTransformation.b2, node->mTransformation.b3, node->mTransformation.b4, - node->mTransformation.c1, node->mTransformation.c2, node->mTransformation.c3, node->mTransformation.c4, - node->mTransformation.d1, node->mTransformation.d2, node->mTransformation.d3, node->mTransformation.d4); + if (node->mParent) + joint->SetParent(skeleton->GetJoint(node->mParent->mName.C_Str())); - transformMatrix.Transpose(); + Matrix4f transformMatrix(node->mTransformation.a1, node->mTransformation.a2, node->mTransformation.a3, node->mTransformation.a4, + node->mTransformation.b1, node->mTransformation.b2, node->mTransformation.b3, node->mTransformation.b4, + node->mTransformation.c1, node->mTransformation.c2, node->mTransformation.c3, node->mTransformation.c4, + node->mTransformation.d1, node->mTransformation.d2, node->mTransformation.d3, node->mTransformation.d4); - transformMatrix.InverseTransform(); + transformMatrix.Transpose(); + transformMatrix.InverseTransform(); - joint->SetInverseBindMatrix(transformMatrix); - } + joint->SetInverseBindMatrix(transformMatrix); } for (unsigned int i = 0; i < node->mNumChildren; ++i) @@ -118,8 +115,8 @@ std::shared_ptr LoadAnimation(Stream& stream, const AnimationParams& | aiProcess_TransformUVCoords | aiProcess_Triangulate; aiPropertyStore* properties = aiCreatePropertyStore(); - aiSetImportPropertyInteger(properties, AI_CONFIG_PP_LBW_MAX_WEIGHTS, 4); - aiSetImportPropertyInteger(properties, AI_CONFIG_PP_RVC_FLAGS, ~aiComponent_ANIMATIONS); + aiSetImportPropertyInteger(properties, AI_CONFIG_PP_LBW_MAX_WEIGHTS, 4); + aiSetImportPropertyInteger(properties, AI_CONFIG_PP_RVC_FLAGS, ~aiComponent_ANIMATIONS); const aiScene* scene = aiImportFileExWithProperties(userdata.originalFilePath, postProcess, &fileIO, properties); aiReleasePropertyStore(properties); @@ -259,7 +256,7 @@ std::shared_ptr LoadMesh(Stream& stream, const MeshParams& parameters) return nullptr; } - std::set joints; + std::unordered_set joints; bool animatedMesh = false; if (parameters.animated) @@ -285,8 +282,8 @@ std::shared_ptr LoadMesh(Stream& stream, const MeshParams& parameters) // First, assign names unsigned int jointIndex = 0; - for (const std::string& jointName : joints) - skeleton->GetJoint(jointIndex++)->SetName(jointName); + for (std::string_view jointName : joints) + skeleton->GetJoint(jointIndex++)->SetName(std::string(jointName)); ProcessJoints(scene->mRootNode, skeleton, joints); @@ -330,34 +327,51 @@ std::shared_ptr LoadMesh(Stream& stream, const MeshParams& parameters) normalTangentMatrix.ApplyScale(1.f / normalTangentMatrix.GetScale()); std::shared_ptr vertexBuffer = std::make_shared(VertexDeclaration::Get(VertexLayout::XYZ_Normal_UV_Tangent_Skinning), vertexCount, parameters.vertexBufferFlags, parameters.bufferFactory); - BufferMapper vertexMapper(*vertexBuffer, 0, vertexBuffer->GetVertexCount()); - SkeletalMeshVertex* vertices = static_cast(vertexMapper.GetPointer()); - for (std::size_t vertexIdx = 0; vertexIdx < vertexCount; ++vertexIdx) + VertexMapper vertexMapper(*vertexBuffer); + + auto posPtr = vertexMapper.GetComponentPtr(VertexComponent::Position); + auto normalPtr = vertexMapper.GetComponentPtr(VertexComponent::Normal); + auto tangentPtr = vertexMapper.GetComponentPtr(VertexComponent::Tangent); + auto jointIndicesPtr = vertexMapper.GetComponentPtr(VertexComponent::JointIndices); + auto jointWeightPtr = vertexMapper.GetComponentPtr(VertexComponent::JointWeights); + auto uvPtr = vertexMapper.GetComponentPtr(VertexComponent::TexCoord); + + for (std::size_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { - aiVector3D normal = iMesh->mNormals[vertexIdx]; - aiVector3D position = iMesh->mVertices[vertexIdx]; - aiVector3D tangent = iMesh->mTangents[vertexIdx]; - aiVector3D uv = iMesh->mTextureCoords[0][vertexIdx]; + aiVector3D normal = iMesh->mNormals[vertexIndex]; + aiVector3D position = iMesh->mVertices[vertexIndex]; + aiVector3D tangent = iMesh->mTangents[vertexIndex]; + aiVector3D uv = iMesh->mTextureCoords[0][vertexIndex]; - vertices[vertexIdx].weightCount = 0; - vertices[vertexIdx].normal = normalTangentMatrix.Transform({ normal.x, normal.y, normal.z }, 0.f); - vertices[vertexIdx].position = parameters.matrix * Vector3f(position.x, position.y, position.z); - vertices[vertexIdx].tangent = normalTangentMatrix.Transform({ tangent.x, tangent.y, tangent.z }, 0.f); - vertices[vertexIdx].uv = parameters.texCoordOffset + Vector2f(uv.x, uv.y) * parameters.texCoordScale; + if (posPtr) + posPtr[vertexIndex] = parameters.matrix * Vector3f(position.x, position.y, position.z); + + if (normalPtr) + normalPtr[vertexIndex] = normalTangentMatrix.Transform({ normal.x, normal.y, normal.z }, 0.f); + + if (tangentPtr) + tangentPtr[vertexIndex] = normalTangentMatrix.Transform({ tangent.x, tangent.y, tangent.z }, 0.f); + + if (uvPtr) + uvPtr[vertexIndex] = parameters.texCoordOffset + Vector2f(uv.x, uv.y) * parameters.texCoordScale; } - for (unsigned int boneIdx = 0; boneIdx < iMesh->mNumBones; ++boneIdx) + if (jointIndicesPtr || jointWeightPtr) { - aiBone* bone = iMesh->mBones[boneIdx]; - for (unsigned int weightIdx = 0; weightIdx < bone->mNumWeights; ++weightIdx) + for (unsigned int boneIndex = 0; boneIndex < iMesh->mNumBones; ++boneIndex) { - aiVertexWeight& vertexWeight = bone->mWeights[weightIdx]; - SkeletalMeshVertex& vertex = vertices[vertexWeight.mVertexId]; + aiBone* bone = iMesh->mBones[boneIndex]; + for (unsigned int weightIndex = 0; weightIndex < bone->mNumWeights; ++weightIndex) + { + aiVertexWeight& vertexWeight = bone->mWeights[weightIndex]; - std::size_t weightIndex = vertex.weightCount++; - vertex.jointIndexes[weightIndex] = boneIdx; - vertex.weights[weightIndex] = vertexWeight.mWeight; + if (jointIndicesPtr) + jointIndicesPtr[vertexWeight.mVertexId][weightIndex] = boneIndex; + + if (jointWeightPtr) + jointWeightPtr[vertexWeight.mVertexId][weightIndex] = vertexWeight.mWeight; + } } } diff --git a/src/Nazara/Graphics/BasicMaterial.cpp b/src/Nazara/Graphics/BasicMaterial.cpp index 4533e7bf7..263e7021f 100644 --- a/src/Nazara/Graphics/BasicMaterial.cpp +++ b/src/Nazara/Graphics/BasicMaterial.cpp @@ -151,6 +151,16 @@ namespace Nz options.defaultValues }); + FieldOffsets skeletalOffsets(StructLayout::Std140); + skeletalOffsets.AddMatrixArray(StructFieldType::Float1, 4, 4, true, 100); + + settings.sharedUniformBlocks.push_back({ + 6, + "SkeletalData", + {}, + ShaderStageType::Vertex + }); + // Common data settings.textures.push_back({ 3, @@ -160,9 +170,11 @@ namespace Nz settings.sharedUniformBlocks.push_back(PredefinedInstanceData::GetUniformBlock(4, nzsl::ShaderStageType::Vertex)); settings.sharedUniformBlocks.push_back(PredefinedViewerData::GetUniformBlock(5, nzsl::ShaderStageType_All)); + //settings.sharedUniformBlocks.push_back(PredefinedInstanceData::GetUniformBlock(6, nzsl::ShaderStageType::Vertex)); settings.predefinedBindings[UnderlyingCast(PredefinedShaderBinding::InstanceDataUbo)] = 4; settings.predefinedBindings[UnderlyingCast(PredefinedShaderBinding::OverlayTexture)] = 3; + //settings.predefinedBindings[UnderlyingCast(PredefinedShaderBinding::SkeletalDataUbo)] = 6; settings.predefinedBindings[UnderlyingCast(PredefinedShaderBinding::ViewerDataUbo)] = 5; settings.shaders = options.shaders; @@ -194,6 +206,14 @@ namespace Nz config.optionValues[CRC32("UvLocation")] = locationIndex; break; + case VertexComponent::JointIndices: + config.optionValues[CRC32("JointIndicesLocation")] = locationIndex; + break; + + case VertexComponent::JointWeights: + config.optionValues[CRC32("JointWeightsLocation")] = locationIndex; + break; + case VertexComponent::Unused: default: break; diff --git a/src/Nazara/Graphics/GraphicalMesh.cpp b/src/Nazara/Graphics/GraphicalMesh.cpp index 63b475ba5..f5a435788 100644 --- a/src/Nazara/Graphics/GraphicalMesh.cpp +++ b/src/Nazara/Graphics/GraphicalMesh.cpp @@ -13,8 +13,6 @@ namespace Nz { std::shared_ptr GraphicalMesh::BuildFromMesh(const Mesh& mesh) { - assert(mesh.GetAnimationType() == AnimationType::Static); - const std::shared_ptr& renderDevice = Graphics::Instance()->GetRenderDevice(); std::shared_ptr gfxMesh = std::make_shared(); diff --git a/src/Nazara/Graphics/MaterialPass.cpp b/src/Nazara/Graphics/MaterialPass.cpp index 9b6207cdc..bc9611f93 100644 --- a/src/Nazara/Graphics/MaterialPass.cpp +++ b/src/Nazara/Graphics/MaterialPass.cpp @@ -47,9 +47,11 @@ namespace Nz } const auto& textureSettings = m_settings->GetTextures(); + const auto& sharedUboSettings = m_settings->GetSharedUniformBlocks(); const auto& uboSettings = m_settings->GetUniformBlocks(); m_textures.resize(textureSettings.size()); + m_sharedUniformBuffers.resize(sharedUboSettings.size()); m_uniformBuffers.reserve(uboSettings.size()); for (const auto& uniformBufferInfo : uboSettings) @@ -68,6 +70,7 @@ namespace Nz void MaterialPass::FillShaderBinding(std::vector& bindings) const { const auto& textureSettings = m_settings->GetTextures(); + const auto& sharedUboSettings = m_settings->GetSharedUniformBlocks(); const auto& uboSettings = m_settings->GetUniformBlocks(); // Textures @@ -98,7 +101,22 @@ namespace Nz }); } - // Shared UBO (TODO) + // Shared UBO + for (std::size_t i = 0; i < m_sharedUniformBuffers.size(); ++i) + { + const auto& sharedUboSlot = m_sharedUniformBuffers[i]; + if (!sharedUboSlot.bufferView) + continue; + + const auto& sharedUboSetting = sharedUboSettings[i]; + + bindings.push_back({ + sharedUboSetting.bindingIndex, + ShaderBinding::UniformBufferBinding { + sharedUboSlot.bufferView.GetBuffer(), sharedUboSlot.bufferView.GetOffset(), sharedUboSlot.bufferView.GetSize() + } + }); + } // Owned UBO for (std::size_t i = 0; i < m_uniformBuffers.size(); ++i) diff --git a/src/Nazara/Graphics/PhongLightingMaterial.cpp b/src/Nazara/Graphics/PhongLightingMaterial.cpp index c713b0372..57e029773 100644 --- a/src/Nazara/Graphics/PhongLightingMaterial.cpp +++ b/src/Nazara/Graphics/PhongLightingMaterial.cpp @@ -186,7 +186,7 @@ namespace Nz options.phongTextureIndexes->emissive = settings.textures.size(); settings.textures.push_back({ - 7, + 8, "Emissive", ImageType::E2D }); @@ -195,7 +195,7 @@ namespace Nz options.phongTextureIndexes->height = settings.textures.size(); settings.textures.push_back({ - 8, + 9, "Height", ImageType::E2D }); @@ -204,7 +204,7 @@ namespace Nz options.phongTextureIndexes->normal = settings.textures.size(); settings.textures.push_back({ - 9, + 10, "Normal", ImageType::E2D }); @@ -213,7 +213,7 @@ namespace Nz options.phongTextureIndexes->specular = settings.textures.size(); settings.textures.push_back({ - 10, + 11, "Specular", ImageType::E2D }); @@ -229,8 +229,8 @@ namespace Nz options.defaultValues }); - settings.sharedUniformBlocks.push_back(PredefinedLightData::GetUniformBlock(6, nzsl::ShaderStageType::Fragment)); - settings.predefinedBindings[UnderlyingCast(PredefinedShaderBinding::LightDataUbo)] = 6; + settings.sharedUniformBlocks.push_back(PredefinedLightData::GetUniformBlock(7, nzsl::ShaderStageType::Fragment)); + settings.predefinedBindings[UnderlyingCast(PredefinedShaderBinding::LightDataUbo)] = 7; settings.shaders = options.shaders; diff --git a/src/Nazara/Graphics/Resources/Shaders/BasicMaterial.nzsl b/src/Nazara/Graphics/Resources/Shaders/BasicMaterial.nzsl index 3fa01d4d4..4b124c19a 100644 --- a/src/Nazara/Graphics/Resources/Shaders/BasicMaterial.nzsl +++ b/src/Nazara/Graphics/Resources/Shaders/BasicMaterial.nzsl @@ -19,9 +19,13 @@ option ColorLocation: i32 = -1; option PosLocation: i32; option UvLocation: i32 = -1; +option JointIndicesLocation: i32 = -1; +option JointWeightsLocation: i32 = -1; + const HasVertexColor = (ColorLocation >= 0); const HasColor = (HasVertexColor || Billboard); const HasUV = (UvLocation >= 0); +const HasSkinning = (JointIndicesLocation >= 0 && JointWeightsLocation >= 0); [layout(std140)] struct MaterialSettings @@ -30,6 +34,14 @@ struct MaterialSettings BaseColor: vec4[f32] } +const MaxJointCount: u32 = u32(200); //< FIXME: Fix integral value types + +[layout(std140)] +struct SkeletalData +{ + JointMatrices: array[mat4[f32], MaxJointCount] +} + external { [binding(0)] settings: uniform[MaterialSettings], @@ -38,6 +50,7 @@ external [binding(3)] TextureOverlay: sampler2D[f32], [binding(4)] instanceData: uniform[InstanceData], [binding(5)] viewerData: uniform[ViewerData], + [binding(6)] skeletalData: uniform[SkeletalData] } // Fragment stage @@ -92,6 +105,12 @@ struct VertIn [cond(HasUV), location(UvLocation)] uv: vec2[f32], + [cond(HasSkinning), location(JointIndicesLocation)] + jointIndices: vec4[i32], + + [cond(HasSkinning), location(JointWeightsLocation)] + jointWeights: vec4[f32], + [cond(Billboard), location(BillboardCenterLocation)] billboardCenter: vec3[f32], @@ -143,8 +162,26 @@ fn billboardMain(input: VertIn) -> VertOut [entry(vert), cond(!Billboard)] fn main(input: VertIn) -> VertOut { + let pos: vec3[f32]; + const if (HasSkinning) + { + pos = vec3[f32](0.0, 0.0, 0.0); + + [unroll] + for i in 0 -> 4 + { + let jointIndex = input.jointIndices[i]; + let jointWeight = input.jointWeights[i]; + + let jointMatrix = skeletalData.JointMatrices[jointIndex]; + pos += (jointMatrix * vec4[f32](input.pos, 1.0)).xyz * jointWeight; + } + } + else + pos = input.pos; + let output: VertOut; - output.position = viewerData.viewProjMatrix * instanceData.worldMatrix * vec4[f32](input.pos, 1.0); + output.position = viewerData.viewProjMatrix * instanceData.worldMatrix * vec4[f32](pos, 1.0); const if (HasColor) output.color = input.color; diff --git a/src/Nazara/Graphics/Resources/Shaders/PhongMaterial.nzsl b/src/Nazara/Graphics/Resources/Shaders/PhongMaterial.nzsl index d00cf7fcb..2e448b046 100644 --- a/src/Nazara/Graphics/Resources/Shaders/PhongMaterial.nzsl +++ b/src/Nazara/Graphics/Resources/Shaders/PhongMaterial.nzsl @@ -62,11 +62,11 @@ external [binding(3)] TextureOverlay: sampler2D[f32], [binding(4)] instanceData: uniform[InstanceData], [binding(5)] viewerData: uniform[ViewerData], - [binding(6)] lightData: uniform[LightData], - [binding(7)] MaterialEmissiveMap: sampler2D[f32], - [binding(8)] MaterialHeightMap: sampler2D[f32], - [binding(9)] MaterialNormalMap: sampler2D[f32], - [binding(10)] MaterialSpecularMap: sampler2D[f32], + [binding(7)] lightData: uniform[LightData], + [binding(8)] MaterialEmissiveMap: sampler2D[f32], + [binding(9)] MaterialHeightMap: sampler2D[f32], + [binding(10)] MaterialNormalMap: sampler2D[f32], + [binding(11)] MaterialSpecularMap: sampler2D[f32], } struct VertToFrag diff --git a/src/Nazara/Graphics/SubmeshRenderer.cpp b/src/Nazara/Graphics/SubmeshRenderer.cpp index 808adbf1e..8dc547561 100644 --- a/src/Nazara/Graphics/SubmeshRenderer.cpp +++ b/src/Nazara/Graphics/SubmeshRenderer.cpp @@ -33,6 +33,7 @@ namespace Nz const WorldInstance* currentWorldInstance = nullptr; Recti currentScissorBox = invalidScissorBox; RenderBufferView currentLightData; + RenderBufferView currentSkeletalData; auto FlushDrawCall = [&]() { @@ -95,6 +96,12 @@ namespace Nz currentLightData = renderState.lightData; } + if (currentSkeletalData != renderState.skeletalData) + { + FlushDrawData(); + currentSkeletalData = renderState.skeletalData; + } + const Recti& scissorBox = submesh.GetScissorBox(); const Recti& targetScissorBox = (scissorBox.width >= 0) ? scissorBox : invalidScissorBox; if (currentScissorBox != targetScissorBox) @@ -135,6 +142,16 @@ namespace Nz }; } + if (std::size_t bindingIndex = matSettings->GetPredefinedBinding(PredefinedShaderBinding::SkeletalDataUbo); bindingIndex != MaterialSettings::InvalidIndex && currentSkeletalData) + { + auto& bindingEntry = m_bindingCache.emplace_back(); + bindingEntry.bindingIndex = bindingIndex; + bindingEntry.content = ShaderBinding::UniformBufferBinding{ + currentSkeletalData.GetBuffer(), + currentSkeletalData.GetOffset(), currentSkeletalData.GetSize() + }; + } + if (std::size_t bindingIndex = matSettings->GetPredefinedBinding(PredefinedShaderBinding::ViewerDataUbo); bindingIndex != MaterialSettings::InvalidIndex) { const auto& viewerBuffer = viewerInstance.GetViewerBuffer(); diff --git a/src/Nazara/Utility/AlgorithmUtility.cpp b/src/Nazara/Utility/AlgorithmUtility.cpp index 608d64c9f..c2b5f68b1 100644 --- a/src/Nazara/Utility/AlgorithmUtility.cpp +++ b/src/Nazara/Utility/AlgorithmUtility.cpp @@ -1067,33 +1067,43 @@ namespace Nz void SkinPosition(const SkinningData& skinningInfos, UInt64 startVertex, UInt64 vertexCount) { - const SkeletalMeshVertex* inputVertex = &skinningInfos.inputVertex[startVertex]; - MeshVertex* outputVertex = &skinningInfos.outputVertex[startVertex]; + NazaraAssert(skinningInfos.inputJointIndices, "missing input joint indices"); + NazaraAssert(skinningInfos.inputJointWeights, "missing input joint weights"); UInt64 endVertex = startVertex + vertexCount - 1; - for (UInt64 i = startVertex; i <= endVertex; ++i) + if (skinningInfos.outputPositions) { - Vector3f finalPosition(Vector3f::Zero()); + NazaraAssert(skinningInfos.inputPositions, "missing input positions"); + NazaraAssert(skinningInfos.joints, "missing skeleton joints"); - for (Int32 j = 0; j < inputVertex->weightCount; ++j) + for (UInt64 i = startVertex; i <= endVertex; ++i) { - Matrix4f mat(skinningInfos.joints[inputVertex->jointIndexes[j]].GetSkinningMatrix()); - mat *= inputVertex->weights[j]; + Vector3f finalPosition(Vector3f::Zero()); - finalPosition += mat.Transform(inputVertex->position); + for (Int32 j = 0; j < 4; ++j) + { + Matrix4f mat(skinningInfos.joints[skinningInfos.inputJointIndices[i][j]].GetSkinningMatrix()); + mat *= skinningInfos.inputJointWeights[i][j]; + + finalPosition += mat.Transform(skinningInfos.inputPositions[i]); + } + + skinningInfos.outputPositions[i] = finalPosition; } + } - outputVertex->position = finalPosition; - outputVertex->uv = inputVertex->uv; + if (skinningInfos.outputUv) + { + NazaraAssert(skinningInfos.inputUv, "missing input uv"); - inputVertex++; - outputVertex++; + for (UInt64 i = startVertex; i <= endVertex; ++i) + skinningInfos.outputUv[i] = skinningInfos.inputUv[i]; } } void SkinPositionNormal(const SkinningData& skinningInfos, UInt64 startVertex, UInt64 vertexCount) { - const SkeletalMeshVertex* inputVertex = &skinningInfos.inputVertex[startVertex]; + /*const SkeletalMeshVertex* inputVertex = &skinningInfos.inputVertex[startVertex]; MeshVertex* outputVertex = &skinningInfos.outputVertex[startVertex]; UInt64 endVertex = startVertex + vertexCount - 1; @@ -1102,7 +1112,7 @@ namespace Nz Vector3f finalPosition(Vector3f::Zero()); Vector3f finalNormal(Vector3f::Zero()); - for (Int32 j = 0; j < inputVertex->weightCount; ++j) + for (Int32 j = 0; j < 4; ++j) { Matrix4f mat(skinningInfos.joints[inputVertex->jointIndexes[j]].GetSkinningMatrix()); mat *= inputVertex->weights[j]; @@ -1119,12 +1129,12 @@ namespace Nz inputVertex++; outputVertex++; - } + }*/ } void SkinPositionNormalTangent(const SkinningData& skinningInfos, UInt64 startVertex, UInt64 vertexCount) { - const SkeletalMeshVertex* inputVertex = &skinningInfos.inputVertex[startVertex]; + /*const SkeletalMeshVertex* inputVertex = &skinningInfos.inputVertex[startVertex]; MeshVertex* outputVertex = &skinningInfos.outputVertex[startVertex]; UInt64 endVertex = startVertex + vertexCount - 1; @@ -1134,7 +1144,7 @@ namespace Nz Vector3f finalNormal(Vector3f::Zero()); Vector3f finalTangent(Vector3f::Zero()); - for (int j = 0; j < inputVertex->weightCount; ++j) + for (int j = 0; j < 4; ++j) { Matrix4f mat(skinningInfos.joints[inputVertex->jointIndexes[j]].GetSkinningMatrix()); mat *= inputVertex->weights[j]; @@ -1154,6 +1164,6 @@ namespace Nz inputVertex++; outputVertex++; - } + }*/ } } diff --git a/src/Nazara/Utility/Formats/MD5AnimLoader.cpp b/src/Nazara/Utility/Formats/MD5AnimLoader.cpp index c5181a8ac..d8658d137 100644 --- a/src/Nazara/Utility/Formats/MD5AnimLoader.cpp +++ b/src/Nazara/Utility/Formats/MD5AnimLoader.cpp @@ -62,22 +62,27 @@ namespace Nz Quaternionf rotationQuat = Quaternionf::RotationBetween(Vector3f::UnitX(), Vector3f::Forward()) * Quaternionf::RotationBetween(Vector3f::UnitZ(), Vector3f::Up()); - for (UInt32 i = 0; i < jointCount; ++i) - { - int parent = joints[i].parent; - for (UInt32 j = 0; j < frameCount; ++j) - { - SequenceJoint& sequenceJoint = sequenceJoints[j*jointCount + i]; + //Matrix4f matrix = Matrix4f::Transform(Nz::Vector3f::Zero(), rotationQuat, Vector3f(1.f / 40.f)); + //matrix *= parameters.matrix; - if (parent >= 0) + rotationQuat = Quaternionf::Identity(); + + for (UInt32 frameIndex = 0; frameIndex < frameCount; ++frameIndex) + { + for (UInt32 jointIndex = 0; jointIndex < jointCount; ++jointIndex) + { + SequenceJoint& sequenceJoint = sequenceJoints[frameIndex * jointCount + jointIndex]; + + Int32 parentId = joints[jointIndex].parent; + if (parentId >= 0) { - sequenceJoint.position = frames[j].joints[i].pos; - sequenceJoint.rotation = frames[j].joints[i].orient; + sequenceJoint.position = frames[frameIndex].joints[jointIndex].pos; + sequenceJoint.rotation = frames[frameIndex].joints[jointIndex].orient; } else { - sequenceJoint.position = rotationQuat * frames[j].joints[i].pos; - sequenceJoint.rotation = rotationQuat * frames[j].joints[i].orient; + sequenceJoint.position = rotationQuat * frames[frameIndex].joints[jointIndex].pos; + sequenceJoint.rotation = rotationQuat * frames[frameIndex].joints[jointIndex].orient; } sequenceJoint.scale.Set(1.f); diff --git a/src/Nazara/Utility/Formats/MD5MeshLoader.cpp b/src/Nazara/Utility/Formats/MD5MeshLoader.cpp index 84e643179..5b51856c1 100644 --- a/src/Nazara/Utility/Formats/MD5MeshLoader.cpp +++ b/src/Nazara/Utility/Formats/MD5MeshLoader.cpp @@ -44,6 +44,19 @@ namespace Nz return nullptr; } + UInt32 maxWeightCount = 4; + long long customMaxWeightCount; + if (parameters.custom.GetIntegerParameter("MaxWeightCount", &customMaxWeightCount)) + { + maxWeightCount = SafeCast(customMaxWeightCount); + if (maxWeightCount > 4) + { + NazaraWarning("MaxWeightCount cannot be over 4"); + maxWeightCount = 4; + } + } + + // Pour que le squelette soit correctement aligné, il faut appliquer un quaternion "de correction" aux joints à la base du squelette Quaternionf rotationQuat = Quaternionf::RotationBetween(Vector3f::UnitX(), Vector3f::Forward()) * Quaternionf::RotationBetween(Vector3f::UnitZ(), Vector3f::Up()); @@ -55,6 +68,8 @@ namespace Nz Matrix4f matrix = Matrix4f::Transform(Nz::Vector3f::Zero(), rotationQuat, Vector3f(1.f / 40.f)); matrix *= parameters.matrix; + rotationQuat = Quaternionf::Identity(); + const MD5MeshParser::Joint* joints = parser.GetJoints(); const MD5MeshParser::Mesh* meshes = parser.GetMeshes(); UInt32 jointCount = parser.GetJointCount(); @@ -120,8 +135,12 @@ namespace Nz std::vector tempWeights; - BufferMapper vertexMapper(*vertexBuffer, 0, vertexBuffer->GetVertexCount()); - SkeletalMeshVertex* vertices = static_cast(vertexMapper.GetPointer()); + VertexMapper vertexMapper(*vertexBuffer); + + auto posPtr = vertexMapper.GetComponentPtr(VertexComponent::Position); + auto jointIndicesPtr = vertexMapper.GetComponentPtr(VertexComponent::JointIndices); + auto jointWeightPtr = vertexMapper.GetComponentPtr(VertexComponent::JointWeights); + auto uvPtr = vertexMapper.GetComponentPtr(VertexComponent::TexCoord); for (const MD5MeshParser::Vertex& vertex : md5Mesh.vertices) { @@ -130,21 +149,21 @@ namespace Nz // On stocke tous les poids dans le tableau temporaire en même temps qu'on calcule la position finale du sommet. tempWeights.resize(vertex.weightCount); - for (unsigned int j = 0; j < vertex.weightCount; ++j) + for (unsigned int weightIndex = 0; weightIndex < vertex.weightCount; ++weightIndex) { - const MD5MeshParser::Weight& weight = md5Mesh.weights[vertex.startWeight + j]; + const MD5MeshParser::Weight& weight = md5Mesh.weights[vertex.startWeight + weightIndex]; const MD5MeshParser::Joint& joint = joints[weight.joint]; - finalPos += (joint.bindPos + joint.bindOrient*weight.pos) * weight.bias; + finalPos += (joint.bindPos + joint.bindOrient * weight.pos) * weight.bias; // Avant d'ajouter les poids, il faut s'assurer qu'il n'y en ait pas plus que le maximum supporté // et dans le cas contraire, garder les poids les plus importants et les renormaliser - tempWeights[j] = {weight.bias, weight.joint}; + tempWeights[weightIndex] = {weight.bias, weight.joint}; } // Avons nous plus de poids que le moteur ne peut en supporter ? - unsigned int weightCount = vertex.weightCount; - if (weightCount > NAZARA_UTILITY_SKINNING_MAX_WEIGHTS) + UInt32 weightCount = vertex.weightCount; + if (weightCount > maxWeightCount) { // Pour augmenter la qualité du skinning tout en ne gardant que X poids, on ne garde que les poids // les plus importants, ayant le plus d'impact sur le sommet final @@ -154,36 +173,43 @@ namespace Nz // Sans oublier bien sûr de renormaliser les poids (que leur somme soit 1) float weightSum = 0.f; - for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j) + for (UInt32 j = 0; j < maxWeightCount; ++j) weightSum += tempWeights[j].bias; - for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j) + for (UInt32 j = 0; j < maxWeightCount; ++j) tempWeights[j].bias /= weightSum; - weightCount = NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; + weightCount = maxWeightCount; } - vertices->weightCount = weightCount; - for (unsigned int j = 0; j < NAZARA_UTILITY_SKINNING_MAX_WEIGHTS; ++j) + if (posPtr) + *posPtr++ = /*finalPos * */Vector3f(1.f / 40.f); + + if (uvPtr) + *uvPtr++ = Vector2f(parameters.texCoordOffset + vertex.uv * parameters.texCoordScale); + + if (jointIndicesPtr) { - if (j < weightCount) - { - // On donne une valeur aux poids présents - vertices->weights[j] = tempWeights[j].bias; - vertices->jointIndexes[j] = tempWeights[j].jointIndex; - } - else - { - // Et un poids de 0 sur le joint 0 pour les autres (nécessaire pour le GPU Skinning) - // La raison est que le GPU ne tiendra pas compte du nombre de poids pour des raisons de performances. - vertices->weights[j] = 0.f; - vertices->jointIndexes[j] = 0; - } + Vector4i32& jointIndices = *jointIndicesPtr++; + + for (UInt32 j = 0; j < maxWeightCount; ++j) + jointIndices[j] = (j < weightCount) ? tempWeights[j].jointIndex : 0; } - vertices->position = finalPos; - vertices->uv.Set(parameters.texCoordOffset + vertex.uv * parameters.texCoordScale); - vertices++; + if (jointWeightPtr) + { + Vector4f& jointWeights = *jointWeightPtr++; + + for (UInt32 j = 0; j < maxWeightCount; ++j) + jointWeights[j] = (j < weightCount) ? tempWeights[j].bias : 0; + } + } + + // Vertex colors (.md5mesh files have no vertex color) + if (auto colorPtr = vertexMapper.GetComponentPtr(VertexComponent::Color)) + { + for (std::size_t j = 0; j < md5Mesh.vertices.size(); ++j) + *colorPtr++ = Color::White; } vertexMapper.Unmap(); @@ -196,7 +222,15 @@ namespace Nz // Submesh std::shared_ptr subMesh = std::make_shared(vertexBuffer, indexBuffer); - subMesh->GenerateNormalsAndTangents(); + + if (parameters.vertexDeclaration->HasComponentOfType(VertexComponent::Normal)) + { + if (parameters.vertexDeclaration->HasComponentOfType(VertexComponent::Tangent)) + subMesh->GenerateNormalsAndTangents(); + else + subMesh->GenerateNormals(); + } + subMesh->SetMaterialIndex(i); mesh->AddSubMesh(subMesh); diff --git a/src/Nazara/Utility/VertexDeclaration.cpp b/src/Nazara/Utility/VertexDeclaration.cpp index 18556dbff..bf9c45905 100644 --- a/src/Nazara/Utility/VertexDeclaration.cpp +++ b/src/Nazara/Utility/VertexDeclaration.cpp @@ -280,19 +280,14 @@ namespace Nz 0 }, { - VertexComponent::Userdata, - ComponentType::Int1, - 0 // Weight count - }, - { - VertexComponent::Userdata, + VertexComponent::JointWeights, ComponentType::Float4, - 1 // Weights + 0 }, { - VertexComponent::Userdata, + VertexComponent::JointIndices, ComponentType::Int4, - 2 // Joint indexes + 0 }, });