Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blend Shape / Morph Target Support #33

Merged
merged 8 commits into from
Nov 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,15 @@ process happens in reverse when we construct meshes and materials that conform
to the expectations of the glTF format.

### Animations
Every skinned animation in the FBX file becomes an animation in the glTF file.
The method used is one of "baking": we step through the interval of time spanned
by the animation, keyframe by keyframe, calculate the local transform of each
node,and whenever we find any node that's rotated, translated or scaled, we
Every animation in the FBX file becomes an animation in the glTF file. The
method used is one of "baking": we step through the interval of time spanned by
the animation, keyframe by keyframe, calculate the local transform of each
node, and whenever we find any node that's rotated, translated or scaled, we
record that fact in the output.

(*Blend Shapes* are not currently supported, but are
[high on the TODO list](https://github.com/facebookincubator/FBX2glTF/issues/17).)
Beyond skeleton-based animation, *Blend Shapes* are also supported; they are
read from the FBX file on a per-mesh basis, and animations can them by varying
the weights associated with each one.

The baking method has the benefit of being simple and precise. It has the
drawback of creating potentially very large files. The more complex the
Expand Down
296 changes: 287 additions & 9 deletions src/Fbx2Raw.cpp

Large diffs are not rendered by default.

62 changes: 56 additions & 6 deletions src/Raw2Gltf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,15 +333,15 @@ ModelData *Raw2Gltf(
fmt::printf("Animation '%s' has %lu channels:\n", animation.name.c_str(), animation.channels.size());
}

for (size_t j = 0; j < animation.channels.size(); j++) {
const RawChannel &channel = animation.channels[j];
for (size_t channelIx = 0; channelIx < animation.channels.size(); channelIx++) {
const RawChannel &channel = animation.channels[channelIx];
const RawNode &node = raw.GetNode(channel.nodeIndex);

if (verboseOutput) {
fmt::printf(
" Channel %lu (%s) has translations/rotations/scales: [%lu, %lu, %lu]\n",
j, node.name.c_str(), channel.translations.size(),
channel.rotations.size(), channel.scales.size());
" Channel %lu (%s) has translations/rotations/scales/weights: [%lu, %lu, %lu, %lu]\n",
channelIx, node.name.c_str(), channel.translations.size(), channel.rotations.size(),
channel.scales.size(), channel.weights.size());
}

NodeData &nDat = require(nodesByName, node.name);
Expand All @@ -354,6 +354,9 @@ ModelData *Raw2Gltf(
if (!channel.scales.empty()) {
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.scales), "scale");
}
if (!channel.weights.empty()) {
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, {CT_FLOAT, 1, "SCALAR"}, channel.weights), "weights");
}
}
}

Expand Down Expand Up @@ -494,7 +497,11 @@ ModelData *Raw2Gltf(
mesh = meshIter->second.get();

} else {
auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name));
std::vector<float> defaultDeforms;
for (const auto &channel : rawSurface.blendChannels) {
defaultDeforms.push_back(channel.defaultDeform);
}
auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name, defaultDeforms));
meshByNodeName[nodeName] = meshPtr;
meshNode.SetMesh(meshPtr->ix);
mesh = meshPtr.get();
Expand Down Expand Up @@ -598,6 +605,49 @@ ModelData *Raw2Gltf(
GLT_VEC4F, draco::GeometryAttribute::GENERIC, draco::DT_FLOAT32);
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
}

// each channel present in the mesh always ends up a target in the primitive
for (int channelIx = 0; channelIx < rawSurface.blendChannels.size(); channelIx ++) {
const auto &channel = rawSurface.blendChannels[channelIx];

// track the bounds of each shape channel
Bounds<float, 3> shapeBounds;

std::vector<Vec3f> positions, normals;
std::vector<Vec4f> tangents;
for (int jj = 0; jj < surfaceModel.GetVertexCount(); jj ++) {
auto blendVertex = surfaceModel.GetVertex(jj).blends[channelIx];
shapeBounds.AddPoint(blendVertex.position);
positions.push_back(blendVertex.position);
if (channel.hasNormals) {
normals.push_back(blendVertex.normal);
}
if (channel.hasTangents) {
tangents.push_back(blendVertex.tangent);
}
}
std::shared_ptr<AccessorData> pAcc = gltf->AddAccessorWithView(
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
GLT_VEC3F, positions);
pAcc->min = toStdVec(shapeBounds.min);
pAcc->max = toStdVec(shapeBounds.max);

std::shared_ptr<AccessorData> nAcc;
if (channel.hasNormals) {
nAcc = gltf->AddAccessorWithView(
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
GLT_VEC3F, normals);
}

std::shared_ptr<AccessorData> tAcc;
if (channel.hasTangents) {
nAcc = gltf->AddAccessorWithView(
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
GLT_VEC4F, tangents);
}

primitive->AddTarget(pAcc.get(), nAcc.get(), tAcc.get());
}
}
if (options.useDraco) {
// Set up the encoder.
Expand Down
4 changes: 3 additions & 1 deletion src/RawModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ bool RawVertex::operator==(const RawVertex &other) const
(uv1 == other.uv1) &&
(jointIndices == other.jointIndices) &&
(jointWeights == other.jointWeights) &&
(polarityUv0 == other.polarityUv0);
(polarityUv0 == other.polarityUv0) &&
(blendSurfaceIx == other.blendSurfaceIx) &&
(blends == other.blends);
}

size_t RawVertex::Difference(const RawVertex &other) const
Expand Down
48 changes: 38 additions & 10 deletions src/RawModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ enum RawVertexAttribute
RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31
};

struct RawBlendVertex
{
Vec3f position {};
Vec3f normal {};
Vec4f tangent {};

bool operator==(const RawBlendVertex &other) const {
return position == other.position &&
normal == other.normal &&
tangent == other.tangent;
}
};

struct RawVertex
{
RawVertex() :
Expand All @@ -45,6 +58,12 @@ struct RawVertex
Vec2f uv1 { 0.0f };
Vec4i jointIndices { 0, 0, 0, 0 };
Vec4f jointWeights { 0.0f };
// end of members that directly correspond to vertex attributes

// if this vertex participates in a blend shape setup, the surfaceIx of its dedicated mesh; otherwise, -1
int blendSurfaceIx = -1;
// the size of this vector is always identical to the size of the corresponding RawSurface.blendChannels
std::vector<RawBlendVertex> blends { };

bool polarityUv0;
bool pad1;
Expand Down Expand Up @@ -154,25 +173,34 @@ struct RawMaterial
int textures[RAW_TEXTURE_USAGE_MAX];
};

struct RawBlendChannel
{
float defaultDeform;
bool hasNormals;
bool hasTangents;
};

struct RawSurface
{
std::string name; // The name of this surface
std::string nodeName; // The node that links to this surface.
std::string skeletonRootName; // The name of the root of the skeleton.
Bounds<float, 3> bounds;
std::vector<std::string> jointNames;
std::vector<Vec3f> jointGeometryMins;
std::vector<Vec3f> jointGeometryMaxs;
std::vector<Mat4f> inverseBindMatrices;
bool discrete;
std::string name; // The name of this surface
std::string nodeName; // The node that links to this surface.
std::string skeletonRootName; // The name of the root of the skeleton.
Bounds<float, 3> bounds;
std::vector<std::string> jointNames;
std::vector<Vec3f> jointGeometryMins;
std::vector<Vec3f> jointGeometryMaxs;
std::vector<Mat4f> inverseBindMatrices;
std::vector<RawBlendChannel> blendChannels;
bool discrete;
};

struct RawChannel
{
int nodeIndex;
int nodeIndex;
std::vector<Vec3f> translations;
std::vector<Quatf> rotations;
std::vector<Vec3f> scales;
std::vector<float> weights;
};

struct RawAnimation
Expand Down
11 changes: 8 additions & 3 deletions src/glTF/MeshData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
#include "MeshData.h"
#include "PrimitiveData.h"

MeshData::MeshData(std::string name)
MeshData::MeshData(const std::string &name, const std::vector<float> &weights)
: Holdable(),
name(std::move(name))
name(name),
weights(weights)
{
}

Expand All @@ -22,8 +23,12 @@ json MeshData::serialize() const
for (const auto &primitive : primitives) {
jsonPrimitivesArray.push_back(*primitive);
}
return {
json result = {
{ "name", name },
{ "primitives", jsonPrimitivesArray }
};
if (!weights.empty()) {
result["weights"] = weights;
}
return result;
}
3 changes: 2 additions & 1 deletion src/glTF/MeshData.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

struct MeshData : Holdable
{
explicit MeshData(std::string name);
MeshData(const std::string &name, const std::vector<float> &weights);

void AddPrimitive(std::shared_ptr<PrimitiveData> primitive)
{
Expand All @@ -30,6 +30,7 @@ struct MeshData : Holdable
json serialize() const override;

const std::string name;
const std::vector<float> weights;
std::vector<std::shared_ptr<PrimitiveData>> primitives;
};

Expand Down
23 changes: 22 additions & 1 deletion src/glTF/PrimitiveData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ void PrimitiveData::NoteDracoBuffer(const BufferViewData &data)
dracoBufferView = data.ix;
}

void PrimitiveData::AddTarget(const AccessorData *positions, const AccessorData *normals, const AccessorData *tangents)
{
targetAccessors.push_back({
positions->ix,
normals ? normals->ix : -1,
tangents ? tangents ->ix : -1
});
}

void to_json(json &j, const PrimitiveData &d) {
j = {
{ "material", d.material },
Expand All @@ -48,7 +57,19 @@ void to_json(json &j, const PrimitiveData &d) {
if (d.indices >= 0) {
j["indices"] = d.indices;
}

if (!d.targetAccessors.empty()) {
json targets {};
int pIx, nIx, tIx;
for (auto accessor : d.targetAccessors) {
std::tie(pIx, nIx, tIx) = accessor;
json target {};
if (pIx >= 0) { target["POSITION"] = pIx; }
if (nIx >= 0) { target["NORMAL"] = nIx; }
if (tIx >= 0) { target["TANGENT"] = tIx; }
targets.push_back(target);
}
j["targets"] = targets;
}
if (!d.dracoAttributes.empty()) {
j["extensions"] = {
{ KHR_DRACO_MESH_COMPRESSION, {
Expand Down
4 changes: 4 additions & 0 deletions src/glTF/PrimitiveData.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ struct PrimitiveData

void AddAttrib(std::string name, const AccessorData &accessor);

void AddTarget(const AccessorData *positions, const AccessorData *normals, const AccessorData *tangents);

template<class T>
void AddDracoAttrib(const AttributeDefinition<T> attribute, const std::vector<T> &attribArr)
{
Expand Down Expand Up @@ -59,6 +61,8 @@ struct PrimitiveData
const unsigned int material;
const MeshMode mode;

std::vector<std::tuple<int, int, int>> targetAccessors {};

std::map<std::string, int> attributes;
std::map<std::string, int> dracoAttributes;

Expand Down