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.
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

View File

@ -12,8 +12,8 @@
#define GLFW_INCLUDE_GLU
#include <GLFW/glfw3.h>
#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<GLuint> 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<float> positions; // float4
std::vector<float> positions; // float4
} SkinnedMesh;
typedef struct {
std::vector<example::mat4> inverseBindMatrices; // mat44
std::vector<example::mat4> 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<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<std::string, GLMeshState> 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<SkinnedMesh> *skinned_meshes) {
example::mat4 xform = parent_xform;
static void ApplySkinningToMesh(const tinygltf::Model &model,
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.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<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++) {
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<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::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;
Matrix::Print(inverse_bind_matrices[j].m);
}
skinning_matrices[s].inverseBindMatrices = inverse_bind_matrices;
}
}
}
}
@ -862,7 +893,8 @@ int main(int argc, char **argv) {
bool ret = false;
if (ext.compare("glb") == 0) {
// assume binary glTF.
ret = loader.LoadBinaryFromFile(&model, &err, &warn, input_filename.c_str());
ret =
loader.LoadBinaryFromFile(&model, &err, &warn, input_filename.c_str());
} else {
// assume ascii glTF.
ret = loader.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str());
@ -886,10 +918,11 @@ int main(int argc, char **argv) {
std::cerr << "Scene is empty" << std::endl;
return EXIT_FAILURE;
}
std::cout << "defaultScene = " << model.defaultScene << std::endl;
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;
}

View File

@ -1,19 +1,49 @@
#include "skinning.h"
#include "../common/matrix.h"
#include "../common/trackball.h" // for quaternion
#include "../common/trackball.h" // for quaternion
#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 <cstring>
#include <cassert>
#include <cstring>
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<mat4> global_transform_of_nodes,
const std::vector<mat4> global_transform_of_joint_nodes,
const std::vector<mat4> inverse_bind_matrix_for_joints,
std::vector<mat4> *output_joint_matrices) {
const std::vector<mat4> global_transform_of_nodes,
const std::vector<mat4> global_transform_of_joint_nodes,
const std::vector<mat4> inverse_bind_matrix_for_joints,
std::vector<mat4> *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<float> vertices,
const std::vector<float> weights, const std::vector<size_t> joints,
const size_t num_skinning_weights,
const std::vector<mat4> joint_matrices,
const float t,
const std::vector<mat4> joint_matrices, const float t,
std::vector<float> *skinned_vertices) {
assert((vertices.size() % 4) == 0);
const size_t num_vertices = vertices.size() / 4;
@ -135,7 +161,6 @@ void Skining(const std::vector<float> 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<float> vertices,
float ret[4];
Matrix::MultV4(ret, M.m, vtx);
}
}
}
void UpdateAnimation(std::vector<Animation> &animations, uint32_t index,
float time, std::vector<Node*> &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

View File

@ -3,6 +3,9 @@
#include <vector>
#include <cstddef>
#include <cstdint>
#include <string>
#include <limits>
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<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
///