Implement some animation.

This commit is contained in:
Syoyo Fujita 2018-10-07 21:25:19 +09:00
parent 1b85cb8c59
commit 5c47eda8be
4 changed files with 220 additions and 52 deletions

View File

@ -2,7 +2,9 @@
Example use CPU implementation of skinning for the explanation of how to process skin property in glTF format. 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 ## Build on Linux and macOS

View File

@ -12,8 +12,8 @@
#define GLFW_INCLUDE_GLU #define GLFW_INCLUDE_GLU
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include "../common/trackball.h"
#include "../common/matrix.h" #include "../common/matrix.h"
#include "../common/trackball.h"
#include "skinning.h" #include "skinning.h"
@ -49,7 +49,9 @@ float eye[3], lookat[3], up[3];
GLFWwindow *window; GLFWwindow *window;
typedef struct { GLuint vb; } GLBufferState; typedef struct {
GLuint vb;
} GLBufferState;
typedef struct { typedef struct {
std::vector<GLuint> diffuseTex; // for each primitive in mesh std::vector<GLuint> diffuseTex; // for each primitive in mesh
@ -65,7 +67,6 @@ typedef struct {
size_t count; // byte count size_t count; // byte count
} GLCurvesState; } GLCurvesState;
// Stores vertex position transformed by skinning // Stores vertex position transformed by skinning
typedef struct { typedef struct {
std::vector<float> positions; // float4 std::vector<float> positions; // float4
@ -75,6 +76,27 @@ typedef struct {
std::vector<example::mat4> inverseBindMatrices; // mat44 std::vector<example::mat4> inverseBindMatrices; // mat44
} SkinningMatrices; } 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<float> inputs;
std::vector<example::vec4> outputsVec4;
};
struct Animation {
std::string name;
std::vector<AnimationSampler> samplers;
std::vector<AnimationChannel> channels;
float start = std::numeric_limits<float>::max();
float end = std::numeric_limits<float>::min();
};
std::map<int, GLBufferState> gBufferState; std::map<int, GLBufferState> gBufferState;
std::map<std::string, GLMeshState> gMeshState; std::map<std::string, GLMeshState> gMeshState;
@ -545,7 +567,8 @@ static void DrawMesh(tinygltf::Model &model, const tinygltf::Mesh &mesh) {
for (; it != itEnd; it++) { for (; it != itEnd; it++) {
assert(it->second >= 0); assert(it->second >= 0);
const tinygltf::Accessor &accessor = model.accessors[it->second]; 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) { if (bufferView.target == 0) {
continue; continue;
@ -571,12 +594,13 @@ static void DrawMesh(tinygltf::Model &model, const tinygltf::Mesh &mesh) {
(it->first.compare("TEXCOORD_0") == 0)) { (it->first.compare("TEXCOORD_0") == 0)) {
if (gGLProgramState.attribs[it->first] >= 0) { if (gGLProgramState.attribs[it->first] >= 0) {
// Compute byteStride from Accessor + BufferView combination. // 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); assert(byteStride != -1);
glVertexAttribPointer(gGLProgramState.attribs[it->first], size, glVertexAttribPointer(gGLProgramState.attribs[it->first], size,
accessor.componentType, accessor.normalized ? GL_TRUE : GL_FALSE, accessor.componentType,
byteStride, accessor.normalized ? GL_TRUE : GL_FALSE,
BUFFER_OFFSET(accessor.byteOffset)); byteStride, BUFFER_OFFSET(accessor.byteOffset));
CheckErrors("vertex attrib pointer"); CheckErrors("vertex attrib pointer");
glEnableVertexAttribArray(gGLProgramState.attribs[it->first]); glEnableVertexAttribArray(gGLProgramState.attribs[it->first]);
CheckErrors("enable vertex attrib array"); 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) { if (node.matrix.size() == 16) {
for (size_t j = 0; j < 4; j++) { for (size_t j = 0; j < 4; j++) {
for (size_t i = 0; i < 4; i++) { 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); 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<SkinnedMesh> *skinned_meshes) { static void ApplySkinningToMesh(const tinygltf::Model &model,
example::mat4 xform = parent_xform; const tinygltf::Node &node,
example::mat4 &parent_xform,
std::vector<SkinnedMesh> *skinned_meshes) {
example::mat4 node_xform;
ConstructNodeMatrix(node, &node_xform);
if (node.mesh) { if (node.mesh) {
if (node.skin) { if (node.skin) {
} }
} }
example::mat4 xform;
Matrix::Mult(xform.m, parent_xform.m, node_xform.m);
for (auto &child : node.children) { 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 // Read inverseBindMatricies for each skin
// //
static void SetupSkinningMatrices(const tinygltf::Model &model, std::map<int, SkinningMatrices> &skinning_matrices) static void SetupSkinningMatrices(
{ const tinygltf::Model &model,
std::map<int, SkinningMatrices> &skinning_matrices) {
for (size_t s = 0; s < model.skins.size(); s++) { for (size_t s = 0; s < model.skins.size(); s++) {
const tinygltf::Skin &skin = model.skins[s]; const tinygltf::Skin &skin = model.skins[s];
if (skin.inverseBindMatrices > -1) { if (skin.inverseBindMatrices > -1) {
if (skin.joints.size() > 0) { if (skin.joints.size() > 0) {
const tinygltf::Accessor &accessor =
const tinygltf::Accessor &accessor = model.accessors[skin.inverseBindMatrices]; model.accessors[skin.inverseBindMatrices];
assert(accessor.type == TINYGLTF_TYPE_MAT4); 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 tinygltf::Buffer &buffer = model.buffers[bufferView.buffer];
const float *ptr = reinterpret_cast<const float *>(buffer.data.data() + accessor.byteOffset + bufferView.byteOffset); const float *ptr = reinterpret_cast<const float *>(
buffer.data.data() + accessor.byteOffset + bufferView.byteOffset);
std::cout << "count = " << accessor.count << std::endl; std::cout << "count = " << accessor.count << std::endl;
std::vector<example::mat4> inverse_bind_matrices(accessor.count); std::vector<example::mat4> inverse_bind_matrices(accessor.count);
@ -830,14 +864,11 @@ static void SetupSkinningMatrices(const tinygltf::Model &model, std::map<int, Sk
std::cout << "j[" << j << "] = " << std::endl; std::cout << "j[" << j << "] = " << std::endl;
Matrix::Print(inverse_bind_matrices[j].m); Matrix::Print(inverse_bind_matrices[j].m);
} }
skinning_matrices[s].inverseBindMatrices = inverse_bind_matrices; skinning_matrices[s].inverseBindMatrices = inverse_bind_matrices;
} }
} }
} }
} }
@ -862,7 +893,8 @@ int main(int argc, char **argv) {
bool ret = false; bool ret = false;
if (ext.compare("glb") == 0) { if (ext.compare("glb") == 0) {
// assume binary glTF. // assume binary glTF.
ret = loader.LoadBinaryFromFile(&model, &err, &warn, input_filename.c_str()); ret =
loader.LoadBinaryFromFile(&model, &err, &warn, input_filename.c_str());
} else { } else {
// assume ascii glTF. // assume ascii glTF.
ret = loader.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str()); ret = loader.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str());
@ -889,7 +921,8 @@ int main(int argc, char **argv) {
std::cout << "defaultScene = " << model.defaultScene << std::endl; std::cout << "defaultScene = " << model.defaultScene << std::endl;
if (model.defaultScene >= int(model.scenes.size())) { if (model.defaultScene >= int(model.scenes.size())) {
std::cerr << "Invalid defualtScene value : " << model.defaultScene << std::endl; std::cerr << "Invalid defualtScene value : " << model.defaultScene
<< std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -3,17 +3,47 @@
#include "../common/matrix.h" #include "../common/matrix.h"
#include "../common/trackball.h" // for quaternion #include "../common/trackball.h" // for quaternion
#include <cstring> #ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#endif
#define HANDMADE_MATH_IMPLEMENTATION
#include "HandmadeMath.h"
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#include <cassert> #include <cassert>
#include <cstring>
namespace example { namespace example {
void BuildTransofrmMatrix( struct Node {
const float translate[3],
const float rotation[4], // as quaternion in glTF
const float scale[3],
mat4 *transform_matrix) {
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]; float T[4][4];
T[0][0] = 1.0f; T[0][0] = 1.0f;
T[0][1] = 0.0f; T[0][1] = 0.0f;
@ -72,7 +102,6 @@ void ComputeJointMatrices(
const std::vector<mat4> global_transform_of_joint_nodes, const std::vector<mat4> global_transform_of_joint_nodes,
const std::vector<mat4> inverse_bind_matrix_for_joints, const std::vector<mat4> inverse_bind_matrix_for_joints,
std::vector<mat4> *output_joint_matrices) { std::vector<mat4> *output_joint_matrices) {
const size_t n = global_transform_of_nodes.size(); const size_t n = global_transform_of_nodes.size();
output_joint_matrices->resize(n); output_joint_matrices->resize(n);
@ -91,14 +120,11 @@ void ComputeJointMatrices(
} }
} }
void Skining(const std::vector<float> vertices, void Skining(const std::vector<float> vertices,
const std::vector<float> weights, const std::vector<size_t> joints, const std::vector<float> weights, const std::vector<size_t> joints,
const size_t num_skinning_weights, const size_t num_skinning_weights,
const std::vector<mat4> joint_matrices, const std::vector<mat4> joint_matrices, const float t,
const float t,
std::vector<float> *skinned_vertices) { std::vector<float> *skinned_vertices) {
assert((vertices.size() % 4) == 0); assert((vertices.size() % 4) == 0);
const size_t num_vertices = vertices.size() / 4; const size_t num_vertices = vertices.size() / 4;
@ -136,7 +162,6 @@ void Skining(const std::vector<float> vertices,
} }
} }
float vtx[4]; float vtx[4];
vtx[0] = vertices[4 * v + 0]; vtx[0] = vertices[4 * v + 0];
vtx[1] = vertices[4 * v + 1]; vtx[1] = vertices[4 * v + 1];
@ -148,5 +173,80 @@ void Skining(const std::vector<float> vertices,
} }
} }
void UpdateAnimation(std::vector<Animation> &animations, uint32_t index,
float time, std::vector<Node*> &nodes) {
if (index > uint32_t(animations.size() - 1)) {
return;
}
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 } // namespace example

View File

@ -3,6 +3,9 @@
#include <vector> #include <vector>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <string>
#include <limits>
namespace example { namespace example {
@ -10,6 +13,36 @@ struct mat4 {
float m[4][4]; 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<float> inputs;
std::vector<example::vec4> outputsVec4;
};
struct Animation {
std::string name;
std::vector<AnimationSampler> samplers;
std::vector<AnimationChannel> channels;
float start = std::numeric_limits<float>::max();
float end = std::numeric_limits<float>::min();
};
/// ///
/// Utility function to build transformation matrix from translate/rotation/scale /// Utility function to build transformation matrix from translate/rotation/scale
/// ///