From 5c47eda8be3c243a4d9176744e18223d30286fb7 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 7 Oct 2018 21:25:19 +0900 Subject: [PATCH] Implement some animation. --- examples/skinning/README.md | 4 +- examples/skinning/main.cc | 95 +++++++++++++++-------- examples/skinning/skinning.cc | 140 +++++++++++++++++++++++++++++----- examples/skinning/skinning.h | 33 ++++++++ 4 files changed, 220 insertions(+), 52 deletions(-) diff --git a/examples/skinning/README.md b/examples/skinning/README.md index cd14579..6bd7c07 100644 --- a/examples/skinning/README.md +++ b/examples/skinning/README.md @@ -2,7 +2,9 @@ Example use CPU implementation of skinning for the explanation of how to process skin property in glTF format. -OpenGL is used to display transformed vertex. +Animation and skinning code is based on SacchaWillems' Vulkan-glTF-PBR: https://github.com/SaschaWillems/Vulkan-glTF-PBR + +OpenGL is still used to display renderings. ## Build on Linux and macOS diff --git a/examples/skinning/main.cc b/examples/skinning/main.cc index 1cb0133..0fd5896 100644 --- a/examples/skinning/main.cc +++ b/examples/skinning/main.cc @@ -12,8 +12,8 @@ #define GLFW_INCLUDE_GLU #include -#include "../common/trackball.h" #include "../common/matrix.h" +#include "../common/trackball.h" #include "skinning.h" @@ -49,7 +49,9 @@ float eye[3], lookat[3], up[3]; GLFWwindow *window; -typedef struct { GLuint vb; } GLBufferState; +typedef struct { + GLuint vb; +} GLBufferState; typedef struct { std::vector diffuseTex; // for each primitive in mesh @@ -65,16 +67,36 @@ typedef struct { size_t count; // byte count } GLCurvesState; - -// Stores vertex position transformed by skinning +// Stores vertex position transformed by skinning typedef struct { - std::vector positions; // float4 + std::vector positions; // float4 } SkinnedMesh; typedef struct { - std::vector inverseBindMatrices; // mat44 + std::vector inverseBindMatrices; // mat44 } SkinningMatrices; +struct AnimationChannel { + enum PathType { TRANSLATION, ROTATION, SCALE }; + PathType path; + // Node *node; + uint32_t samplerIndex; +}; + +struct AnimationSampler { + enum InterpolationType { LINEAR, STEP, CUBICSPLINE }; + InterpolationType interpolation; + std::vector inputs; + std::vector outputsVec4; +}; + +struct Animation { + std::string name; + std::vector samplers; + std::vector channels; + float start = std::numeric_limits::max(); + float end = std::numeric_limits::min(); +}; std::map gBufferState; std::map gMeshState; @@ -545,7 +567,8 @@ static void DrawMesh(tinygltf::Model &model, const tinygltf::Mesh &mesh) { for (; it != itEnd; it++) { assert(it->second >= 0); const tinygltf::Accessor &accessor = model.accessors[it->second]; - const tinygltf::BufferView &bufferView = model.bufferViews[accessor.bufferView]; + const tinygltf::BufferView &bufferView = + model.bufferViews[accessor.bufferView]; if (bufferView.target == 0) { continue; @@ -571,12 +594,13 @@ static void DrawMesh(tinygltf::Model &model, const tinygltf::Mesh &mesh) { (it->first.compare("TEXCOORD_0") == 0)) { if (gGLProgramState.attribs[it->first] >= 0) { // Compute byteStride from Accessor + BufferView combination. - int byteStride = accessor.ByteStride(model.bufferViews[accessor.bufferView]); + int byteStride = + accessor.ByteStride(model.bufferViews[accessor.bufferView]); assert(byteStride != -1); glVertexAttribPointer(gGLProgramState.attribs[it->first], size, - accessor.componentType, accessor.normalized ? GL_TRUE : GL_FALSE, - byteStride, - BUFFER_OFFSET(accessor.byteOffset)); + accessor.componentType, + accessor.normalized ? GL_TRUE : GL_FALSE, + byteStride, BUFFER_OFFSET(accessor.byteOffset)); CheckErrors("vertex attrib pointer"); glEnableVertexAttribArray(gGLProgramState.attribs[it->first]); CheckErrors("enable vertex attrib array"); @@ -742,8 +766,9 @@ static void PrintNodes(const tinygltf::Scene &scene) { } } -static void ConstructNodeMatrix(const tinygltf::Node &node, example::mat4 *xform) { - +#if 0 +static void ConstructNodeMatrix(const tinygltf::Node &node, + example::mat4 *xform) { if (node.matrix.size() == 16) { for (size_t j = 0; j < 4; j++) { for (size_t i = 0; i < 4; i++) { @@ -779,45 +804,54 @@ static void ConstructNodeMatrix(const tinygltf::Node &node, example::mat4 *xform } example::BuildTransofrmMatrix(translation, rotation, scale, xform); - } // -// Hierarchically evalute skining matrix +// Hierarchically evalute skining // -static void ApplySkinningToMesh(const tinygltf::model &model, const tinygltf::Node &node, example::mat4 &parent_xform, std::vector *skinned_meshes) { - example::mat4 xform = parent_xform; +static void ApplySkinningToMesh(const tinygltf::Model &model, + const tinygltf::Node &node, + example::mat4 &parent_xform, + std::vector *skinned_meshes) { + example::mat4 node_xform; + ConstructNodeMatrix(node, &node_xform); if (node.mesh) { if (node.skin) { } } + example::mat4 xform; + Matrix::Mult(xform.m, parent_xform.m, node_xform.m); + for (auto &child : node.children) { - ApplySkinningToMesh(model.nodes[child], xform, skinned_meshes); + ApplySkinningToMesh(model, model.nodes[child], xform, skinned_meshes); } } +#endif // // Read inverseBindMatricies for each skin // -static void SetupSkinningMatrices(const tinygltf::Model &model, std::map &skinning_matrices) -{ +static void SetupSkinningMatrices( + const tinygltf::Model &model, + std::map &skinning_matrices) { for (size_t s = 0; s < model.skins.size(); s++) { const tinygltf::Skin &skin = model.skins[s]; if (skin.inverseBindMatrices > -1) { - if (skin.joints.size() > 0) { - - const tinygltf::Accessor &accessor = model.accessors[skin.inverseBindMatrices]; + const tinygltf::Accessor &accessor = + model.accessors[skin.inverseBindMatrices]; assert(accessor.type == TINYGLTF_TYPE_MAT4); - const tinygltf::BufferView &bufferView = model.bufferViews[accessor.bufferView]; + const tinygltf::BufferView &bufferView = + model.bufferViews[accessor.bufferView]; const tinygltf::Buffer &buffer = model.buffers[bufferView.buffer]; - const float *ptr = reinterpret_cast(buffer.data.data() + accessor.byteOffset + bufferView.byteOffset); + const float *ptr = reinterpret_cast( + buffer.data.data() + accessor.byteOffset + bufferView.byteOffset); std::cout << "count = " << accessor.count << std::endl; std::vector inverse_bind_matrices(accessor.count); @@ -830,14 +864,11 @@ static void SetupSkinningMatrices(const tinygltf::Model &model, std::map #include +#include namespace example { -void BuildTransofrmMatrix( - const float translate[3], - const float rotation[4], // as quaternion in glTF - const float scale[3], - mat4 *transform_matrix) { +struct Node { + float translation[3] = {0.0f, 0.0f, 0.0f}; + float scale[4] = {1.0f, 1.0f, 1.0f}; + float rotation[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + + + void update() { + } +}; + +static inline vec4 mix(vec4 x, vec4 y, float a) { + vec4 v; + v.f[0] = (1.0f - a) * x.f[0] + a * y.f[0]; + v.f[1] = (1.0f - a) * x.f[1] + a * y.f[1]; + v.f[2] = (1.0f - a) * x.f[2] + a * y.f[2]; + v.f[3] = (1.0f - a) * x.f[3] + a * y.f[3]; + + return v; +} + +void BuildTransofrmMatrix(const float translate[3], + const float rotation[4], // as quaternion in glTF + const float scale[3], mat4 *transform_matrix) { float T[4][4]; T[0][0] = 1.0f; T[0][1] = 0.0f; @@ -68,11 +98,10 @@ void BuildTransofrmMatrix( } void ComputeJointMatrices( - const std::vector global_transform_of_nodes, - const std::vector global_transform_of_joint_nodes, - const std::vector inverse_bind_matrix_for_joints, - std::vector *output_joint_matrices) { - + const std::vector global_transform_of_nodes, + const std::vector global_transform_of_joint_nodes, + const std::vector inverse_bind_matrix_for_joints, + std::vector *output_joint_matrices) { const size_t n = global_transform_of_nodes.size(); output_joint_matrices->resize(n); @@ -84,21 +113,18 @@ void ComputeJointMatrices( mat4 g_joint = global_transform_of_joint_nodes[i]; mat4 inverse_bind_matrix = inverse_bind_matrix_for_joints[i]; - float a[4][4]; // temp matrix + float a[4][4]; // temp matrix Matrix::Mult(a, g_joint.m, inverse_bind_matrix.m); Matrix::Mult((*output_joint_matrices)[i].m, g_inv.m, a); } } - void Skining(const std::vector vertices, const std::vector weights, const std::vector joints, const size_t num_skinning_weights, - const std::vector joint_matrices, - const float t, + const std::vector joint_matrices, const float t, std::vector *skinned_vertices) { - assert((vertices.size() % 4) == 0); const size_t num_vertices = vertices.size() / 4; @@ -135,7 +161,6 @@ void Skining(const std::vector vertices, M.m[j][i] = I.m[j][i] * t + (1.0f - t) * skin_mat.m[j][i]; } } - float vtx[4]; vtx[0] = vertices[4 * v + 0]; @@ -145,8 +170,83 @@ void Skining(const std::vector vertices, float ret[4]; Matrix::MultV4(ret, M.m, vtx); - } + } } +void UpdateAnimation(std::vector &animations, uint32_t index, + float time, std::vector &nodes) { + if (index > uint32_t(animations.size() - 1)) { + return; + } -} // namespace example + const Animation &animation = animations[index]; + + bool updated = false; + for (auto &channel : animation.channels) { + const AnimationSampler &sampler = animation.samplers[channel.samplerIndex]; + if (sampler.inputs.size() > sampler.outputsVec4.size()) { + continue; + } + + // TODO(LTE): support interpolation other than LINEAR + for (size_t i = 0; i < sampler.inputs.size() - 1; i++) { + if ((time >= sampler.inputs[i]) && (time <= sampler.inputs[i + 1])) { + float u = std::max(0.0f, time - sampler.inputs[i]) / + (sampler.inputs[i + 1] - sampler.inputs[i]); + if (u <= 1.0f) { + switch (channel.path) { + case AnimationChannel::PathType::TRANSLATION: { + example::vec4 trans = + mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); + channel.node->translation[0] = trans.f[0]; + channel.node->translation[1] = trans.f[1]; + channel.node->translation[2] = trans.f[2]; + // drop w + break; + } + case AnimationChannel::PathType::SCALE: { + example::vec4 scale = + mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); + channel.node->scale[0] = scale.f[0]; + channel.node->scale[1] = scale.f[1]; + channel.node->scale[2] = scale.f[2]; + break; + } + case AnimationChannel::PathType::ROTATION: { + + hmm_quaternion q1 = HMM_Quaternion( + sampler.outputsVec4[i].f[0], + sampler.outputsVec4[i].f[1], + sampler.outputsVec4[i].f[2], + sampler.outputsVec4[i].f[3]); + + hmm_quaternion q2 = HMM_Quaternion( + sampler.outputsVec4[i + 1].f[0], + sampler.outputsVec4[i + 1].f[1], + sampler.outputsVec4[i + 1].f[2], + sampler.outputsVec4[i + 1].f[3]); + + hmm_quaternion q = HMM_NormalizeQuaternion(HMM_Slerp(q1, u, q2)); + + channel.node->rotation[0] = q.Elements[0]; + channel.node->rotation[1] = q.Elements[1]; + channel.node->rotation[2] = q.Elements[2]; + channel.node->rotation[3] = q.Elements[3]; + + break; + } + } + updated = true; + } + } + } + } + + if (updated) { + for (auto &node : nodes) { + node->update(); + } + } +} + +} // namespace example diff --git a/examples/skinning/skinning.h b/examples/skinning/skinning.h index 12a3cc4..7ad4c0c 100644 --- a/examples/skinning/skinning.h +++ b/examples/skinning/skinning.h @@ -3,6 +3,9 @@ #include #include +#include +#include +#include namespace example { @@ -10,6 +13,36 @@ struct mat4 { float m[4][4]; }; +struct vec4 { + float f[4]; +}; + +// glTF node +struct Node; + + +struct AnimationChannel { + enum PathType { TRANSLATION, ROTATION, SCALE }; + PathType path; + Node *node; + uint32_t samplerIndex; +}; + +struct AnimationSampler { + enum InterpolationType { LINEAR, STEP, CUBICSPLINE }; + InterpolationType interpolation; + std::vector inputs; + std::vector outputsVec4; +}; + +struct Animation { + std::string name; + std::vector samplers; + std::vector channels; + float start = std::numeric_limits::max(); + float end = std::numeric_limits::min(); +}; + /// /// Utility function to build transformation matrix from translate/rotation/scale ///