Initial commit
This commit is contained in:
@@ -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/ruby_mesh.ozz"
|
||||
"${ozz_media_directory}/bin/ruby_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/ruby_animation.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/mesh.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.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/ruby_mesh.ozz" "./media/mesh.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/ruby_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/ruby_animation.ozz" "./media/animation.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_skinning
|
||||
sample_skinning.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/mesh.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz")
|
||||
target_link_libraries(sample_skinning
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_skinning
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_skinning
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_skinning.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_skinning.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_skinning.wasm
|
||||
DESTINATION bin/samples/skinning)
|
||||
else()
|
||||
install(TARGETS sample_skinning DESTINATION bin/samples/skinning)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/skinning)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/skinning)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_skinning COMMAND sample_skinning "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_skinning_path COMMAND sample_skinning "--skeleton=media/skeleton.ozz" "--animation=media/animation.ozz" "--mesh=media/mesh.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_skinning_invalid_skeleton_path COMMAND sample_skinning "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_skinning_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_skinning_invalid_animation_path COMMAND sample_skinning "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_skinning_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_skinning_invalid_mesh_path COMMAND sample_skinning "--mesh=media/bad_mesh.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_skinning_invalid_mesh_path PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Ozz-animation sample: Skinning
|
||||
|
||||
## Description
|
||||
|
||||
This sample adds skinning to the playback sample. It loads a skeleton, an animation and skinned meshes from ozz binary archives. It playbacks animation every frame and uses model-space matrices to build skinning matrices and render a skinned mesh.
|
||||
|
||||
## Concept
|
||||
|
||||
This sample is based on playback sample for the most part, reading and sampling an animation. In addition it loads an ozz binary mesh file which was generated with sample_fbx2mesh tool.
|
||||
At every frame, once the animation is sampled and model-space matrices computed, skinning matrices are built. Skinning matrices are the composition (multiplication) of model-space and mesh inverse bind pose matrices. This step can be seen as allowing to transform vertices into joint's local-space, so they can be transformed by this joint.
|
||||
|
||||
## Sample usage
|
||||
|
||||
Animation playback parameters can be tuned from sample UI:
|
||||
- Play/pause animation.
|
||||
- Fix animation time.
|
||||
- Set playback speed, which can be negative to go backward.
|
||||
|
||||
Rendering options are also exposed:
|
||||
- Enabling skeleton display.
|
||||
- Enabling mesh display.
|
||||
- Enabling skinning stage.
|
||||
- Display normals, tangent and binormals.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Load animation and skeleton, sample animation to get local-space transformations, and finally convert local-space transformations to model-space matrices. See Playback sample for more details about these steps.
|
||||
2. Load meshes from ozz archive. There can be multiple meshes as import utility (aka fbx2mesh) maintains dcc file meshes split.
|
||||
3. Computes and allocates skinning matrices. Number of skinning matrices might be less from the number of joints, as a mesh might be skinned by a subset of all skeleton joints only. Mesh::joint_remaps is used to know how to order skinning matrices, hence is size defines their number.
|
||||
4. Skinning matrices array is updated before rendering each mesh. A skinning matrix is the multiplication of the model-space and the mesh inverse bind pose matrix for a joint. Mesh::joint_remaps is used to index skeleton joints, so they match with the mesh.
|
||||
5. Skinning is performed by ozz::geometry::SkinningJob. Please check sample framework DrawSkinnedMesh function for more details about how to setup that job.
|
||||
@@ -0,0 +1,287 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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 <algorithm>
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/mesh.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
#include "ozz/animation/runtime/animation.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/base/log.h"
|
||||
#include "ozz/base/maths/math_ex.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)
|
||||
|
||||
// Animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the animation (ozz archive format).",
|
||||
"media/animation.ozz", false)
|
||||
|
||||
// Mesh archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(mesh,
|
||||
"Path to the skinned mesh (ozz archive format).",
|
||||
"media/mesh.ozz", false)
|
||||
|
||||
class SkinningSampleApplication : public ozz::sample::Application {
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates current animation time.
|
||||
controller_.Update(animation_, _dt);
|
||||
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &skeleton_;
|
||||
ltm_job.input = make_span(locals_);
|
||||
ltm_job.output = make_span(models_);
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
bool success = true;
|
||||
|
||||
if (draw_skeleton_) {
|
||||
success &= _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity());
|
||||
}
|
||||
|
||||
if (draw_mesh_) {
|
||||
// Builds skinning matrices, based on the output of the animation stage.
|
||||
// The mesh might not use (aka be skinned by) all skeleton joints. We use
|
||||
// the joint remapping table (available from the mesh object) to reorder
|
||||
// model-space matrices and build skinning ones.
|
||||
for (const ozz::sample::Mesh& mesh : meshes_) {
|
||||
for (size_t i = 0; i < mesh.joint_remaps.size(); ++i) {
|
||||
skinning_matrices_[i] =
|
||||
models_[mesh.joint_remaps[i]] * mesh.inverse_bind_poses[i];
|
||||
}
|
||||
|
||||
// Renders skin.
|
||||
success &= _renderer->DrawSkinnedMesh(
|
||||
mesh, make_span(skinning_matrices_),
|
||||
ozz::math::Float4x4::identity(), render_options_);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reading animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skeleton and animation needs to match.
|
||||
if (skeleton_.num_joints() != animation_.num_tracks()) {
|
||||
ozz::log::Err() << "The provided animation doesn't match skeleton "
|
||||
"(joint count mismatch)."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates runtime buffers.
|
||||
const int num_soa_joints = skeleton_.num_soa_joints();
|
||||
locals_.resize(num_soa_joints);
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
// Reading skinned meshes.
|
||||
if (!ozz::sample::LoadMeshes(OPTIONS_mesh, &meshes_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Computes the number of skinning matrices required to skin all meshes.
|
||||
// A mesh is skinned by only a subset of joints, so the number of skinning
|
||||
// matrices might be less that the number of skeleton joints.
|
||||
// Mesh::joint_remaps is used to know how to order skinning matrices. So the
|
||||
// number of matrices required is the size of joint_remaps.
|
||||
size_t num_skinning_matrices = 0;
|
||||
for (const ozz::sample::Mesh& mesh : meshes_) {
|
||||
num_skinning_matrices =
|
||||
ozz::math::Max(num_skinning_matrices, mesh.joint_remaps.size());
|
||||
}
|
||||
|
||||
// Allocates skinning matrices.
|
||||
skinning_matrices_.resize(num_skinning_matrices);
|
||||
|
||||
// Check the skeleton matches with the mesh, especially that the mesh
|
||||
// doesn't expect more joints than the skeleton has.
|
||||
for (const ozz::sample::Mesh& mesh : meshes_) {
|
||||
if (num_joints < mesh.highest_joint_index()) {
|
||||
ozz::log::Err() << "The provided mesh doesn't match skeleton "
|
||||
"(joint count mismatch)."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Exposes model informations.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Model statisitics", &open);
|
||||
if (open) {
|
||||
char label[255];
|
||||
sprintf(label, "%d animated joints", skeleton_.num_joints());
|
||||
_im_gui->DoLabel(label);
|
||||
|
||||
int influences = 0;
|
||||
for (const auto& mesh : meshes_) {
|
||||
influences = ozz::math::Max(influences, mesh.max_influences_count());
|
||||
}
|
||||
sprintf(label, "%d influences (max)", influences);
|
||||
_im_gui->DoLabel(label);
|
||||
|
||||
int vertices = 0;
|
||||
for (const auto& mesh : meshes_) {
|
||||
vertices += mesh.vertex_count();
|
||||
}
|
||||
sprintf(label, "%.1fK vertices", vertices / 1000.f);
|
||||
_im_gui->DoLabel(label);
|
||||
|
||||
int indices = 0;
|
||||
for (const auto& mesh : meshes_) {
|
||||
indices += mesh.triangle_index_count();
|
||||
}
|
||||
sprintf(label, "%.1fK triangles", indices / 3000.f);
|
||||
_im_gui->DoLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
controller_.OnGui(animation_, _im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
// Expose mesh rendering options
|
||||
{
|
||||
// Rendering options.
|
||||
static bool oc_open = false;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Rendering options", &oc_open);
|
||||
if (oc_open) {
|
||||
_im_gui->DoCheckBox("Draw skeleton", &draw_skeleton_);
|
||||
_im_gui->DoCheckBox("Draw mesh", &draw_mesh_);
|
||||
|
||||
_im_gui->DoCheckBox("Show texture", &render_options_.texture);
|
||||
_im_gui->DoCheckBox("Show normals", &render_options_.normals);
|
||||
_im_gui->DoCheckBox("Show tangents", &render_options_.tangents);
|
||||
_im_gui->DoCheckBox("Show binormals", &render_options_.binormals);
|
||||
_im_gui->DoCheckBox("Show colors", &render_options_.colors);
|
||||
_im_gui->DoCheckBox("Wireframe", &render_options_.wireframe);
|
||||
_im_gui->DoCheckBox("Skip skinning", &render_options_.skip_skinning);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputeSkeletonBounds(skeleton_, _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// Buffer of model space matrices.
|
||||
ozz::vector<ozz::math::Float4x4> models_;
|
||||
|
||||
// Buffer of skinning matrices, result of the joint multiplication of the
|
||||
// inverse bind pose with the model space matrix.
|
||||
ozz::vector<ozz::math::Float4x4> skinning_matrices_;
|
||||
|
||||
// The mesh used by the sample.
|
||||
ozz::vector<ozz::sample::Mesh> meshes_;
|
||||
|
||||
// Redering options.
|
||||
bool draw_skeleton_ = false;
|
||||
bool draw_mesh_ = true;
|
||||
|
||||
// Mesh rendering options.
|
||||
ozz::sample::Renderer::Options render_options_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Skinning";
|
||||
return SkinningSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
Reference in New Issue
Block a user