Initial commit

This commit is contained in:
Martin Felis
2021-11-11 21:22:24 +01:00
commit b78045ffe7
812 changed files with 421882 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
add_custom_command(
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
$<$<BOOL:${ozz_build_fbx}>:BUILD_DATA_SAMPLE>
"${CMAKE_CURRENT_LIST_DIR}/README.md"
"${ozz_media_directory}/bin/pab_skeleton.ozz"
"${ozz_media_directory}/bin/pab_walk.ozz"
"${ozz_media_directory}/bin/pab_crackhead_additive.ozz"
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_base.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_additive.ozz"
COMMAND ${CMAKE_COMMAND} -E make_directory media
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_walk.ozz" "./media/animation_base.ozz"
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_crackhead_additive.ozz" "./media/animation_additive.ozz"
VERBATIM)
add_executable(sample_additive
sample_additive.cc
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_base.ozz"
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_additive.ozz")
target_link_libraries(sample_additive
sample_framework)
set_target_properties(sample_additive
PROPERTIES FOLDER "samples")
if(EMSCRIPTEN)
# Resource files are embedded to the output file with emscripten
set_target_properties(sample_additive
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/sample_additive.html
${CMAKE_CURRENT_BINARY_DIR}/sample_additive.js
${CMAKE_CURRENT_BINARY_DIR}/sample_additive.wasm
DESTINATION bin/samples/additive)
else()
install(TARGETS sample_additive DESTINATION bin/samples/additive)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/additive)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/additive)
endif(EMSCRIPTEN)
add_test(NAME sample_additive COMMAND sample_additive "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_additive_path COMMAND sample_additive "--skeleton=media/skeleton.ozz" "--animation=media/animation_base.ozz" "--additive_animation=media/animation_additive.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
add_test(NAME sample_additive_invalid_skeleton_path COMMAND sample_additive "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
set_tests_properties(sample_additive_invalid_skeleton_path PROPERTIES WILL_FAIL true)
add_test(NAME sample_additive_invalid_animation_path1 COMMAND sample_additive "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
set_tests_properties(sample_additive_invalid_animation_path1 PROPERTIES WILL_FAIL true)
add_test(NAME sample_additive_invalid_animation_path2 COMMAND sample_additive "--additive_animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
set_tests_properties(sample_additive_invalid_animation_path2 PROPERTIES WILL_FAIL true)
+33
View File
@@ -0,0 +1,33 @@
# Ozz-animation sample: Additive animation blending
## Description
Additive blending is a key concept in run-time animation. By superimposing a movement on top of a playing animation, it allows to add variety while lessening animation count and complexity.
In this sample, a "cracking head" animation is added to a "walk" cycle. Note that no synchronization is required between the two animations.
## Concept
Additive blending is different to normal blending because it does not interpolate from a pose to an other. Instead it combines poses, meaning you can see the two animations at the same time. In this example the walk cycle is never altered. The cracking head and shoulder movements from the additive animation are simply (visually speaking) added.
Additive blending is performed by ozz::animation::BlendingJob. ozz::animation::BlendingJob exposes additive layers with the same input as normal blending layers: per joint local transforms, a global layer weight and optional per-joint weights. The main differences are that additive blending is done at the end of the normal blending pass, with a different equation.
The additive (or delta) animation is created by subtracting a reference pose from a source animation. ozz proposes a ozz::animation::offline::AdditiveAnimationBuilder utility to build additive animations. It uses the first frame of the animation as the reference pose. fbx2ozz supports additive configuration option to export delta animations from a source file.
Additionally, the sample uses an optional mask to blend the additive animation on the character upper body only. See partial blending sample for more details.
## Sample usage
The sample proposes to modify main and additive layers weights:
- Normal blending layer weight.
- Additive blending layer weight.
- Upper body masking activation and base joint selection.
Both main and additive animation playback parameters are exposed.
## Implementation
1. Loads main, additive animations, and their skeleton. See "playback" sample for more details.
2. Samples each animation to get local-space transformations. Sampling an additive animation is not different from a standard one.
3. Setups ozz::animation::BlendingJob object. BlendingJob object takes as input two arrays of BlendingJob::Layer, one for standard blending, the other for additive blending. Each layer is setup with its weights and the local-space transforms outputted from the sampling stage.
4. Convert local-space transformations to model-space matrices using ozz::animation::LocalToModelJob. It takes as input the skeleton (to know about joint's hierarchy) and local-space transforms outputted from the blending pass. Output is model-space matrices array.
5. Model-space matrices array is then used for rendering skeleton posture.
@@ -0,0 +1,382 @@
//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include <cstring>
#include "framework/application.h"
#include "framework/imgui.h"
#include "framework/renderer.h"
#include "framework/utils.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/blending_job.h"
#include "ozz/animation/runtime/local_to_model_job.h"
#include "ozz/animation/runtime/sampling_job.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/animation/runtime/skeleton_utils.h"
#include "ozz/base/containers/vector.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/simd_math.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/maths/vec_float.h"
#include "ozz/options/options.h"
// Skeleton archive can be specified as an option.
OZZ_OPTIONS_DECLARE_STRING(skeleton,
"Path to the skeleton (ozz archive format).",
"media/skeleton.ozz", false)
// MAin animation archive can be specified as an option.
OZZ_OPTIONS_DECLARE_STRING(animation,
"Path to the main animation(ozz archive format).",
"media/animation_base.ozz", false)
// Additive animation archive can be specified as an option.
OZZ_OPTIONS_DECLARE_STRING(
additive_animation, "Path to the additive animation (ozz archive format).",
"media/animation_additive.ozz", false)
class AdditiveBlendSampleApplication : public ozz::sample::Application {
public:
AdditiveBlendSampleApplication()
: upper_body_root_(0),
upper_body_mask_enable_(true),
upper_body_joint_weight_setting_(1.f),
threshold_(ozz::animation::BlendingJob().threshold) {}
protected:
// Updates current animation time and skeleton pose.
virtual bool OnUpdate(float _dt, float) {
// Updates and samples both animations to their respective local space
// transform buffers.
for (int i = 0; i < kNumLayers; ++i) {
Sampler& sampler = samplers_[i];
// Updates animations time.
sampler.controller.Update(sampler.animation, _dt);
// Setup sampling job.
ozz::animation::SamplingJob sampling_job;
sampling_job.animation = &sampler.animation;
sampling_job.cache = &sampler.cache;
sampling_job.ratio = sampler.controller.time_ratio();
sampling_job.output = make_span(sampler.locals);
// Samples animation.
if (!sampling_job.Run()) {
return false;
}
}
// Blends animations.
// Blends the local spaces transforms computed by sampling all animations
// (1st stage just above), and outputs the result to the local space
// transform buffer blended_locals_
// Prepares standard blending layers.
ozz::animation::BlendingJob::Layer layers[1];
layers[0].transform = make_span(samplers_[kMainAnimation].locals);
layers[0].weight = samplers_[kMainAnimation].weight_setting;
// Prepares additive blending layers.
ozz::animation::BlendingJob::Layer additive_layers[1];
additive_layers[0].transform =
make_span(samplers_[kAdditiveAnimation].locals);
additive_layers[0].weight = samplers_[kAdditiveAnimation].weight_setting;
// Set per-joint weights for the additive blended layer.
if (upper_body_mask_enable_) {
additive_layers[0].joint_weights = make_span(upper_body_joint_weights_);
}
// Setups blending job.
ozz::animation::BlendingJob blend_job;
blend_job.threshold = threshold_;
blend_job.layers = layers;
blend_job.additive_layers = additive_layers;
blend_job.bind_pose = skeleton_.joint_bind_poses();
blend_job.output = make_span(blended_locals_);
// Blends.
if (!blend_job.Run()) {
return false;
}
// Gets the output of the blending stage, and converts it to model space.
// Setup local-to-model conversion job.
ozz::animation::LocalToModelJob ltm_job;
ltm_job.skeleton = &skeleton_;
ltm_job.input = make_span(blended_locals_);
ltm_job.output = make_span(models_);
// Run ltm job.
if (!ltm_job.Run()) {
return false;
}
return true;
}
// Samples animation, transforms to model space and renders.
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
return _renderer->DrawPosture(skeleton_, make_span(models_),
ozz::math::Float4x4::identity());
}
virtual bool OnInitialize() {
// Reading skeleton.
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
return false;
}
const int num_soa_joints = skeleton_.num_soa_joints();
const int num_joints = skeleton_.num_joints();
// Reading animations.
const char* filenames[] = {OPTIONS_animation, OPTIONS_additive_animation};
for (int i = 0; i < kNumLayers; ++i) {
Sampler& sampler = samplers_[i];
if (!ozz::sample::LoadAnimation(filenames[i], &sampler.animation)) {
return false;
}
// Allocates sampler runtime buffers.
sampler.locals.resize(num_soa_joints);
// Allocates a cache that matches animation requirements.
sampler.cache.Resize(num_joints);
}
// Default weight settings.
samplers_[kMainAnimation].weight_setting = 1.f;
upper_body_joint_weight_setting_ = 1.f;
samplers_[kAdditiveAnimation].weight_setting = 1.f;
// Allocates local space runtime buffers of blended data.
blended_locals_.resize(num_soa_joints);
// Allocates model space runtime buffers of blended data.
models_.resize(num_joints);
// Allocates per-joint weights used for the partial additive animation.
// Note that this is a Soa structure.
upper_body_joint_weights_.resize(num_soa_joints);
// Finds the "Spine1" joint in the joint hierarchy.
for (int i = 0; i < num_joints; ++i) {
if (std::strstr(skeleton_.joint_names()[i], "Spine1")) {
upper_body_root_ = i;
break;
}
}
SetupPerJointWeights();
return true;
}
// Helper functor used to set weights while traversing joints hierarchy.
struct WeightSetupIterator {
WeightSetupIterator(ozz::vector<ozz::math::SimdFloat4>* _weights,
float _weight_setting)
: weights(_weights), weight_setting(_weight_setting) {}
void operator()(int _joint, int) {
ozz::math::SimdFloat4& soa_weight = weights->at(_joint / 4);
soa_weight = ozz::math::SetI(
soa_weight, ozz::math::simd_float4::Load1(weight_setting),
_joint % 4);
}
ozz::vector<ozz::math::SimdFloat4>* weights;
float weight_setting;
};
void SetupPerJointWeights() {
// Setup partial animation mask. This mask is defined by a weight_setting
// assigned to each joint of the hierarchy. Joint to disable are set to a
// weight_setting of 0.f, and enabled joints are set to 1.f.
// Disables all joints: set all weights to 0.
for (int i = 0; i < skeleton_.num_soa_joints(); ++i) {
upper_body_joint_weights_[i] = ozz::math::simd_float4::zero();
}
// Extracts the list of children of the shoulder
WeightSetupIterator it(&upper_body_joint_weights_,
upper_body_joint_weight_setting_);
IterateJointsDF(skeleton_, it, upper_body_root_);
}
virtual void OnDestroy() {}
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
char label[64];
// Exposes blending parameters.
{
static bool open = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "Blending parameters", &open);
if (open) {
_im_gui->DoLabel("Main layer:");
std::sprintf(label, "Layer weight: %.2f",
samplers_[kMainAnimation].weight_setting);
_im_gui->DoSlider(label, 0.f, 1.f,
&samplers_[kMainAnimation].weight_setting, 1.f);
_im_gui->DoLabel("Additive layer:");
std::sprintf(label, "Layer weight: %.2f",
samplers_[kAdditiveAnimation].weight_setting);
_im_gui->DoSlider(label, -1.f, 1.f,
&samplers_[kAdditiveAnimation].weight_setting, 1.f);
_im_gui->DoLabel("Global settings:");
std::sprintf(label, "Threshold: %.2f", threshold_);
_im_gui->DoSlider(label, .01f, 1.f, &threshold_);
}
}
// Exposes selection of the root of the partial blending hierarchy.
{
static bool open = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "Upper body masking", &open);
if (open) {
bool rebuild_joint_weights = false;
rebuild_joint_weights |=
_im_gui->DoCheckBox("Enable mask", &upper_body_mask_enable_);
std::sprintf(label, "Joints weight: %.2f",
upper_body_joint_weight_setting_);
rebuild_joint_weights |= _im_gui->DoSlider(
label, 0.f, 1.f, &upper_body_joint_weight_setting_, 1.f,
upper_body_mask_enable_);
if (skeleton_.num_joints() != 0) {
_im_gui->DoLabel("Root of the upper body hierarchy:",
ozz::sample::ImGui::kLeft, false);
std::sprintf(label, "%s (%d)",
skeleton_.joint_names()[upper_body_root_],
upper_body_root_);
rebuild_joint_weights |= _im_gui->DoSlider(
label, 0, skeleton_.num_joints() - 1, &upper_body_root_, 1.f,
upper_body_mask_enable_);
}
// Rebuilds per-joint weights if something has changed.
if (rebuild_joint_weights) {
SetupPerJointWeights();
}
}
}
// Exposes animations runtime playback controls.
{
static bool oc_open = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &oc_open);
if (oc_open) {
static bool open[kNumLayers] = {true, true};
const char* oc_names[kNumLayers] = {"Main animation",
"Additive animation"};
for (int i = 0; i < kNumLayers; ++i) {
Sampler& sampler = samplers_[i];
ozz::sample::ImGui::OpenClose loc(_im_gui, oc_names[i], nullptr);
if (open[i]) {
sampler.controller.OnGui(sampler.animation, _im_gui);
}
}
}
}
return true;
}
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
}
private:
// Runtime skeleton.
ozz::animation::Skeleton skeleton_;
// The number of layers to blend.
enum {
kMainAnimation = 0,
kAdditiveAnimation = 1,
kNumLayers = 2,
};
// Sampler structure contains all the data required to sample a single
// animation.
struct Sampler {
// Constructor, default initialization.
Sampler() : weight_setting(1.f) {}
// Playback animation controller. This is a utility class that helps with
// controlling animation playback time.
ozz::sample::PlaybackController controller;
// Blending weight_setting for the layer.
float weight_setting;
// Runtime animation.
ozz::animation::Animation animation;
// Sampling cache.
ozz::animation::SamplingCache cache;
// Buffer of local transforms as sampled from animation_.
ozz::vector<ozz::math::SoaTransform> locals;
} samplers_[kNumLayers]; // kNumLayers animations to blend.
// Index of the joint at the base of the upper body hierarchy.
int upper_body_root_;
// Enables upper boddy per-joint weights.
bool upper_body_mask_enable_;
// Blending weight_setting setting of the joints of this layer that are
// affected
// by the masking.
float upper_body_joint_weight_setting_;
// Per-joint weights used to define the partial animation mask. Allows to
// select which joints are considered during blending, and their individual
// weight_setting.
ozz::vector<ozz::math::SimdFloat4> upper_body_joint_weights_;
// Blending job bind pose threshold.
float threshold_;
// Buffer of local transforms which stores the blending result.
ozz::vector<ozz::math::SoaTransform> blended_locals_;
// Buffer of model space matrices. These are computed by the local-to-model
// job after the blending stage.
ozz::vector<ozz::math::Float4x4> models_;
};
int main(int _argc, const char** _argv) {
const char* title = "Ozz-animation sample: Additive animations blending";
return AdditiveBlendSampleApplication().Run(_argc, _argv, "1.0", title);
}