diff --git a/.gitmodules b/.gitmodules index 01ca7bef..a1139df9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "ThirdParty/glfw"] path = ThirdParty/glfw url = https://github.com/DiligentGraphics/glfw.git +[submodule "ThirdParty/ozz-animation"] + path = ThirdParty/ozz-animation + url = https://github.com/guillaumeblanc/ozz-animation.git diff --git a/Samples/Animation/Animation00_Playback/Animation_Large.gif b/Samples/Animation/Animation00_Playback/Animation_Large.gif new file mode 100644 index 00000000..e6f9a008 Binary files /dev/null and b/Samples/Animation/Animation00_Playback/Animation_Large.gif differ diff --git a/Samples/Animation/Animation00_Playback/Animation_Small.gif b/Samples/Animation/Animation00_Playback/Animation_Small.gif new file mode 100644 index 00000000..0f5359f1 Binary files /dev/null and b/Samples/Animation/Animation00_Playback/Animation_Small.gif differ diff --git a/Samples/Animation/Animation00_Playback/CMakeLists.txt b/Samples/Animation/Animation00_Playback/CMakeLists.txt new file mode 100644 index 00000000..871b1b8e --- /dev/null +++ b/Samples/Animation/Animation00_Playback/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required (VERSION 3.6) + +project(Animation00_Playback CXX) + +set(SOURCE + src/Animation00_Playback.cpp + ../Common/src/AnimationUtilities.cpp + ../Common/src/PlaybackController.cpp +) + +set(INCLUDE + src/Animation00_Playback.hpp + ../Common/src/AnimationUtilities.hpp + ../Common/src/PlaybackController.hpp +) + +set(SHADERS + assets/skeleton.vsh + assets/skeleton.psh + assets/plane.vsh + assets/plane.psh +) + +set(ASSETS + assets/pab_crossarms.ozz + assets/pab_skeleton.ozz +) + +add_sample_app("Animation00_Playback" "DiligentSamples/Samples/Animation" "${SOURCE}" "${INCLUDE}" "${SHADERS}" "${ASSETS}") + +target_link_libraries(Animation00_Playback +PRIVATE + ozz_base + ozz_animation +) \ No newline at end of file diff --git a/Samples/Animation/Animation00_Playback/Screenshot.png b/Samples/Animation/Animation00_Playback/Screenshot.png new file mode 100644 index 00000000..14dc7377 Binary files /dev/null and b/Samples/Animation/Animation00_Playback/Screenshot.png differ diff --git a/Samples/Animation/Animation00_Playback/assets/pab_crossarms.ozz b/Samples/Animation/Animation00_Playback/assets/pab_crossarms.ozz new file mode 100644 index 00000000..5d46383a Binary files /dev/null and b/Samples/Animation/Animation00_Playback/assets/pab_crossarms.ozz differ diff --git a/Samples/Animation/Animation00_Playback/assets/pab_skeleton.ozz b/Samples/Animation/Animation00_Playback/assets/pab_skeleton.ozz new file mode 100644 index 00000000..d1f1c95b Binary files /dev/null and b/Samples/Animation/Animation00_Playback/assets/pab_skeleton.ozz differ diff --git a/Samples/Animation/Animation00_Playback/assets/plane.psh b/Samples/Animation/Animation00_Playback/assets/plane.psh new file mode 100644 index 00000000..ffab43dc --- /dev/null +++ b/Samples/Animation/Animation00_Playback/assets/plane.psh @@ -0,0 +1,32 @@ +struct PlanePSInput +{ + float4 Pos : SV_POSITION; + float2 TexCoord : TEXCOORD; +}; + +struct PlanePSOutput +{ + float4 Color : SV_TARGET; +}; + +void main(in PlanePSInput PSIn, + out PlanePSOutput PSOut) +{ + float tol = 0.001000000; + float res = 0.05; + float4 backgroundColor = float4(0.5, 0.75, 0.85, 1.0f); // blue + float4 lineColor = float4(0.25, 0.25, 0.25, 1.0); // grey + float4 originColor = float4(0.0, 0.0, 0.0, 1.0); // black + if (((abs(((PSIn.TexCoord).x - 0.5)) <= tol) && (abs(((PSIn.TexCoord).y - 0.5)) <= tol))) + { + PSOut.Color = originColor; + } + else if (((((fmod((PSIn.TexCoord).x, res) >= (res - tol)) || (fmod((PSIn.TexCoord).x, res) < tol)) || (fmod((PSIn.TexCoord).y, res) >= (res - tol))) || (fmod((PSIn.TexCoord).y, res) < tol))) + { + PSOut.Color = lineColor; + } + else + { + PSOut.Color = backgroundColor; + } +} diff --git a/Samples/Animation/Animation00_Playback/assets/plane.vsh b/Samples/Animation/Animation00_Playback/assets/plane.vsh new file mode 100644 index 00000000..09a71038 --- /dev/null +++ b/Samples/Animation/Animation00_Playback/assets/plane.vsh @@ -0,0 +1,32 @@ +cbuffer Constants +{ + float4x4 g_WorldViewProj; +} + +struct PlanePSInput +{ + float4 Pos : SV_POSITION; + float2 TexCoord : TEXCOORD; +}; + +void main(in uint VertId : SV_VertexID, + out PlanePSInput PSIn) +{ + float PlaneExtent = 5.0; + float PlanePos = 0.0; + + float4 Pos[4]; + Pos[0] = float4(-PlaneExtent, PlanePos, -PlaneExtent, 1.0); + Pos[1] = float4(-PlaneExtent, PlanePos, +PlaneExtent, 1.0); + Pos[2] = float4(+PlaneExtent, PlanePos, -PlaneExtent, 1.0); + Pos[3] = float4(+PlaneExtent, PlanePos, +PlaneExtent, 1.0); + + float2 Uv[4]; + Uv[0] = float2(0.0, 0.0); + Uv[1] = float2(0.0, 1.0); + Uv[2] = float2(1.0, 0.0); + Uv[3] = float2(1.0, 1.0); + + PSIn.Pos = mul(Pos[VertId], g_WorldViewProj); + PSIn.TexCoord = Uv[VertId]; +} diff --git a/Samples/Animation/Animation00_Playback/assets/skeleton.psh b/Samples/Animation/Animation00_Playback/assets/skeleton.psh new file mode 100644 index 00000000..ad78d551 --- /dev/null +++ b/Samples/Animation/Animation00_Playback/assets/skeleton.psh @@ -0,0 +1,31 @@ +struct PSInput +{ + float4 Pos : SV_POSITION; + float3 Normal : NORMAL; + float4 Color : COLOR0; +}; + +struct PSOutput +{ + float4 Color : SV_TARGET; +}; + +float4 GetAmbient(float3 WorldNormal) +{ + float3 Normal = normalize(WorldNormal); + float3 Alpha = (Normal + 1.0) * 0.5; + float2 Bt = lerp(float2(0.3, 0.7), float2(0.4, 0.8), Alpha.xz); + float3 Ambine = lerp(float3(Bt.x, 0.3, Bt.x), float3(Bt.y, 0.8, Bt.y), Alpha.y); + + return float4(Ambine, 1.0); +} + +// Note that if separate shader objects are not supported (this is only the case for old GLES3.0 devices), vertex +// shader output variable name must match exactly the name of the pixel shader input variable. +// If the variable has structure type (like in this example), the structure declarations must also be indentical. +void main(in PSInput PSIn, + out PSOutput PSOut) +{ + float4 Ambine = GetAmbient(PSIn.Normal); + PSOut.Color = Ambine * PSIn.Color; +} diff --git a/Samples/Animation/Animation00_Playback/assets/skeleton.vsh b/Samples/Animation/Animation00_Playback/assets/skeleton.vsh new file mode 100644 index 00000000..bb112a0e --- /dev/null +++ b/Samples/Animation/Animation00_Playback/assets/skeleton.vsh @@ -0,0 +1,53 @@ +cbuffer Constants +{ + float4x4 g_WorldViewProj; +}; + +// Vertex shader takes two inputs: vertex position and color. +// By convention, Diligent Engine expects vertex shader inputs to be +// labeled 'ATTRIBn', where n is the attribute number. +struct VSInput +{ + // Vertex attributes + float3 Pos : ATTRIB0; + float3 Normal : ATTRIB1; + float4 Color : ATTRIB2; + + // Instance attributes + float4 MtrxRow0 : ATTRIB3; + float4 MtrxRow1 : ATTRIB4; + float4 MtrxRow2 : ATTRIB5; + float4 MtrxRow3 : ATTRIB6; +}; + +struct PSInput +{ + float4 Pos : SV_POSITION; + float3 Normal : NORMAL; + float4 Color : COLOR0; +}; + +// Note that if separate shader objects are not supported (this is only the case for old GLES3.0 devices), vertex +// shader output variable name must match exactly the name of the pixel shader input variable. +// If the variable has structure type (like in this example), the structure declarations must also be indentical. +void main(in VSInput VSIn, + out PSInput PSIn) +{ + // HLSL matrices are row-major while GLSL matrices are column-major. We will + // use convenience function MatrixFromRows() appropriately defined by the engine + float4x4 InstanceMatr = MatrixFromRows(VSIn.MtrxRow0, VSIn.MtrxRow1, VSIn.MtrxRow2, VSIn.MtrxRow3); + // Apply instance-specific transformation + float4 TransformedPos = mul(float4(VSIn.Pos, 1.0), InstanceMatr); + // Apply view-projection matrix + PSIn.Pos = mul(TransformedPos, g_WorldViewProj); + + float3x3 CrossMatr = float3x3( + cross(InstanceMatr[1].xyz, InstanceMatr[2].xyz), + cross(InstanceMatr[2].xyz, InstanceMatr[0].xyz), + cross(InstanceMatr[0].xyz, InstanceMatr[1].xyz) + ); + float Invdet = 1.0 / dot(CrossMatr[2], InstanceMatr[2].xyz); + float3x3 NormalMatr = CrossMatr * Invdet; + PSIn.Normal = mul(VSIn.Normal, NormalMatr); + PSIn.Color = VSIn.Color; +} diff --git a/Samples/Animation/Animation00_Playback/readme.md b/Samples/Animation/Animation00_Playback/readme.md new file mode 100644 index 00000000..3005eca0 --- /dev/null +++ b/Samples/Animation/Animation00_Playback/readme.md @@ -0,0 +1,5 @@ +# Animation Playback Demo + +![](Screenshot.png) + +This sample demonstrates how to integrate ozz-animation library into an application to play skeletal animation. \ No newline at end of file diff --git a/Samples/Animation/Animation00_Playback/src/Animation00_Playback.cpp b/Samples/Animation/Animation00_Playback/src/Animation00_Playback.cpp new file mode 100644 index 00000000..36953bb0 --- /dev/null +++ b/Samples/Animation/Animation00_Playback/src/Animation00_Playback.cpp @@ -0,0 +1,424 @@ +/* + * Copyright 2019-2021 Diligent Graphics LLC + * Copyright 2015-2019 Egor Yusov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#include "Animation00_Playback.hpp" +#include "MapHelper.hpp" +#include "GraphicsUtilities.h" +#include "../../Common/src/AnimationUtilities.hpp" +#include "ozz/animation/runtime/local_to_model_job.h" + +namespace Diligent +{ + +SampleBase* CreateSample() +{ + return new Animation00_Playback(); +} + +void Animation00_Playback::CreatePlanePSO() +{ + GraphicsPipelineStateCreateInfo PSOCreateInfo; + + // Pipeline state name is used by the engine to report issues. + // It is always a good idea to give objects descriptive names. + PSOCreateInfo.PSODesc.Name = "Plane PSO"; + + // This is a graphics pipeline + PSOCreateInfo.PSODesc.PipelineType = PIPELINE_TYPE_GRAPHICS; + + // clang-format off + // This tutorial renders to a single render target + PSOCreateInfo.GraphicsPipeline.NumRenderTargets = 1; + // Set render target format which is the format of the swap chain's color buffer + PSOCreateInfo.GraphicsPipeline.RTVFormats[0] = m_pSwapChain->GetDesc().ColorBufferFormat; + // Set depth buffer format which is the format of the swap chain's back buffer + PSOCreateInfo.GraphicsPipeline.DSVFormat = m_pSwapChain->GetDesc().DepthBufferFormat; + // Primitive topology defines what kind of primitives will be rendered by this pipeline state + PSOCreateInfo.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + // No cull + PSOCreateInfo.GraphicsPipeline.RasterizerDesc.CullMode = CULL_MODE_NONE; + // Enable depth testing + PSOCreateInfo.GraphicsPipeline.DepthStencilDesc.DepthEnable = True; + // clang-format on + + ShaderCreateInfo ShaderCI; + // Tell the system that the shader source code is in HLSL. + // For OpenGL, the engine will convert this into GLSL under the hood. + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; + + // OpenGL backend requires emulated combined HLSL texture samplers (g_Texture + g_Texture_sampler combination) + ShaderCI.UseCombinedTextureSamplers = true; + + // Create a shader source stream factory to load shaders from files. + RefCntAutoPtr pShaderSourceFactory; + m_pEngineFactory->CreateDefaultShaderSourceStreamFactory(nullptr, &pShaderSourceFactory); + ShaderCI.pShaderSourceStreamFactory = pShaderSourceFactory; + // Create plane vertex shader + RefCntAutoPtr pPlaneVS; + { + ShaderCI.Desc.ShaderType = SHADER_TYPE_VERTEX; + ShaderCI.EntryPoint = "main"; + ShaderCI.Desc.Name = "Plane VS"; + ShaderCI.FilePath = "plane.vsh"; + m_pDevice->CreateShader(ShaderCI, &pPlaneVS); + } + + // Create plane pixel shader + RefCntAutoPtr pPlanePS; + { + ShaderCI.Desc.ShaderType = SHADER_TYPE_PIXEL; + ShaderCI.EntryPoint = "main"; + ShaderCI.Desc.Name = "Plane PS"; + ShaderCI.FilePath = "plane.psh"; + m_pDevice->CreateShader(ShaderCI, &pPlanePS); + } + + PSOCreateInfo.pVS = pPlaneVS; + PSOCreateInfo.pPS = pPlanePS; + + // Define variable type that will be used by default + PSOCreateInfo.PSODesc.ResourceLayout.DefaultVariableType = SHADER_RESOURCE_VARIABLE_TYPE_STATIC; + + m_pDevice->CreateGraphicsPipelineState(PSOCreateInfo, &m_pPlanePSO); + + // Since we did not explcitly specify the type for 'Constants' variable, default + // type (SHADER_RESOURCE_VARIABLE_TYPE_STATIC) will be used. Static variables never + // change and are bound directly through the pipeline state object. + m_pPlanePSO->GetStaticVariableByName(SHADER_TYPE_VERTEX, "Constants")->Set(m_VSConstants); + + // Create a shader resource binding object and bind all static resources in it + m_pPlanePSO->CreateShaderResourceBinding(&m_pPlaneSRB, true); +} + +void Animation00_Playback::RenderPlane() +{ + + m_pImmediateContext->SetPipelineState(m_pPlanePSO); + // Commit shader resources. RESOURCE_STATE_TRANSITION_MODE_TRANSITION mode + // makes sure that resources are transitioned to required states. + // Note that Vulkan requires shadow map to be transitioned to DEPTH_READ state, not SHADER_RESOURCE + m_pImmediateContext->CommitShaderResources(m_pPlaneSRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + DrawAttribs DrawAttrs(4, DRAW_FLAG_VERIFY_ALL); + m_pImmediateContext->Draw(DrawAttrs); +} + +bool Animation00_Playback::InitAnimation() +{ + // Reading skeleton. + if (!LoadSkeleton("pab_skeleton.ozz", &m_Skeleton)) + { + return false; + } + + // Reading animation. + if (!LoadAnimation("pab_crossarms.ozz", &m_Animation)) + { + return false; + } + + // Skeleton and animation needs to match. + if (m_Skeleton.num_joints() != m_Animation.num_tracks()) + { + return false; + } + + // Allocates runtime buffers. + const int NumSoaJoints = m_Skeleton.num_soa_joints(); + m_Locals.resize(NumSoaJoints); + const int NumJoints = m_Skeleton.num_joints(); + m_Models.resize(NumJoints); + + // Allocates a cache that matches animation requirements. + m_Cache.Resize(NumJoints); + + // Allocates instance buffers. + const int NumInstance = NumJoints - 1; + m_Bones.resize(NumInstance); + m_Joints.resize(NumInstance); + + + return true; +} + +void Animation00_Playback::Initialize(const SampleInitInfo& InitInfo) +{ + SampleBase::Initialize(InitInfo); + + //Initialize Animation + InitAnimation(); + + std::vector Barriers; + // Create dynamic uniform buffer that will store our transformation matrices + // Dynamic buffers can be frequently updated by the CPU + CreateUniformBuffer(m_pDevice, sizeof(float4x4), "VS constants CB", &m_VSConstants); + Barriers.emplace_back(m_VSConstants, RESOURCE_STATE_UNKNOWN, RESOURCE_STATE_CONSTANT_BUFFER, true); + + CreateSkeletonPSO(); + m_JointVertexBuffer = CreateJointVertexBuffer(m_pDevice); + m_BoneVertexBuffer = CreateBoneVertexBuffer(m_pDevice); + CreateInstanceBuffer(); + + CreatePlanePSO(); +} + +// Render a frame +void Animation00_Playback::Render() +{ + auto* pRTV = m_pSwapChain->GetCurrentBackBufferRTV(); + auto* pDSV = m_pSwapChain->GetDepthBufferDSV(); + // Clear the back buffer + const float ClearColor[] = {0.350f, 0.350f, 0.350f, 1.0f}; + m_pImmediateContext->ClearRenderTarget(pRTV, ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + m_pImmediateContext->ClearDepthStencil(pDSV, CLEAR_DEPTH_FLAG, 1.f, 0, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + { + struct Constants + { + float4x4 WorldViewProj; + }; + MapHelper CBConstants(m_pImmediateContext, m_VSConstants, MAP_WRITE, MAP_FLAG_DISCARD); + CBConstants->WorldViewProj = m_WorldViewProjMatrix.Transpose(); + } + + RenderPlane(); + + RenderSkeleton(); +} + +void Animation00_Playback::Update(double CurrTime, double ElapsedTime) +{ + SampleBase::Update(CurrTime, ElapsedTime); + + m_PlaybackController.UpdateUI(m_Animation); + m_PlaybackController.Update(m_Animation, (float)ElapsedTime); + + // Samples optimized animation at t = animation_time_. + ozz::animation::SamplingJob Sampling; + Sampling.animation = &m_Animation; + Sampling.cache = &m_Cache; + Sampling.ratio = m_PlaybackController.TimeRatio(); + Sampling.output = make_span(m_Locals); + if (!Sampling.Run()) + { + LOG_ERROR_MESSAGE("Animation sampling job failed"); + } + + // Converts from local space to model space matrices. + ozz::animation::LocalToModelJob LocalToModel; + LocalToModel.skeleton = &m_Skeleton; + LocalToModel.input = make_span(m_Locals); + LocalToModel.output = make_span(m_Models); + if (!LocalToModel.Run()) + { + LOG_ERROR_MESSAGE("Animation local to model job failed"); + } + + // Rebuild matrices for render + FillInstanceBuffer(m_Skeleton, make_span(m_Models), make_span(m_Joints), make_span(m_Bones)); + // Update instance data buffer + Uint32 DataSize = static_cast(sizeof(m_Joints[0]) * m_Joints.size()); + m_pImmediateContext->UpdateBuffer(m_JointInstanceBuffer, 0, DataSize, m_Joints.data(), RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + DataSize = static_cast(sizeof(m_Bones[0]) * m_Joints.size()); + m_pImmediateContext->UpdateBuffer(m_BoneInstanceBuffer, 0, DataSize, m_Bones.data(), RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + float4x4 CameraView = float4x4::RotationY(PI_F) * float4x4::RotationX(PI_F * -0.2) * float4x4::Translation(0.f, -1.0f, 5.0f); + + // Get pretransform matrix that rotates the scene according the surface orientation + auto SrfPreTransform = GetSurfacePretransformMatrix(float3{0, 0, 1}); + + // Get projection matrix adjusted to the current screen orientation + auto Proj = GetAdjustedProjectionMatrix(PI_F / 4.0f, 0.1f, 100.f); + + // Compute camera view-projection matrix + m_WorldViewProjMatrix = CameraView * SrfPreTransform * Proj; +} + +void Animation00_Playback::CreateSkeletonPSO() +{ + // Pipeline state object encompasses configuration of all GPU stages + + GraphicsPipelineStateCreateInfo PSOCreateInfo; + + // Pipeline state name is used by the engine to report issues. + // It is always a good idea to give objects descriptive names. + PSOCreateInfo.PSODesc.Name = "Bone PSO"; + + // This is a graphics pipeline + PSOCreateInfo.PSODesc.PipelineType = PIPELINE_TYPE_GRAPHICS; + + // clang-format off + // This tutorial will render to a single render target + PSOCreateInfo.GraphicsPipeline.NumRenderTargets = 1; + // Set render target format which is the format of the swap chain's color buffer + PSOCreateInfo.GraphicsPipeline.RTVFormats[0] = m_pSwapChain->GetDesc().ColorBufferFormat; + // Set depth buffer format which is the format of the swap chain's back buffer + PSOCreateInfo.GraphicsPipeline.DSVFormat = m_pSwapChain->GetDesc().DepthBufferFormat; + // Primitive topology defines what kind of primitives will be rendered by this pipeline state + PSOCreateInfo.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + // Cull back faces + PSOCreateInfo.GraphicsPipeline.RasterizerDesc.CullMode = CULL_MODE_BACK; + // Enable depth testing + PSOCreateInfo.GraphicsPipeline.DepthStencilDesc.DepthEnable = True; + // clang-format on + + ShaderCreateInfo ShaderCI; + // Tell the system that the shader source code is in HLSL. + // For OpenGL, the engine will convert this into GLSL under the hood. + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; + + // OpenGL backend requires emulated combined HLSL texture samplers (g_Texture + g_Texture_sampler combination) + ShaderCI.UseCombinedTextureSamplers = true; + + // In this tutorial, we will load shaders from file. To be able to do that, + // we need to create a shader source stream factory + RefCntAutoPtr pShaderSourceFactory; + m_pEngineFactory->CreateDefaultShaderSourceStreamFactory(nullptr, &pShaderSourceFactory); + ShaderCI.pShaderSourceStreamFactory = pShaderSourceFactory; + // Create a vertex shader + RefCntAutoPtr pVS; + { + ShaderCI.Desc.ShaderType = SHADER_TYPE_VERTEX; + ShaderCI.EntryPoint = "main"; + ShaderCI.Desc.Name = "Skeleton VS"; + ShaderCI.FilePath = "skeleton.vsh"; + m_pDevice->CreateShader(ShaderCI, &pVS); + } + + // Create a pixel shader + RefCntAutoPtr pPS; + { + ShaderCI.Desc.ShaderType = SHADER_TYPE_PIXEL; + ShaderCI.EntryPoint = "main"; + ShaderCI.Desc.Name = "Skeleton PS"; + ShaderCI.FilePath = "skeleton.psh"; + m_pDevice->CreateShader(ShaderCI, &pPS); + } + + // clang-format off + // Define vertex shader input layout + // This tutorial uses two types of input: per-vertex data and per-instance data. + LayoutElement LayoutElems[] = + { + // Per-vertex data - first buffer slot + // Attribute 0 - vertex position + LayoutElement{0, 0, 3, VT_FLOAT32, False}, + // Attribute 0 - vertex normal + LayoutElement{1, 0, 3, VT_FLOAT32, False}, + // Attribute 1 - vertex color + LayoutElement{2, 0, 4, VT_FLOAT32, False}, + + // Per-instance data - second buffer slot + // We will use four attributes to encode instance-specific 4x4 transformation matrix + // Attribute 2 - first row + LayoutElement{3, 1, 4, VT_FLOAT32, False, INPUT_ELEMENT_FREQUENCY_PER_INSTANCE}, + // Attribute 3 - second row + LayoutElement{4, 1, 4, VT_FLOAT32, False, INPUT_ELEMENT_FREQUENCY_PER_INSTANCE}, + // Attribute 4 - third row + LayoutElement{5, 1, 4, VT_FLOAT32, False, INPUT_ELEMENT_FREQUENCY_PER_INSTANCE}, + // Attribute 5 - fourth row + LayoutElement{6, 1, 4, VT_FLOAT32, False, INPUT_ELEMENT_FREQUENCY_PER_INSTANCE} + }; + // clang-format on + PSOCreateInfo.GraphicsPipeline.InputLayout.LayoutElements = LayoutElems; + PSOCreateInfo.GraphicsPipeline.InputLayout.NumElements = _countof(LayoutElems); + + PSOCreateInfo.pVS = pVS; + PSOCreateInfo.pPS = pPS; + + // Define variable type that will be used by default + PSOCreateInfo.PSODesc.ResourceLayout.DefaultVariableType = SHADER_RESOURCE_VARIABLE_TYPE_STATIC; + + m_pDevice->CreateGraphicsPipelineState(PSOCreateInfo, &m_pBonePSO); + + // Since we did not explcitly specify the type for 'Constants' variable, default + // type (SHADER_RESOURCE_VARIABLE_TYPE_STATIC) will be used. Static variables never + // change and are bound directly through the pipeline state object. + m_pBonePSO->GetStaticVariableByName(SHADER_TYPE_VERTEX, "Constants")->Set(m_VSConstants); + + // Create a shader resource binding object and bind all static resources in it + m_pBonePSO->CreateShaderResourceBinding(&m_pSkeletonSRB, true); + + //Joint use line strip + PSOCreateInfo.PSODesc.Name = "Joint PSO"; + PSOCreateInfo.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_LINE_STRIP; + m_pDevice->CreateGraphicsPipelineState(PSOCreateInfo, &m_pJointPSO); + m_pJointPSO->GetStaticVariableByName(SHADER_TYPE_VERTEX, "Constants")->Set(m_VSConstants); + m_pJointPSO->CreateShaderResourceBinding(&m_pSkeletonSRB, true); + +} + +void Animation00_Playback::RenderSkeleton() +{ + // Render Joints + // Set the pipeline state + m_pImmediateContext->SetPipelineState(m_pJointPSO); + // Commit shader resources. RESOURCE_STATE_TRANSITION_MODE_TRANSITION mode + // makes sure that resources are transitioned to required states. + m_pImmediateContext->CommitShaderResources(m_pSkeletonSRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + // Bind vertex, instance and index buffers + Uint32 offsets[] = {0, 0}; + IBuffer* pJointBuffs[] = {m_JointVertexBuffer, m_JointInstanceBuffer}; + m_pImmediateContext->SetVertexBuffers(0, _countof(pJointBuffs), pJointBuffs, offsets, RESOURCE_STATE_TRANSITION_MODE_TRANSITION, SET_VERTEX_BUFFERS_FLAG_RESET); + + DrawAttribs DrawAttrs; + DrawAttrs.NumVertices = 68; + DrawAttrs.NumInstances = m_Skeleton.num_joints() - 1; + // Verify the state of vertex buffers + DrawAttrs.Flags = DRAW_FLAG_VERIFY_ALL; + m_pImmediateContext->Draw(DrawAttrs); + + // Render Bones + m_pImmediateContext->SetPipelineState(m_pBonePSO); + // Bind vertex, instance and index buffers + IBuffer* pBoneBuffs[] = {m_BoneVertexBuffer, m_BoneInstanceBuffer}; + m_pImmediateContext->SetVertexBuffers(0, _countof(pBoneBuffs), pBoneBuffs, offsets, RESOURCE_STATE_TRANSITION_MODE_TRANSITION, SET_VERTEX_BUFFERS_FLAG_RESET); + + DrawAttrs.NumVertices = 24; + m_pImmediateContext->Draw(DrawAttrs); +} + +void Animation00_Playback::CreateInstanceBuffer() +{ + int NumInstance = m_Skeleton.num_joints() - 1; + + // Create instance data buffer that will store transformation matrices + BufferDesc InstBuffDesc; + InstBuffDesc.Name = "Joint Instance data buffer"; + // Use default usage as this buffer will only be updated when grid size changes + InstBuffDesc.Usage = USAGE_DEFAULT; + InstBuffDesc.BindFlags = BIND_VERTEX_BUFFER; + InstBuffDesc.uiSizeInBytes = sizeof(float4x4) * NumInstance; + m_pDevice->CreateBuffer(InstBuffDesc, nullptr, &m_JointInstanceBuffer); + + InstBuffDesc.Name = "Bone Instance data buffer"; + m_pDevice->CreateBuffer(InstBuffDesc, nullptr, &m_BoneInstanceBuffer); +} + +} // namespace Diligent diff --git a/Samples/Animation/Animation00_Playback/src/Animation00_Playback.hpp b/Samples/Animation/Animation00_Playback/src/Animation00_Playback.hpp new file mode 100644 index 00000000..a29e676d --- /dev/null +++ b/Samples/Animation/Animation00_Playback/src/Animation00_Playback.hpp @@ -0,0 +1,100 @@ +/* + * Copyright 2019-2021 Diligent Graphics LLC + * Copyright 2015-2019 Egor Yusov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#pragma once + +#include "SampleBase.hpp" +#include "BasicMath.hpp" + +#include "ozz/animation/runtime/animation.h" +#include "ozz/animation/runtime/skeleton.h" +#include "ozz/animation/runtime/sampling_job.h" +#include "ozz/base/maths/simd_math.h" +#include "ozz/base/maths/soa_transform.h" +#include "ozz/base/containers/vector.h" + +#include "../../Common/src/PlaybackController.hpp" + +namespace Diligent +{ + +class Animation00_Playback final : public SampleBase +{ +public: + virtual void Initialize(const SampleInitInfo& InitInfo) override final; + + virtual void Render() override final; + virtual void Update(double CurrTime, double ElapsedTime) override final; + + virtual const Char* GetSampleName() const override final { return "Animation00: Playback"; } + +private: + bool InitAnimation(); + + void CreateSkeletonPSO(); + void RenderSkeleton(); + + void CreateInstanceBuffer(); + + void CreatePlanePSO(); + void RenderPlane(); + + RefCntAutoPtr m_pPlanePSO; + RefCntAutoPtr m_pJointPSO; + RefCntAutoPtr m_pBonePSO; + + RefCntAutoPtr m_pPlaneSRB; + RefCntAutoPtr m_pSkeletonSRB; + + RefCntAutoPtr m_BoneVertexBuffer; + RefCntAutoPtr m_BoneInstanceBuffer; + RefCntAutoPtr m_JointVertexBuffer; + RefCntAutoPtr m_JointInstanceBuffer; + + RefCntAutoPtr m_VSConstants; + float4x4 m_WorldViewProjMatrix; + + // Runtime skeleton. + ozz::animation::Skeleton m_Skeleton; + // Runtime animation. + ozz::animation::Animation m_Animation; + // Sampling cache. + ozz::animation::SamplingCache m_Cache; + // Buffer of local transforms as sampled from animation_. + ozz::vector m_Locals; + // Buffer of model space matrices. + ozz::vector m_Models; + + // Buffer of bone matrices. + ozz::vector m_Bones; + // Buffer of joint matrices. + ozz::vector m_Joints; + + PlaybackController m_PlaybackController; +}; + +} // namespace Diligent diff --git a/Samples/Animation/CMakeLists.txt b/Samples/Animation/CMakeLists.txt new file mode 100644 index 00000000..860b9d56 --- /dev/null +++ b/Samples/Animation/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required (VERSION 3.6) + +add_subdirectory(Animation00_Playback) \ No newline at end of file diff --git a/Samples/Animation/Common/src/AnimationUtilities.cpp b/Samples/Animation/Common/src/AnimationUtilities.cpp new file mode 100644 index 00000000..963e87ba --- /dev/null +++ b/Samples/Animation/Common/src/AnimationUtilities.cpp @@ -0,0 +1,281 @@ +/* + * Copyright 2019-2021 Diligent Graphics LLC + * Copyright 2015-2019 Egor Yusov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#include "AnimationUtilities.hpp" +#include "BasicMath.hpp" + +#include + +#include "ozz/base/io/archive.h" +#include "ozz/base/io/stream.h" +#include "ozz/base/log.h" + +using namespace ozz::math; + +namespace Diligent +{ + bool LoadSkeleton(const char* pFileName, ozz::animation::Skeleton* pSkeleton) + { + assert(pFileName && pSkeleton); + ozz::log::Out() << "Loading skeleton archive " << pFileName << "." + << std::endl; + ozz::io::File file(pFileName, "rb"); + if (!file.opened()) + { + ozz::log::Err() << "Failed to open skeleton file " << pFileName << "." + << std::endl; + return false; + } + ozz::io::IArchive archive(&file); + if (!archive.TestTag()) + { + ozz::log::Err() << "Failed to load skeleton instance from file " + << pFileName << "." << std::endl; + return false; + } + + // Once the tag is validated, reading cannot fail. + archive >> *pSkeleton; + + return true; + } + + bool LoadAnimation(const char* pFileName, ozz::animation::Animation* pAnimation) + { + assert(pFileName && pAnimation); + ozz::log::Out() << "Loading animation archive: " << pFileName << "." + << std::endl; + ozz::io::File file(pFileName, "rb"); + if (!file.opened()) + { + ozz::log::Err() << "Failed to open animation file " << pFileName << "." + << std::endl; + return false; + } + ozz::io::IArchive archive(&file); + if (!archive.TestTag()) + { + ozz::log::Err() << "Failed to load animation instance from file " + << pFileName << "." << std::endl; + return false; + } + + // Once the tag is validated, reading cannot fail. + archive >> *pAnimation; + + return true; + } + + RefCntAutoPtr CreateJointVertexBuffer(IRenderDevice* pDevice) + { + // Layout of this structure matches the one we defined in the pipeline state + struct Vertex + { + float3 pos; + float3 normal; + float4 color; + }; + + const float kInter = .2f; + const int kNumSlices = 20; + const int kNumPointsPerCircle = kNumSlices + 1; + const int kNumPointsYZ = kNumPointsPerCircle; + const int kNumPointsXY = kNumPointsPerCircle + kNumPointsPerCircle / 4; + const int kNumPointsXZ = kNumPointsPerCircle; + const int kNumPoints = kNumPointsXY + kNumPointsXZ + kNumPointsYZ; + const float kRadius = kInter; // Radius multiplier. + const float4 Red = {1.f, 0.75f, 0.75f, 1.f}; + const float4 Green = {0.75f, 1.f, 0.75f, 1.f}; + const float4 Blue = {0.75f, 0.75f, 1.f, 1.f}; + + Vertex JointVerts[kNumPoints]; + + int Index = 0; + const float k2PI = 2.f * PI_F; + // YZ plan. + for (int i = 0; i < kNumPointsYZ; ++i) + { + float Angle = i * k2PI / kNumSlices; + float SinA = sinf(Angle); + float CosA = cosf(Angle); + Vertex& NewVertex = JointVerts[Index++]; + NewVertex.pos = float3(0.f, CosA * kRadius, SinA * kRadius); + NewVertex.normal = float3(0.f, CosA, SinA); + NewVertex.color = Red; + } + + // XY plan. + for (int i = 0; i < kNumPointsXY; ++i) + { + float Angle = i * k2PI / kNumSlices; + float SinA = sinf(Angle); + float CosA = cosf(Angle); + Vertex& NewVertex = JointVerts[Index++]; + NewVertex.pos = float3(SinA * kRadius, CosA * kRadius, 0.f); + NewVertex.normal = float3(SinA, CosA, 0.f); + NewVertex.color = Blue; + } + + // XZ plan. + for (int i = 0; i < kNumPointsXZ; ++i) + { + float Angle = i * k2PI / kNumSlices; + float SinA = sinf(Angle); + float CosA = cosf(Angle); + Vertex& NewVertex = JointVerts[Index++]; + NewVertex.pos = float3(CosA * kRadius, 0.f, -SinA * kRadius); + NewVertex.normal = float3(CosA, 0.f, -SinA); + NewVertex.color = Green; + } + + BufferDesc VertBuffDesc; + VertBuffDesc.Name = "Joint vertex buffer"; + VertBuffDesc.Usage = USAGE_IMMUTABLE; + VertBuffDesc.BindFlags = BIND_VERTEX_BUFFER; + VertBuffDesc.uiSizeInBytes = sizeof(JointVerts); + BufferData VBData; + VBData.pData = JointVerts; + VBData.DataSize = sizeof(JointVerts); + RefCntAutoPtr pJointVertexBuffer; + + pDevice->CreateBuffer(VertBuffDesc, &VBData, &pJointVertexBuffer); + + return pJointVertexBuffer; + } + + RefCntAutoPtr CreateBoneVertexBuffer(IRenderDevice* pDevice) + { + // Layout of this structure matches the one we defined in the pipeline state + struct Vertex + { + float3 pos; + float3 normal; + float4 color; + }; + + const float kInter = .2f; + // clang-format off + const float3 pos[6] = + { + float3{1.f, 0.f, 0.f}, float3{kInter, .1f, .1f}, + float3{kInter, .1f, -.1f}, float3{kInter, -.1f, -.1f}, + float3{kInter, -.1f, .1f}, float3{0.f, 0.f, 0.f}, + }; + + const float3 normals[8] = + { + normalize(cross(pos[2] - pos[1], pos[2] - pos[0])), + normalize(cross(pos[1] - pos[2], pos[1] - pos[5])), + normalize(cross(pos[3] - pos[2], pos[3] - pos[0])), + normalize(cross(pos[2] - pos[3], pos[2] - pos[5])), + normalize(cross(pos[4] - pos[3], pos[4] - pos[0])), + normalize(cross(pos[3] - pos[4], pos[3] - pos[5])), + normalize(cross(pos[1] - pos[4], pos[1] - pos[0])), + normalize(cross(pos[4] - pos[1], pos[4] - pos[5])) + }; + + const float4 white = {1.f, 1.f, 1.f, 1.f}; + + Vertex BoneVerts[] = + { + {pos[0], normals[0], white}, {pos[2], normals[0], white}, + {pos[1], normals[0], white}, {pos[5], normals[1], white}, + {pos[1], normals[1], white}, {pos[2], normals[1], white}, + {pos[0], normals[2], white}, {pos[3], normals[2], white}, + {pos[2], normals[2], white}, {pos[5], normals[3], white}, + {pos[2], normals[3], white}, {pos[3], normals[3], white}, + {pos[0], normals[4], white}, {pos[4], normals[4], white}, + {pos[3], normals[4], white}, {pos[5], normals[5], white}, + {pos[3], normals[5], white}, {pos[4], normals[5], white}, + {pos[0], normals[6], white}, {pos[1], normals[6], white}, + {pos[4], normals[6], white}, {pos[5], normals[7], white}, + {pos[4], normals[7], white}, {pos[1], normals[7], white} + }; + + // clang-format on + BufferDesc VertBuffDesc; + VertBuffDesc.Name = "Bone vertex buffer"; + VertBuffDesc.Usage = USAGE_IMMUTABLE; + VertBuffDesc.BindFlags = BIND_VERTEX_BUFFER; + VertBuffDesc.uiSizeInBytes = sizeof(BoneVerts); + BufferData VBData; + VBData.pData = BoneVerts; + VBData.DataSize = sizeof(BoneVerts); + RefCntAutoPtr pBoneVertexBuffer; + + pDevice->CreateBuffer(VertBuffDesc, &VBData, &pBoneVertexBuffer); + + return pBoneVertexBuffer; + } + + void FillInstanceBuffer(const ozz::animation::Skeleton& Skeleton, + ozz::span ModelMatrices, + ozz::span JointMatrices, + ozz::span BoneMatrices) + { + const int NumJoints = Skeleton.num_joints(); + const ozz::span& Parents = Skeleton.joint_parents(); + + int Instance = 0; + for (int i = 0; i < NumJoints; ++i) + { + const int16_t ParentIdx = Parents[i]; + + // Root isn't rendered. + if (ParentIdx == ozz::animation::Skeleton::kNoParent) + { + continue; + } + + // Selects joint matrices. + const Float4x4& ParentMatr = ModelMatrices[ParentIdx]; + const Float4x4& CurrentMatr = ModelMatrices[i]; + + const SimdFloat4 BoneDir = CurrentMatr.cols[3] - ParentMatr.cols[3]; + const SimdFloat4 BoneLen = SplatX(Length3(BoneDir)); + + // Use the parent and child world matrices to create a bone world + // matrix which will place it between the two joints + // Using Gramm Schmidt process' + float Dot1 = GetX(Dot3(ParentMatr.cols[2], BoneDir)); + float Dot2 = GetX(Dot3(ParentMatr.cols[0], BoneDir)); + SimdFloat4 Binormal = std::abs(Dot1) < std::abs(Dot2) ? ParentMatr.cols[2] : ParentMatr.cols[0]; + + Float4x4& BoneMatr = BoneMatrices[Instance]; + BoneMatr.cols[0] = BoneDir; + BoneMatr.cols[1] = _mm_mul_ps(Normalize3(Cross3(Binormal, BoneDir)), BoneLen); + BoneMatr.cols[2] = _mm_mul_ps(Normalize3(Cross3(BoneDir, BoneMatr.cols[1])), BoneLen); + BoneMatr.cols[3] = ParentMatr.cols[3]; + + Float4x4& JointMatr = JointMatrices[Instance]; + JointMatr = Scale(CurrentMatr, BoneLen); + + Instance++; + } + } + +} \ No newline at end of file diff --git a/Samples/Animation/Common/src/AnimationUtilities.hpp b/Samples/Animation/Common/src/AnimationUtilities.hpp new file mode 100644 index 00000000..dadbc95c --- /dev/null +++ b/Samples/Animation/Common/src/AnimationUtilities.hpp @@ -0,0 +1,64 @@ +/* + * Copyright 2019-2021 Diligent Graphics LLC + * Copyright 2015-2019 Egor Yusov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#pragma once + +#include "ozz/animation/runtime/animation.h" +#include "ozz/animation/runtime/skeleton.h" +#include "ozz/base/maths/simd_math.h" + +#include "Buffer.h" +#include "RenderDevice.h" +#include "RefCntAutoPtr.hpp" + + +namespace Diligent +{ + // Loads a skeleton from an ozz archive file named pFileName. + // This function will fail and return false if the file cannot be opened or if + // it is not a valid ozz skeleton archive. A valid skeleton archive can be + // produced with ozz tools (fbx2ozz) or using ozz skeleton serialization API. + // pFileName and pSkeleton must be non-nullptr. + bool LoadSkeleton(const char* pFileName, ozz::animation::Skeleton* pSkeleton); + + // Loads an animation from an ozz archive file named pFileName. + // This function will fail and return false if the file cannot be opened or if + // it is not a valid ozz animation archive. A valid animation archive can be + // produced with ozz tools (fbx2ozz) or using ozz animation serialization API. + // pFileName and pAnimation must be non-nullptr. + bool LoadAnimation(const char* pFileName, ozz::animation::Animation* pAnimation); + + RefCntAutoPtr CreateJointVertexBuffer(IRenderDevice* pDevice); + + RefCntAutoPtr CreateBoneVertexBuffer(IRenderDevice* pDevice); + + void FillInstanceBuffer(const ozz::animation::Skeleton& Skeleton, + ozz::span ModelMatrices, + ozz::span JointMatrices, + ozz::span BoneMatrices); + +} // namespace Diligent diff --git a/Samples/Animation/Common/src/PlaybackController.cpp b/Samples/Animation/Common/src/PlaybackController.cpp new file mode 100644 index 00000000..29c686d6 --- /dev/null +++ b/Samples/Animation/Common/src/PlaybackController.cpp @@ -0,0 +1,92 @@ +#include "PlaybackController.hpp" +#include "BasicMath.hpp" + +namespace Diligent +{ + +PlaybackController::PlaybackController() : + m_TimeRatio(0.f), + m_PreviousTimeRatio(0.f), + m_PlaybackSpeed(1.f), + m_bPlay(true), + m_bLoop(true) +{ +} + +void PlaybackController::SetTimeRatio(float Ratio) +{ + m_PreviousTimeRatio = m_TimeRatio; + if (m_bLoop) + { + // Wraps in the unit interval [0:1], even for negative values (the reason + // for using floorf). + m_TimeRatio = Ratio - floorf(Ratio); + } + else + { + // Clamps in the unit interval [0:1]. + m_TimeRatio = clamp(Ratio, 0.f, 1.f); + } +} + +void PlaybackController::Update(const class ozz::animation::Animation& Animation, float Dt) +{ + float NewTimeRatio = m_TimeRatio; + + if (m_bPlay) + { + NewTimeRatio = m_TimeRatio + Dt * m_PlaybackSpeed / Animation.duration(); + } + + SetTimeRatio(NewTimeRatio); +} + +void PlaybackController::Reset() +{ + m_PreviousTimeRatio = m_TimeRatio = 0.f; + m_PlaybackSpeed = 1.f; + m_bPlay = true; +} + +bool PlaybackController::UpdateUI(const ozz::animation::Animation& Animation) +{ + bool time_changed = false; + + ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Playback", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + if (ImGui::Button(m_bPlay ? "Pause" : "Play")) + { + m_bPlay = !m_bPlay; + } + + ImGui::Checkbox("Loop", &m_bLoop); + + char szLabel[64]; + + // Uses a local copy of time_ so that set_time is used to actually apply + // changes. Otherwise previous time would be incorrect. + sprintf_s(szLabel, "Animation time: %.2f", m_TimeRatio * Animation.duration()); + if (ImGui::SliderFloat(szLabel, &m_TimeRatio, 0.f, 1.f)) + { + m_bPlay = false; + time_changed = true; + } + sprintf_s(szLabel, "Playback speed: %.2f", m_PlaybackSpeed); + ImGui::SliderFloat(szLabel, &m_PlaybackSpeed, -5.f, 5.f); + + // Allow to reset speed if it is not the default value. + if (ImGui::Button("Reset playback speed") && m_PlaybackSpeed != 1.f) + { + m_PlaybackSpeed = 1.f; + } + + } + ImGui::End(); + + return time_changed; +} + +} + + diff --git a/Samples/Animation/Common/src/PlaybackController.hpp b/Samples/Animation/Common/src/PlaybackController.hpp new file mode 100644 index 00000000..b4f8853a --- /dev/null +++ b/Samples/Animation/Common/src/PlaybackController.hpp @@ -0,0 +1,99 @@ +/* + * Copyright 2019-2021 Diligent Graphics LLC + * Copyright 2015-2019 Egor Yusov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#pragma once + +#include "ozz/animation/runtime/animation.h" +#include "imgui.h" + +namespace Diligent +{ +// Utility class that helps with controlling animation playback time. Time is +// computed every update according to the dt given by the caller, playback speed +// and "play" state. +// Internally time is stored as a ratio in unit interval [0,1], as expected by +// ozz runtime animation jobs. +// OnGui function allows to tweak controller parameters through the application +// Gui. +class PlaybackController +{ +public: + // Constructor. + PlaybackController(); + + // Sets animation current time. + void SetTimeRatio(float Ratio); + + // Gets animation current time. + float TimeRatio() const { return m_TimeRatio; } + + // Gets animation time ratio of last update. Useful when the range between + // previous and current frame needs to pe processed. + float PreviousTimeRatio() const { return m_PreviousTimeRatio; } + + // Sets playback speed. + void SetPlaybackSpeed(float Speed) { m_PlaybackSpeed = Speed; } + + // Gets playback speed. + float PlaybackSpeed() const { return m_PlaybackSpeed; } + + // Sets loop modes. If true, animation time is always clamped between 0 and 1. + void SetLoop(bool _loop) { m_bLoop = _loop; } + + // Gets loop mode. + bool Loop() const { return m_bLoop; } + + // Updates animation time if in "play" state, according to playback speed and + // given frame time _dt. + // Returns true if animation has looped during update + void Update(const ozz::animation::Animation& Animation, float Dt); + + // Resets all parameters to their default value. + void Reset(); + + // Do controller Gui. + // Returns true if animation time has been changed. + bool UpdateUI(const ozz::animation::Animation& Animation); + + private: + // Current animation time ratio, in the unit interval [0,1], where 0 is the + // beginning of the animation, 1 is the end. + float m_TimeRatio; + + // Time ratio of the previous update. + float m_PreviousTimeRatio; + + // Playback speed, can be negative in order to play the animation backward. + float m_PlaybackSpeed; + + // Animation play mode state: play/pause. + bool m_bPlay; + + // Animation loop mode. + bool m_bLoop; + }; +} // namespace name diff --git a/Samples/CMakeLists.txt b/Samples/CMakeLists.txt index e7ea14ba..33f86ae8 100644 --- a/Samples/CMakeLists.txt +++ b/Samples/CMakeLists.txt @@ -15,3 +15,7 @@ add_subdirectory(NuklearDemo) if(TARGET glfw) add_subdirectory(GLFWDemo) endif() + +if(TARGET ozz_animation) + add_subdirectory(Animation) +endif() \ No newline at end of file diff --git a/ThirdParty/CMakeLists.txt b/ThirdParty/CMakeLists.txt index a97a65bf..14fa22c7 100644 --- a/ThirdParty/CMakeLists.txt +++ b/ThirdParty/CMakeLists.txt @@ -11,4 +11,25 @@ if(PLATFORM_WIN32 OR PLATFORM_LINUX OR PLATFORM_MACOS) set(GLFW_INSTALL OFF CACHE INTERNAL "" FORCE) add_subdirectory(glfw) set_target_properties(glfw PROPERTIES FOLDER DiligentSamples/ThirdParty) + + set(ozz_build_tools OFF CACHE INTERNAL "" FORCE) + set(ozz_build_fbx OFF CACHE INTERNAL "" FORCE) + set(ozz_build_gltf OFF CACHE INTERNAL "" FORCE) + set(ozz_build_samples OFF CACHE INTERNAL "" FORCE) + set(ozz_build_howtos OFF CACHE INTERNAL "" FORCE) + set(ozz_build_tests OFF CACHE INTERNAL "" FORCE) + set(ozz_build_postfix OFF CACHE INTERNAL "" FORCE) + set(ozz_build_msvc_rt_dll ON CACHE INTERNAL "" FORCE) + add_subdirectory(ozz-animation) + set_target_properties(ozz_animation PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz) + set_target_properties(ozz_animation_offline PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz) + set_target_properties(ozz_base PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz) + set_target_properties(ozz_geometry PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz) + set_target_properties(ozz_options PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz) + set_target_properties(BUILD_FUSE_ozz_animation PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz/fuse) + set_target_properties(BUILD_FUSE_ozz_animation_offline PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz/fuse) + set_target_properties(BUILD_FUSE_ozz_base PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz/fuse) + set_target_properties(BUILD_FUSE_ozz_geometry PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz/fuse) + set_target_properties(BUILD_FUSE_ozz_options PROPERTIES FOLDER DiligentSamples/ThirdParty/ozz/fuse) + endif()