mirror of
https://git.mirrors.martin98.com/https://github.com/syoyo/tinygltf.git
synced 2025-09-23 15:03:15 +08:00
Implement some animation.
This commit is contained in:
parent
1b85cb8c59
commit
5c47eda8be
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
///
|
||||
|
Loading…
x
Reference in New Issue
Block a user