Merge master

This commit is contained in:
Robert Long 2018-01-10 15:22:12 -08:00
commit 0d44fa3d54
11 changed files with 1099 additions and 384 deletions

View File

@ -27,6 +27,7 @@ endif()
# DRACO # DRACO
ExternalProject_Add(Draco ExternalProject_Add(Draco
GIT_REPOSITORY https://github.com/google/draco GIT_REPOSITORY https://github.com/google/draco
GIT_TAG 1.2.0
PREFIX draco PREFIX draco
GIT_TAG 1.2.0 GIT_TAG 1.2.0
INSTALL_DIR INSTALL_DIR
@ -76,6 +77,16 @@ ExternalProject_Add(Json
) )
set(JSON_INCLUDE_DIR "${CMAKE_BINARY_DIR}/json/src/Json/src") set(JSON_INCLUDE_DIR "${CMAKE_BINARY_DIR}/json/src/Json/src")
# stb
ExternalProject_Add(STB
PREFIX stb
GIT_REPOSITORY https://github.com/nothings/stb
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping STB configure step."
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping STB build step."
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping STB install step."
)
set(STB_INCLUDE_DIR "${CMAKE_BINARY_DIR}/stb/src/STB")
# cppcodec # cppcodec
ExternalProject_Add(CPPCodec ExternalProject_Add(CPPCodec
PREFIX cppcodec PREFIX cppcodec
@ -151,6 +162,7 @@ add_dependencies(FBX2glTF
MathFu MathFu
FiFoMap FiFoMap
Json Json
STB
CxxOpts CxxOpts
CPPCodec CPPCodec
Fmt Fmt
@ -182,6 +194,7 @@ target_include_directories(FBX2glTF PUBLIC
${FIFO_MAP_INCLUDE_DIR} ${FIFO_MAP_INCLUDE_DIR}
${JSON_INCLUDE_DIR} ${JSON_INCLUDE_DIR}
${CXXOPTS_INCLUDE_DIR} ${CXXOPTS_INCLUDE_DIR}
${STB_INCLUDE_DIR}
${CPPCODEC_INCLUDE_DIR} ${CPPCODEC_INCLUDE_DIR}
${FMT_INCLUDE_DIR} ${FMT_INCLUDE_DIR}
) )

View File

@ -44,9 +44,10 @@ Usage:
--khr-materials-common (WIP) Use KHR_materials_common extensions to --khr-materials-common (WIP) Use KHR_materials_common extensions to
specify Unlit/Lambert/Blinn/Phong shaders. specify Unlit/Lambert/Blinn/Phong shaders.
--pbr-metallic-roughness (WIP) Try to glean glTF 2.0 native PBR --pbr-metallic-roughness (WIP) Try to glean glTF 2.0 native PBR
attributes from the FBX. attributes from the FBX, or make a best effort
to convert from traditional shader models.
--pbr-specular-glossiness --pbr-specular-glossiness
(WIP) Experimentally fill in the (WIP) Very experimentally employ the
KHR_materials_pbrSpecularGlossiness extension. KHR_materials_pbrSpecularGlossiness extension.
--blend-shape-normals Include blend shape normals, if reported --blend-shape-normals Include blend shape normals, if reported
present by the FBX SDK. present by the FBX SDK.
@ -192,50 +193,68 @@ With glTF 2.0, we leaped headlong into physically-based rendering (BPR), where
the canonical way of expressing what a mesh looks like is by describing its the canonical way of expressing what a mesh looks like is by describing its
visible material in fundamental attributes like "how rough is this surface". visible material in fundamental attributes like "how rough is this surface".
By contrast, FBX's material support remains in the older world of Lambert and By contrast, FBX's material support remains largely in the older world of
Phong, with simpler and more direct illumination and shading models. These modes Lambert and Phong, with simpler and more direct illumination and shading
are largely incompatible — for example, textures in the old workflow often models. These modes are inherently incompatible — for example, textures in the
contain baked lighting, which would arise naturally in a PBR environment. old workflow often contain baked lighting of the type that would arise naturally
in a PBR environment.
Some material settings remain well supported and transfer automatically: Some material settings remain well supported and transfer automatically:
- Emissive constants and textures - Emissive constants and textures
- Occlusion maps - Occlusion maps
- Normal maps - Normal maps
This leaves the other traditional settings of Lambert: This leaves the other traditional settings, first of Lambert:
- Ambient — this is anathema in the PBR world, where such effects should - Ambient — this is anathema in the PBR world, where such effects should
emerge naturally from the fundamental colour of the material and any ambient emerge naturally from the fundamental colour of the material and any ambient
lighting present. lighting present.
- Diffuse — the material's direction-agnostic, non-specular reflection, - Diffuse — the material's direction-agnostic, non-specular reflection,
and additionally, with Blinn/Phong: and additionally, with Blinn/Phong:
- Specular — a more polished material's direction-sensitive reflection, - Specular — a more polished material's direction-sensitive reflection,
- Shininess — just how polished the material is, - Shininess — just how polished the material is; a higher value here yields a
more mirror-like surface.
(All these can be either constants or textures.) (All these can be either constants or textures.)
#### Exporting as Unlit/Lambert/Phong #### Exporting as Unlit/Lambert/Phong
Increasingly with PBR materials, these properties are just left at zero or If you have a model was constructed using the traditional workflow, you may
default values in the FBX. But when they're there, and they're how you want the choose to export it using the --khr-materials-common switch. This incurs a
glTF materials generated, one option is to use the --khr-materials-common dependency on the glTF extension 'KHR_materials_common'; a client that accepts
command line switch, with the awareness that this incurs a required dependency that extension is making a promise it'll do its best to render i.e. Lambert or
on the glTF extension `KHR_materials_common`. Phong.
You can use this flag even for PBR models, but the conversion is imperfect to
say the least, and there is no reason why you would ever want to do such a
thing.
**Note that at the time of writing, this glTF extension is still undergoing the **Note that at the time of writing, this glTF extension is still undergoing the
ratification process, and is furthermore likely to change names.** ratification process, and is furthermore likely to change names.**
#### Exporting as Metallic-Roughness PBR #### Exporting as Metallic-Roughness PBR
Given the command line flag --pbr-metallic-roughness, we accept glTF 2.0's PBR Given the command line flag --pbr-metallic-roughness, we throw ourselves into
mode, but we do so very partially, filling in a couple of reasonable constants the warm embrace of glTF 2.0's PBR preference.
for metalness and roughness and using the diffuse texture, if it exists, as the
`base colour` texture.
More work is needed to harness the power of glTF's 2.0's materials. The biggest As mentioned above, there is lilttle consensus in the world on how PBR should be
issue here is the lack of any obviously emerging standards to complement FBX represented in FBX. At present, we support only one format: Stingray PBS. This
itself. It's not clear what format an artist can export their PBR materials on, is a featue that comes bundled with Maya, and any PBR model exported through
and when they can, how to communicate this information well to `FBX2glTF`. that route should be digested propertly by FBX2glTF.
(*Stingray PBS* support is (A happy note: Allegorithmic's Susbstance Painter also exports Stingray PBS,
[high on the TODO list](https://github.com/facebookincubator/FBX2glTF/issues/12).) when hooked up to Maya.)
If your model is not a Stingray PBS one, but you still wish to export PBR
(perhaps you want to generate only core glTF wirhout reliance on extensions),
this converter will try its best to convert your old textures. It calculates, on
a pixel by pixel basis, reasonable values for base colour, metallicness and
roughness, using your model's diffuse, specular, and shinines textures.
It should noted here that this process cannot ever be perfect; this is very much
an apples and oranges situation.
A note of gratitude here to Gary Hsu who developed the formulae we use for this
process. They can be eyeballed
[here](https://github.com/KhronosGroup/glTF/blob/master/extensions/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows/js/three.pbrUtilities.js)
for the curious.
## Draco Compression ## Draco Compression
The tool will optionally apply [Draco](https://github.com/google/draco) The tool will optionally apply [Draco](https://github.com/google/draco)

View File

@ -1,6 +1,6 @@
{ {
"name": "fbx2gltf", "name": "fbx2gltf",
"version": "0.9.2", "version": "0.9.3",
"description": "Node wrapper around FBX2glTF tools.", "description": "Node wrapper around FBX2glTF tools.",
"main": "index.js", "main": "index.js",
"repository": { "repository": {

View File

@ -88,9 +88,102 @@ private:
const FbxLayerElementArrayTemplate<int> *indices; const FbxLayerElementArrayTemplate<int> *indices;
}; };
class FbxMaterialAccess struct FbxMaterialInfo {
FbxMaterialInfo(const FbxString &name, const FbxString &shadingModel)
: name(name),
shadingModel(shadingModel)
{}
const FbxString name;
const FbxString shadingModel;
};
struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
static constexpr const char *FBX_SHADER_METROUGH = "MetallicRoughness";
FbxRoughMetMaterialInfo(const FbxString &name, const FbxString &shadingModel)
: FbxMaterialInfo(name, shadingModel)
{}
const FbxFileTexture *texColor {};
FbxVector4 colBase {};
const FbxFileTexture *texNormal {};
const FbxFileTexture *texMetallic {};
FbxDouble metallic {};
const FbxFileTexture *texRoughness {};
FbxDouble roughness {};
const FbxFileTexture *texEmissive {};
FbxVector4 colEmissive {};
FbxDouble emissiveIntensity {};
const FbxFileTexture *texAmbientOcclusion {};
static std::unique_ptr<FbxRoughMetMaterialInfo> From(
FbxSurfaceMaterial *fbxMaterial,
const std::map<const FbxTexture *, FbxString> &textureLocations)
{ {
struct FbxMaterialProperties { std::unique_ptr<FbxRoughMetMaterialInfo> res(new FbxRoughMetMaterialInfo(fbxMaterial->GetName(), FBX_SHADER_METROUGH));
const FbxProperty mayaProp = fbxMaterial->FindProperty("Maya");
if (mayaProp.GetPropertyDataType() != FbxCompoundDT) {
return nullptr;
}
if (!fbxMaterial->ShadingModel.Get().IsEmpty()) {
fmt::printf("Warning: Material %s has surprising shading model: %s\n",
fbxMaterial->GetName(), fbxMaterial->ShadingModel.Get());
}
auto getTex = [&](std::string propName) {
const FbxFileTexture *ptr = nullptr;
const FbxProperty useProp = mayaProp.FindHierarchical(("use_" + propName + "_map").c_str());
if (useProp.IsValid() && useProp.Get<bool>()) {
const FbxProperty texProp = mayaProp.FindHierarchical(("TEX_" + propName + "_map").c_str());
if (texProp.IsValid()) {
ptr = texProp.GetSrcObject<FbxFileTexture>();
if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) {
ptr = nullptr;
}
}
} else if (verboseOutput && useProp.IsValid()) {
fmt::printf("Note: Property '%s' of material '%s' exists, but is flagged as 'do not use'.\n",
propName, fbxMaterial->GetName());
}
return ptr;
};
auto getVec = [&](std::string propName) -> FbxDouble3 {
const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str());
return vecProp.IsValid() ? vecProp.Get<FbxDouble3>() : FbxDouble3(1, 1, 1);
};
auto getVal = [&](std::string propName) -> FbxDouble {
const FbxProperty vecProp = mayaProp.FindHierarchical(propName .c_str());
return vecProp.IsValid() ? vecProp.Get<FbxDouble>() : 0;
};
res->texNormal = getTex("normal");
res->texColor = getTex("color");
res->colBase = getVec("base_color");
res->texAmbientOcclusion = getTex("ao");
res->texEmissive = getTex("emissive");
res->colEmissive = getVec("emissive");
res->emissiveIntensity = getVal("emissive_intensity");
res->texMetallic = getTex("metallic");
res->metallic = getVal("metallic");
res->texRoughness = getTex("roughness");
res->roughness = getVal("roughness");
return res;
}
};
struct FbxTraditionalMaterialInfo : FbxMaterialInfo {
static constexpr const char *FBX_SHADER_LAMBERT = "Lambert";
static constexpr const char *FBX_SHADER_BLINN = "Blinn";
static constexpr const char *FBX_SHADER_PHONG = "Phong";
FbxTraditionalMaterialInfo(const FbxString &name, const FbxString &shadingModel)
: FbxMaterialInfo(name, shadingModel)
{}
FbxFileTexture *texAmbient {}; FbxFileTexture *texAmbient {};
FbxVector4 colAmbient {}; FbxVector4 colAmbient {};
FbxFileTexture *texSpecular {}; FbxFileTexture *texSpecular {};
@ -102,80 +195,12 @@ class FbxMaterialAccess
FbxFileTexture *texNormal {}; FbxFileTexture *texNormal {};
FbxFileTexture *texShininess {}; FbxFileTexture *texShininess {};
FbxDouble shininess {}; FbxDouble shininess {};
};
private: static std::unique_ptr<FbxTraditionalMaterialInfo> From(
const FbxSurfaceMaterial *fbxMaterial; FbxSurfaceMaterial *fbxMaterial,
const std::map<const FbxTexture *, FbxString> &textureLocations; const std::map<const FbxTexture *, FbxString> &textureLocations)
public:
const FbxString name;
const FbxString shadingModel;
const struct FbxMaterialProperties props;
explicit FbxMaterialAccess(
const FbxSurfaceMaterial *fbxMaterial, const std::map<const FbxTexture *, FbxString> &textureNames) :
fbxMaterial(fbxMaterial),
name(fbxMaterial->GetName()),
shadingModel(fbxMaterial->ShadingModel),
textureLocations(textureNames),
props(extractTextures())
{}
struct FbxMaterialProperties extractTextures() {
struct FbxMaterialProperties res;
// four properties are on the same structure and follow the same rules
auto handleBasicProperty = [&](const char *colName, const char *facName) {
FbxFileTexture *colTex, *facTex;
FbxVector4 vec;
std::tie(vec, colTex, facTex) = getSurfaceValues(colName, facName);
if (colTex) {
if (facTex) {
fmt::printf("Warning: Mat [%s]: Can't handle both %s and %s textures; discarding %s.\n", name, colName, facName, facName);
}
return std::make_tuple(vec, colTex);
}
return std::make_tuple(vec, facTex);
};
std::tie(res.colAmbient, res.texAmbient) =
handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
std::tie(res.colSpecular, res.texSpecular) =
handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
std::tie(res.colDiffuse, res.texDiffuse) =
handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
std::tie(res.colEmissive, res.texEmissive) =
handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor);
// the normal map can only ever be a map, ignore everything else
std::tie(std::ignore, res.texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap);
// shininess can be a map or a factor
std::tie(res.shininess, res.texShininess) = getSurfaceScalar(FbxSurfaceMaterial::sShininess);
// for transparency we just want a constant vector value;
FbxVector4 transparency;
// extract any existing textures only so we can warn that we're throwing them away
FbxFileTexture *colTex, *facTex;
std::tie(transparency, colTex, facTex) =
getSurfaceValues(FbxSurfaceMaterial::sTransparentColor, FbxSurfaceMaterial::sTransparencyFactor);
if (colTex) {
fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparentColor);
}
if (facTex) {
fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparencyFactor);
}
// FBX color is RGB, so we calculate the A channel as the average of the FBX transparency color vector
res.colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2])/3.0;
return res;
}
std::tuple<FbxDouble, FbxFileTexture *> getSurfaceScalar(const char *propName) const
{ {
auto getSurfaceScalar = [&](const char *propName) -> std::tuple<FbxDouble, FbxFileTexture *> {
const FbxProperty prop = fbxMaterial->FindProperty(propName); const FbxProperty prop = fbxMaterial->FindProperty(propName);
FbxDouble val(0); FbxDouble val(0);
@ -187,10 +212,9 @@ public:
val = prop.Get<FbxDouble>(); val = prop.Get<FbxDouble>();
} }
return std::make_tuple(val, tex); return std::make_tuple(val, tex);
} };
std::tuple<FbxDouble3, FbxFileTexture *> getSurfaceVector(const char *propName) const auto getSurfaceVector = [&](const char *propName) -> std::tuple<FbxDouble3, FbxFileTexture *> {
{
const FbxProperty prop = fbxMaterial->FindProperty(propName); const FbxProperty prop = fbxMaterial->FindProperty(propName);
FbxDouble3 val(1, 1, 1); FbxDouble3 val(1, 1, 1);
@ -202,10 +226,9 @@ public:
val = prop.Get<FbxDouble3>(); val = prop.Get<FbxDouble3>();
} }
return std::make_tuple(val, tex); return std::make_tuple(val, tex);
} };
std::tuple<FbxVector4, FbxFileTexture *, FbxFileTexture *> getSurfaceValues(const char *colName, const char *facName) const auto getSurfaceValues = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *, FbxFileTexture *> {
{
const FbxProperty colProp = fbxMaterial->FindProperty(colName); const FbxProperty colProp = fbxMaterial->FindProperty(colName);
const FbxProperty facProp = fbxMaterial->FindProperty(facName); const FbxProperty facProp = fbxMaterial->FindProperty(facName);
@ -234,8 +257,71 @@ public:
factorVal); factorVal);
return std::make_tuple(val, colTex, facTex); return std::make_tuple(val, colTex, facTex);
}; };
std::string name = fbxMaterial->GetName();
std::unique_ptr<FbxTraditionalMaterialInfo> res(new FbxTraditionalMaterialInfo(name.c_str(), fbxMaterial->ShadingModel.Get()));
// four properties are on the same structure and follow the same rules
auto handleBasicProperty = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *>{
FbxFileTexture *colTex, *facTex;
FbxVector4 vec;
std::tie(vec, colTex, facTex) = getSurfaceValues(colName, facName);
if (colTex) {
if (facTex) {
fmt::printf("Warning: Mat [%s]: Can't handle both %s and %s textures; discarding %s.\n", name, colName, facName, facName);
}
return std::make_tuple(vec, colTex);
}
return std::make_tuple(vec, facTex);
}; };
std::tie(res->colAmbient, res->texAmbient) =
handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
std::tie(res->colSpecular, res->texSpecular) =
handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
std::tie(res->colDiffuse, res->texDiffuse) =
handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
std::tie(res->colEmissive, res->texEmissive) =
handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor);
// the normal map can only ever be a map, ignore everything else
std::tie(std::ignore, res->texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap);
// shininess can be a map or a factor
std::tie(res->shininess, res->texShininess) = getSurfaceScalar(FbxSurfaceMaterial::sShininess);
// for transparency we just want a constant vector value;
FbxVector4 transparency;
// extract any existing textures only so we can warn that we're throwing them away
FbxFileTexture *colTex, *facTex;
std::tie(transparency, colTex, facTex) =
getSurfaceValues(FbxSurfaceMaterial::sTransparentColor, FbxSurfaceMaterial::sTransparencyFactor);
if (colTex) {
fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparentColor);
}
if (facTex) {
fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparencyFactor);
}
// FBX color is RGB, so we calculate the A channel as the average of the FBX transparency color vector
res->colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2])/3.0;
return res;
}
};
std::unique_ptr<FbxMaterialInfo>
GetMaterialInfo(FbxSurfaceMaterial *material, const std::map<const FbxTexture *, FbxString> &textureLocations)
{
std::unique_ptr<FbxMaterialInfo> res;
res = FbxRoughMetMaterialInfo::From(material, textureLocations);
if (!res) {
res = FbxTraditionalMaterialInfo::From(material, textureLocations);
}
return res;
}
class FbxMaterialsAccess class FbxMaterialsAccess
{ {
public: public:
@ -273,14 +359,14 @@ public:
} }
auto summary = summaries[materialNum]; auto summary = summaries[materialNum];
if (summary == nullptr) { if (summary == nullptr) {
summary = summaries[materialNum] = std::make_shared<FbxMaterialAccess>( summary = summaries[materialNum] = GetMaterialInfo(
mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum), mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum),
textureLocations); textureLocations);
} }
} }
} }
const std::shared_ptr<FbxMaterialAccess> GetMaterial(const int polygonIndex) const const std::shared_ptr<FbxMaterialInfo> GetMaterial(const int polygonIndex) const
{ {
if (mappingMode != FbxGeometryElement::eNone) { if (mappingMode != FbxGeometryElement::eNone) {
const int materialNum = indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0); const int materialNum = indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
@ -294,7 +380,7 @@ public:
private: private:
FbxGeometryElement::EMappingMode mappingMode; FbxGeometryElement::EMappingMode mappingMode;
std::vector<std::shared_ptr<FbxMaterialAccess>> summaries {}; std::vector<std::shared_ptr<FbxMaterialInfo>> summaries {};
const FbxMesh *mesh; const FbxMesh *mesh;
const FbxLayerElementArrayTemplate<int> *indices; const FbxLayerElementArrayTemplate<int> *indices;
}; };
@ -338,7 +424,7 @@ public:
inverseBindMatrices.emplace_back(globalBindposeInverseMatrix); inverseBindMatrices.emplace_back(globalBindposeInverseMatrix);
jointNodes.push_back(cluster->GetLink()); jointNodes.push_back(cluster->GetLink());
jointNames.push_back(*cluster->GetLink()->GetName() != '\0' ? cluster->GetLink()->GetName() : cluster->GetName()); jointIds.push_back(cluster->GetLink()->GetUniqueID());
const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform(); const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform();
jointSkinningTransforms.push_back(FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix)); jointSkinningTransforms.push_back(FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix));
@ -400,9 +486,9 @@ public:
return jointNodes[jointIndex]; return jointNodes[jointIndex];
} }
const char *GetJointName(const int jointIndex) const const long GetJointId(const int jointIndex) const
{ {
return jointNames[jointIndex].c_str(); return jointIds[jointIndex];
} }
const FbxMatrix &GetJointSkinningTransform(const int jointIndex) const const FbxMatrix &GetJointSkinningTransform(const int jointIndex) const
@ -415,10 +501,10 @@ public:
return jointInverseGlobalTransforms[jointIndex]; return jointInverseGlobalTransforms[jointIndex];
} }
const char *GetRootNode() const const long GetRootNode() const
{ {
assert(rootIndex != -1); assert(rootIndex != -1);
return jointNames[rootIndex].c_str(); return jointIds[rootIndex];
} }
const FbxAMatrix &GetInverseBindMatrix(const int jointIndex) const const FbxAMatrix &GetInverseBindMatrix(const int jointIndex) const
@ -440,7 +526,7 @@ public:
private: private:
int rootIndex; int rootIndex;
std::vector<std::string> jointNames; std::vector<long> jointIds;
std::vector<FbxNode *> jointNodes; std::vector<FbxNode *> jointNodes;
std::vector<FbxMatrix> jointSkinningTransforms; std::vector<FbxMatrix> jointSkinningTransforms;
std::vector<FbxMatrix> jointInverseGlobalTransforms; std::vector<FbxMatrix> jointInverseGlobalTransforms;
@ -607,8 +693,23 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
meshConverter.Triangulate(pNode->GetNodeAttribute(), true); meshConverter.Triangulate(pNode->GetNodeAttribute(), true);
FbxMesh *pMesh = pNode->GetMesh(); FbxMesh *pMesh = pNode->GetMesh();
// Obtains the surface Id
const long surfaceId = pMesh->GetUniqueID();
// Associate the node to this surface
int nodeId = raw.GetNodeById(pNode->GetUniqueID());
if (nodeId >= 0) {
RawNode &node = raw.GetNode(nodeId);
node.surfaceId = surfaceId;
}
if (raw.GetSurfaceById(surfaceId) >= 0) {
// This surface is already loaded
return;
}
const char *meshName = (pNode->GetName()[0] != '\0') ? pNode->GetName() : pMesh->GetName(); const char *meshName = (pNode->GetName()[0] != '\0') ? pNode->GetName() : pMesh->GetName();
const int rawSurfaceIndex = raw.AddSurface(meshName, pNode->GetName()); const int rawSurfaceIndex = raw.AddSurface(meshName, surfaceId);
const FbxVector4 *controlPoints = pMesh->GetControlPoints(); const FbxVector4 *controlPoints = pMesh->GetControlPoints();
const FbxLayerElementAccess<FbxVector4> normalLayer(pMesh->GetElementNormal(), pMesh->GetElementNormalCount()); const FbxLayerElementAccess<FbxVector4> normalLayer(pMesh->GetElementNormal(), pMesh->GetElementNormalCount());
@ -624,7 +725,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
if (verboseOutput) { if (verboseOutput) {
fmt::printf( fmt::printf(
"mesh %d: %s (skinned: %s)\n", rawSurfaceIndex, meshName, "mesh %d: %s (skinned: %s)\n", rawSurfaceIndex, meshName,
skinning.IsSkinned() ? skinning.GetRootNode() : "NO"); skinning.IsSkinned() ? raw.GetNode(raw.GetNodeById(skinning.GetRootNode())).name.c_str() : "NO");
} }
// The FbxNode geometric transformation describes how a FbxNodeAttribute is offset from // The FbxNode geometric transformation describes how a FbxNodeAttribute is offset from
@ -638,7 +739,10 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
const FbxVector4 meshScaling = pNode->GetGeometricScaling(FbxNode::eSourcePivot); const FbxVector4 meshScaling = pNode->GetGeometricScaling(FbxNode::eSourcePivot);
const FbxAMatrix meshTransform(meshTranslation, meshRotation, meshScaling); const FbxAMatrix meshTransform(meshTranslation, meshRotation, meshScaling);
const FbxMatrix transform = meshTransform; const FbxMatrix transform = meshTransform;
const FbxMatrix inverseTransposeTransform = transform.Inverse().Transpose();
// Remove translation & scaling from transforms that will bi applied to normals, tangents & binormals
const FbxMatrix normalTransform(FbxVector4(), meshRotation, meshScaling);
const FbxMatrix inverseTransposeTransform = normalTransform.Inverse().Transpose();
raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_POSITION); raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_POSITION);
if (normalLayer.LayerPresent()) { raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_NORMAL); } if (normalLayer.LayerPresent()) { raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_NORMAL); }
@ -654,12 +758,12 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
RawSurface &rawSurface = raw.GetSurface(rawSurfaceIndex); RawSurface &rawSurface = raw.GetSurface(rawSurfaceIndex);
rawSurface.skeletonRootName = (skinning.IsSkinned()) ? skinning.GetRootNode() : pNode->GetName(); rawSurface.skeletonRootId = (skinning.IsSkinned()) ? skinning.GetRootNode() : pNode->GetUniqueID();
for (int jointIndex = 0; jointIndex < skinning.GetNodeCount(); jointIndex++) { for (int jointIndex = 0; jointIndex < skinning.GetNodeCount(); jointIndex++) {
const char *jointName = skinning.GetJointName(jointIndex); const long jointId = skinning.GetJointId(jointIndex);
raw.GetNode(raw.GetNodeByName(jointName)).isJoint = true; raw.GetNode(raw.GetNodeById(jointId)).isJoint = true;
rawSurface.jointNames.emplace_back(jointName); rawSurface.jointIds.emplace_back(jointId);
rawSurface.inverseBindMatrices.push_back(toMat4f(skinning.GetInverseBindMatrix(jointIndex))); rawSurface.inverseBindMatrices.push_back(toMat4f(skinning.GetInverseBindMatrix(jointIndex)));
rawSurface.jointGeometryMins.emplace_back(FLT_MAX, FLT_MAX, FLT_MAX); rawSurface.jointGeometryMins.emplace_back(FLT_MAX, FLT_MAX, FLT_MAX);
rawSurface.jointGeometryMaxs.emplace_back(-FLT_MAX, -FLT_MAX, -FLT_MAX); rawSurface.jointGeometryMaxs.emplace_back(-FLT_MAX, -FLT_MAX, -FLT_MAX);
@ -684,27 +788,23 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
for (int polygonIndex = 0; polygonIndex < pMesh->GetPolygonCount(); polygonIndex++) { for (int polygonIndex = 0; polygonIndex < pMesh->GetPolygonCount(); polygonIndex++) {
FBX_ASSERT(pMesh->GetPolygonSize(polygonIndex) == 3); FBX_ASSERT(pMesh->GetPolygonSize(polygonIndex) == 3);
const std::shared_ptr<FbxMaterialInfo> fbxMaterial = materials.GetMaterial(polygonIndex);
const std::shared_ptr<FbxMaterialAccess> fbxMaterial = materials.GetMaterial(polygonIndex);
int textures[RAW_TEXTURE_USAGE_MAX]; int textures[RAW_TEXTURE_USAGE_MAX];
std::fill_n(textures, RAW_TEXTURE_USAGE_MAX, -1); std::fill_n(textures, (int) RAW_TEXTURE_USAGE_MAX, -1);
FbxString shadingModel, materialName; std::shared_ptr<RawMatProps> rawMatProps;
FbxVector4 ambient, specular, diffuse, emissive; FbxString materialName;
FbxDouble shininess;
if (fbxMaterial == nullptr) { if (fbxMaterial == nullptr) {
materialName = "DefaultMaterial"; materialName = "DefaultMaterial";
shadingModel = "Lambert"; rawMatProps.reset(new RawTraditionalMatProps(RAW_SHADING_MODEL_LAMBERT,
Vec3f(0, 0, 0), Vec4f(.5, .5, .5, 1), Vec3f(0, 0, 0), Vec3f(0, 0, 0), 0.5));
} else { } else {
materialName = fbxMaterial->name; materialName = fbxMaterial->name;
shadingModel = fbxMaterial->shadingModel;
const auto &matProps = fbxMaterial->props; const auto maybeAddTexture = [&](const FbxFileTexture *tex, RawTextureUsage usage) {
const auto maybeAddTexture = [&](FbxFileTexture *tex, RawTextureUsage usage) {
if (tex != nullptr) { if (tex != nullptr) {
// dig out the inferred filename from the textureLocations map // dig out the inferred filename from the textureLocations map
FbxString inferredPath = textureLocations.find(tex)->second; FbxString inferredPath = textureLocations.find(tex)->second;
@ -712,19 +812,44 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
} }
}; };
ambient = matProps.colAmbient; std::shared_ptr<RawMatProps> matInfo;
maybeAddTexture(matProps.texAmbient, RAW_TEXTURE_USAGE_AMBIENT); if (fbxMaterial->shadingModel == FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH) {
specular = matProps.colSpecular; FbxRoughMetMaterialInfo *fbxMatInfo = static_cast<FbxRoughMetMaterialInfo *>(fbxMaterial.get());
maybeAddTexture(matProps.texSpecular, RAW_TEXTURE_USAGE_SPECULAR);
diffuse = matProps.colDiffuse;
maybeAddTexture(matProps.texDiffuse, RAW_TEXTURE_USAGE_DIFFUSE);
emissive = matProps.colEmissive;
maybeAddTexture(matProps.texEmissive, RAW_TEXTURE_USAGE_EMISSIVE);
maybeAddTexture(matProps.texNormal, RAW_TEXTURE_USAGE_NORMAL); maybeAddTexture(fbxMatInfo->texColor, RAW_TEXTURE_USAGE_ALBEDO);
maybeAddTexture(fbxMatInfo->texNormal, RAW_TEXTURE_USAGE_NORMAL);
maybeAddTexture(fbxMatInfo->texEmissive, RAW_TEXTURE_USAGE_EMISSIVE);
maybeAddTexture(fbxMatInfo->texRoughness, RAW_TEXTURE_USAGE_ROUGHNESS);
maybeAddTexture(fbxMatInfo->texMetallic, RAW_TEXTURE_USAGE_METALLIC);
maybeAddTexture(fbxMatInfo->texAmbientOcclusion, RAW_TEXTURE_USAGE_OCCLUSION);
rawMatProps.reset(new RawMetRoughMatProps(
RAW_SHADING_MODEL_PBR_MET_ROUGH, toVec4f(fbxMatInfo->colBase), toVec3f(fbxMatInfo->colEmissive),
fbxMatInfo->emissiveIntensity, fbxMatInfo->metallic, fbxMatInfo->roughness));
} else {
shininess = matProps.shininess; FbxTraditionalMaterialInfo *fbxMatInfo = static_cast<FbxTraditionalMaterialInfo *>(fbxMaterial.get());
maybeAddTexture(matProps.texShininess, RAW_TEXTURE_USAGE_SHININESS); RawShadingModel shadingModel;
if (fbxMaterial->shadingModel == "Lambert") {
shadingModel = RAW_SHADING_MODEL_LAMBERT;
} else if (fbxMaterial->shadingModel == "Blinn") {
shadingModel = RAW_SHADING_MODEL_BLINN;
} else if (fbxMaterial->shadingModel == "Phong") {
shadingModel = RAW_SHADING_MODEL_PHONG;
} else if (fbxMaterial->shadingModel == "Constant") {
shadingModel = RAW_SHADING_MODEL_PHONG;
} else {
shadingModel = RAW_SHADING_MODEL_UNKNOWN;
}
maybeAddTexture(fbxMatInfo->texDiffuse, RAW_TEXTURE_USAGE_DIFFUSE);
maybeAddTexture(fbxMatInfo->texNormal, RAW_TEXTURE_USAGE_NORMAL);
maybeAddTexture(fbxMatInfo->texEmissive, RAW_TEXTURE_USAGE_EMISSIVE);
maybeAddTexture(fbxMatInfo->texShininess, RAW_TEXTURE_USAGE_SHININESS);
maybeAddTexture(fbxMatInfo->texAmbient, RAW_TEXTURE_USAGE_AMBIENT);
maybeAddTexture(fbxMatInfo->texSpecular, RAW_TEXTURE_USAGE_SPECULAR);
rawMatProps.reset(new RawTraditionalMatProps(shadingModel,
toVec3f(fbxMatInfo->colAmbient), toVec4f(fbxMatInfo->colDiffuse), toVec3f(fbxMatInfo->colEmissive),
toVec3f(fbxMatInfo->colSpecular), fbxMatInfo->shininess));
}
} }
RawVertex rawVertices[3]; RawVertex rawVertices[3];
@ -852,9 +977,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
} }
const RawMaterialType materialType = GetMaterialType(raw, textures, vertexTransparency, skinning.IsSkinned()); const RawMaterialType materialType = GetMaterialType(raw, textures, vertexTransparency, skinning.IsSkinned());
const int rawMaterialIndex = raw.AddMaterial( const int rawMaterialIndex = raw.AddMaterial(materialName, materialType, textures, rawMatProps);
materialName, shadingModel, materialType, textures,
toVec3f(ambient), toVec4f(diffuse), toVec3f(specular), toVec3f(emissive), shininess);
raw.AddTriangle(rawVertexIndices[0], rawVertexIndices[1], rawVertexIndices[2], rawMaterialIndex, rawSurfaceIndex); raw.AddTriangle(rawVertexIndices[0], rawVertexIndices[1], rawVertexIndices[2], rawMaterialIndex, rawSurfaceIndex);
} }
@ -865,12 +988,12 @@ static void ReadCamera(RawModel &raw, FbxScene *pScene, FbxNode *pNode)
const FbxCamera *pCamera = pNode->GetCamera(); const FbxCamera *pCamera = pNode->GetCamera();
if (pCamera->ProjectionType.Get() == FbxCamera::EProjectionType::ePerspective) { if (pCamera->ProjectionType.Get() == FbxCamera::EProjectionType::ePerspective) {
raw.AddCameraPerspective( raw.AddCameraPerspective(
"", pNode->GetName(), (float) pCamera->FilmAspectRatio, "", pNode->GetUniqueID(), (float) pCamera->FilmAspectRatio,
(float) pCamera->FieldOfViewX, (float) pCamera->FieldOfViewX, (float) pCamera->FieldOfViewX, (float) pCamera->FieldOfViewX,
(float) pCamera->NearPlane, (float) pCamera->FarPlane); (float) pCamera->NearPlane, (float) pCamera->FarPlane);
} else { } else {
raw.AddCameraOrthographic( raw.AddCameraOrthographic(
"", pNode->GetName(), "", pNode->GetUniqueID(),
(float) pCamera->OrthoZoom, (float) pCamera->OrthoZoom, (float) pCamera->OrthoZoom, (float) pCamera->OrthoZoom,
(float) pCamera->FarPlane, (float) pCamera->NearPlane); (float) pCamera->FarPlane, (float) pCamera->NearPlane);
} }
@ -946,10 +1069,11 @@ static FbxVector4 computeLocalScale(FbxNode *pNode, FbxTime pTime = FBXSDK_TIME_
static void ReadNodeHierarchy( static void ReadNodeHierarchy(
RawModel &raw, FbxScene *pScene, FbxNode *pNode, RawModel &raw, FbxScene *pScene, FbxNode *pNode,
const std::string &parentName, const std::string &path) const long parentId, const std::string &path)
{ {
const FbxUInt64 nodeId = pNode->GetUniqueID();
const char *nodeName = pNode->GetName(); const char *nodeName = pNode->GetName();
const int nodeIndex = raw.AddNode(nodeName, parentName.c_str()); const int nodeIndex = raw.AddNode(nodeId, nodeName, parentId);
RawNode &node = raw.GetNode(nodeIndex); RawNode &node = raw.GetNode(nodeIndex);
FbxTransform::EInheritType lInheritType; FbxTransform::EInheritType lInheritType;
@ -962,7 +1086,7 @@ static void ReadNodeHierarchy(
static int warnRrSsCount = 0; static int warnRrSsCount = 0;
static int warnRrsCount = 0; static int warnRrsCount = 0;
if (lInheritType == FbxTransform::eInheritRrSs && !parentName.empty()) { if (lInheritType == FbxTransform::eInheritRrSs && parentId) {
if (++warnRrSsCount == 1) { if (++warnRrSsCount == 1) {
fmt::printf("Warning: node %s uses unsupported transform inheritance type 'eInheritRrSs'.\n", newPath); fmt::printf("Warning: node %s uses unsupported transform inheritance type 'eInheritRrSs'.\n", newPath);
fmt::printf(" (Further warnings of this type squelched.)\n"); fmt::printf(" (Further warnings of this type squelched.)\n");
@ -989,19 +1113,19 @@ static void ReadNodeHierarchy(
node.rotation = toQuatf(localRotation); node.rotation = toQuatf(localRotation);
node.scale = toVec3f(localScaling); node.scale = toVec3f(localScaling);
if (parentName.size() > 0) { if (parentId) {
RawNode &parentNode = raw.GetNode(raw.GetNodeByName(parentName.c_str())); RawNode &parentNode = raw.GetNode(raw.GetNodeById(parentId));
// Add unique child name to the parent node. // Add unique child name to the parent node.
if (std::find(parentNode.childNames.begin(), parentNode.childNames.end(), nodeName) == parentNode.childNames.end()) { if (std::find(parentNode.childIds.begin(), parentNode.childIds.end(), nodeId) == parentNode.childIds.end()) {
parentNode.childNames.push_back(nodeName); parentNode.childIds.push_back(nodeId);
} }
} else { } else {
// If there is no parent then this is the root node. // If there is no parent then this is the root node.
raw.SetRootNode(nodeName); raw.SetRootNode(nodeId);
} }
for (int child = 0; child < pNode->GetChildCount(); child++) { for (int child = 0; child < pNode->GetChildCount(); child++) {
ReadNodeHierarchy(raw, pScene, pNode->GetChild(child), nodeName, newPath); ReadNodeHierarchy(raw, pScene, pNode->GetChild(child), nodeId, newPath);
} }
} }
@ -1057,7 +1181,7 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene)
bool hasMorphs = false; bool hasMorphs = false;
RawChannel channel; RawChannel channel;
channel.nodeIndex = raw.GetNodeByName(pNode->GetName()); channel.nodeIndex = raw.GetNodeById(pNode->GetUniqueID());
for (FbxLongLong frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; frameIndex++) { for (FbxLongLong frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; frameIndex++) {
FbxTime pTime; FbxTime pTime;
@ -1300,7 +1424,7 @@ bool LoadFBXFile(RawModel &raw, const char *fbxFileName, const char *textureExte
FbxSystemUnit::m.ConvertScene(pScene); FbxSystemUnit::m.ConvertScene(pScene);
} }
ReadNodeHierarchy(raw, pScene, pScene->GetRootNode(), "", ""); ReadNodeHierarchy(raw, pScene, pScene->GetRootNode(), 0, "");
ReadNodeAttributes(raw, pScene, pScene->GetRootNode(), textureLocations); ReadNodeAttributes(raw, pScene, pScene->GetRootNode(), textureLocations);
ReadAnimations(raw, pScene); ReadAnimations(raw, pScene);

View File

@ -12,6 +12,11 @@
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#include "FBX2glTF.h" #include "FBX2glTF.h"
#include "utils/String_Utils.h" #include "utils/String_Utils.h"
#include "utils/Image_Utils.h" #include "utils/Image_Utils.h"
@ -217,13 +222,29 @@ struct GLTFData
Holder<SceneData> scenes; Holder<SceneData> scenes;
}; };
static void WriteToVectorContext(void *context, void *data, int size) {
auto *vec = static_cast<std::vector<char> *>(context);
for (int ii = 0; ii < size; ii ++) {
vec->push_back(((char *) data)[ii]);
}
}
/** /**
* This method sanity-checks existance and then returns a *reference* to the *Data instance * This method sanity-checks existance and then returns a *reference* to the *Data instance
* registered under that name. This is safe in the context of this tool, where all such data * registered under that name. This is safe in the context of this tool, where all such data
* classes are guaranteed to stick around for the duration of the process. * classes are guaranteed to stick around for the duration of the process.
*/ */
template<typename T> template<typename T>
T &require(std::map<std::string, std::shared_ptr<T>> map, std::string key) T &require(std::map<std::string, std::shared_ptr<T>> map, const std::string &key)
{
auto iter = map.find(key);
assert(iter != map.end());
T &result = *iter->second;
return result;
}
template<typename T>
T &require(std::map<long, std::shared_ptr<T>> map, long key)
{ {
auto iter = map.find(key); auto iter = map.find(key);
assert(iter != map.end()); assert(iter != map.end());
@ -260,7 +281,7 @@ ModelData *Raw2Gltf(
for (int i = 0; i < raw.GetMaterialCount(); i++) { for (int i = 0; i < raw.GetMaterialCount(); i++) {
fmt::printf( fmt::printf(
"Material %d: %s [shading: %s]\n", i, raw.GetMaterial(i).name.c_str(), "Material %d: %s [shading: %s]\n", i, raw.GetMaterial(i).name.c_str(),
raw.GetMaterial(i).shadingModel.c_str()); Describe(raw.GetMaterial(i).info->shadingModel));
} }
if (raw.GetVertexCount() > 2 * raw.GetTriangleCount()) { if (raw.GetVertexCount() > 2 * raw.GetTriangleCount()) {
fmt::printf( fmt::printf(
@ -282,9 +303,10 @@ ModelData *Raw2Gltf(
std::unique_ptr<GLTFData> gltf(new GLTFData(options.outputBinary)); std::unique_ptr<GLTFData> gltf(new GLTFData(options.outputBinary));
std::map<std::string, std::shared_ptr<NodeData>> nodesByName; std::map<long, std::shared_ptr<NodeData>> nodesById;
std::map<std::string, std::shared_ptr<MaterialData>> materialsByName; std::map<std::string, std::shared_ptr<MaterialData>> materialsByName;
std::map<std::string, std::shared_ptr<MeshData>> meshByNodeName; std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
std::map<long, std::shared_ptr<MeshData>> meshBySurfaceId;
// for now, we only have one buffer; data->binary points to the same vector as that BufferData does. // for now, we only have one buffer; data->binary points to the same vector as that BufferData does.
BufferData &buffer = *gltf->buffers.hold( BufferData &buffer = *gltf->buffers.hold(
@ -303,13 +325,13 @@ ModelData *Raw2Gltf(
auto nodeData = gltf->nodes.hold( auto nodeData = gltf->nodes.hold(
new NodeData(node.name, node.translation, node.rotation, node.scale, node.isJoint)); new NodeData(node.name, node.translation, node.rotation, node.scale, node.isJoint));
for (const auto &childName : node.childNames) { for (const auto &childId : node.childIds) {
int childIx = raw.GetNodeByName(childName.c_str()); int childIx = raw.GetNodeById(childId);
assert(childIx >= 0); assert(childIx >= 0);
nodeData->AddChildNode(childIx); nodeData->AddChildNode(childIx);
} }
assert(nodesByName.find(nodeData->name) == nodesByName.end());
nodesByName.insert(std::make_pair(nodeData->name, nodeData)); nodesById.insert(std::make_pair(node.id, nodeData));
} }
// //
@ -344,7 +366,7 @@ ModelData *Raw2Gltf(
channel.scales.size(), channel.weights.size()); channel.scales.size(), channel.weights.size());
} }
NodeData &nDat = require(nodesByName, node.name); NodeData &nDat = require(nodesById, node.id);
if (!channel.translations.empty()) { if (!channel.translations.empty()) {
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.translations), "translation"); aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.translations), "translation");
} }
@ -370,23 +392,204 @@ ModelData *Raw2Gltf(
// textures // textures
// //
for (int textureIndex = 0; textureIndex < raw.GetTextureCount(); textureIndex++) { using pixel = std::array<float, 4>; // pixel components are floats in [0, 1]
const RawTexture &texture = raw.GetTexture(textureIndex); using pixel_merger = std::function<pixel(const std::vector<const pixel *>)>;
const std::string textureName = Gltf::StringUtils::GetFileBaseString(texture.name);
const std::string relativeFilename = Gltf::StringUtils::GetFileNameString(texture.fileLocation);
ImageData *source = nullptr; auto texIndicesKey = [&](std::vector<int> ixVec, std::string tag) -> std::string {
std::string result = tag;
for (int ix : ixVec) {
result += "_" + std::to_string(ix);
}
return result;
};
/**
* Create a new derived TextureData for the two given RawTexture indexes, or return a previously created one.
* Each pixel in the derived texture will be determined from its equivalent in each source pixels, as decided
* by the provided `combine` function.
*/
auto getDerivedTexture = [&](
std::vector<int> rawTexIndices,
const pixel_merger &combine,
const std::string &tag,
bool transparentOutput
) -> std::shared_ptr<TextureData>
{
const std::string key = texIndicesKey(rawTexIndices, tag);
auto iter = textureByIndicesKey.find(key);
if (iter != textureByIndicesKey.end()) {
return iter->second;
}
auto describeChannel = [&](int channels) -> std::string {
switch(channels) {
case 1: return "G";
case 2: return "GA";
case 3: return "RGB";
case 4: return "RGBA";
default:
return fmt::format("?%d?", channels);
}
};
// keep track of some texture data as we load them
struct TexInfo {
explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {}
const int rawTexIx;
int width {};
int height {};
int channels {};
uint8_t *pixels {};
};
int width = -1, height = -1;
std::string mergedName = tag;
std::string mergedFilename = tag;
std::vector<TexInfo> texes { };
for (const int rawTexIx : rawTexIndices) {
TexInfo info(rawTexIx);
if (rawTexIx >= 0) {
const RawTexture &rawTex = raw.GetTexture(rawTexIx);
const std::string &fileLoc = rawTex.fileLocation;
const std::string &fileLocBase = Gltf::StringUtils::GetFileBaseString(Gltf::StringUtils::GetFileNameString(fileLoc));
if (!fileLoc.empty()) {
info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0);
if (!info.pixels) {
fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n",
rawTexIx,
Gltf::StringUtils::GetFileBaseString(Gltf::StringUtils::GetFileNameString(fileLoc)));
} else {
if (width < 0) {
width = info.width;
height = info.height;
} else if (width != info.width || height != info.height) {
fmt::printf("Warning: texture %s (%d, %d) can't be merged with previous texture(s) of dimension (%d, %d)\n",
Gltf::StringUtils::GetFileBaseString(Gltf::StringUtils::GetFileNameString(fileLoc)),
info.width, info.height, width, height);
// this is bad enough that we abort the whole merge
return nullptr;
}
mergedName += "_" + rawTex.fileName;
mergedFilename += "_" + fileLocBase;
}
}
}
texes.push_back(info);
}
if (width < 0) {
// no textures to merge; bail
return nullptr;
}
// TODO: which channel combinations make sense in input files?
// write 3 or 4 channels depending on whether or not we need transparency
int channels = transparentOutput ? 4 : 3;
std::vector<uint8_t> mergedPixels(static_cast<size_t>(channels * width * height));
std::vector<pixel> pixels(texes.size());
std::vector<const pixel *> pixelPointers(texes.size());
for (int xx = 0; xx < width; xx ++) {
for (int yy = 0; yy < height; yy ++) {
pixels.clear();
for (int jj = 0; jj < texes.size(); jj ++) {
const TexInfo &tex = texes[jj];
// each texture's structure will depend on its channel count
int ii = tex.channels * (xx + yy*width);
int kk = 0;
if (tex.pixels != nullptr) {
for (; kk < tex.channels; kk ++) {
pixels[jj][kk] = tex.pixels[ii++] / 255.0f;
}
}
for (; kk < pixels[jj].size(); kk ++) {
pixels[jj][kk] = 1.0f;
}
pixelPointers[jj] = &pixels[jj];
}
const pixel merged = combine(pixelPointers);
int ii = channels * (xx + yy*width);
for (int jj = 0; jj < channels; jj ++) {
mergedPixels[ii + jj] = static_cast<uint8_t>(fmax(0, fmin(255.0f, merged[jj] * 255.0f)));
}
}
}
// write a .png iff we need transparency in the destination texture
bool png = transparentOutput;
std::vector<char> imgBuffer;
int res;
if (png) {
res = stbi_write_png_to_func(WriteToVectorContext, &imgBuffer,
width, height, channels, mergedPixels.data(), width * channels);
} else {
res = stbi_write_jpg_to_func(WriteToVectorContext, &imgBuffer,
width, height, channels, mergedPixels.data(), 80);
}
if (!res) {
fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename);
return nullptr;
}
ImageData *image;
if (options.outputBinary) { if (options.outputBinary) {
auto bufferView = gltf->AddBufferViewForFile(buffer, texture.fileLocation); const auto bufferView = gltf->AddRawBufferView(buffer, imgBuffer.data(), imgBuffer.size());
image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
} else {
const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg");
const std::string imagePath = outputFolder + imageFilename;
FILE *fp = fopen(imagePath.c_str(), "wb");
if (fp == nullptr) {
fmt::printf("Warning:: Couldn't write file '%s' for writing.\n", imagePath);
return nullptr;
}
if (fwrite(imgBuffer.data(), imgBuffer.size(), 1, fp) != 1) {
fmt::printf("Warning: Failed to write %lu bytes to file '%s'.\n", imgBuffer.size(), imagePath);
fclose(fp);
return nullptr;
}
fclose(fp);
if (verboseOutput) {
fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath);
}
image = new ImageData(mergedName, imageFilename);
}
std::shared_ptr<TextureData> texDat = gltf->textures.hold(
new TextureData(mergedName, defaultSampler, *gltf->images.hold(image)));
textureByIndicesKey.insert(std::make_pair(key, texDat));
return texDat;
};
/** Create a new TextureData for the given RawTexture index, or return a previously created one. */
auto getSimpleTexture = [&](int rawTexIndex, const std::string &tag) {
const std::string key = texIndicesKey({ rawTexIndex }, tag);
auto iter = textureByIndicesKey.find(key);
if (iter != textureByIndicesKey.end()) {
return iter->second;
}
const RawTexture &rawTexture = raw.GetTexture(rawTexIndex);
const std::string textureName = Gltf::StringUtils::GetFileBaseString(rawTexture.name);
const std::string relativeFilename = Gltf::StringUtils::GetFileNameString(rawTexture.fileLocation);
ImageData *image = nullptr;
if (options.outputBinary) {
auto bufferView = gltf->AddBufferViewForFile(buffer, rawTexture.fileLocation);
if (bufferView) { if (bufferView) {
std::string suffix = Gltf::StringUtils::GetFileSuffixString(texture.fileLocation); std::string suffix = Gltf::StringUtils::GetFileSuffixString(rawTexture.fileLocation);
source = new ImageData(relativeFilename, *bufferView, suffixToMimeType(suffix)); image = new ImageData(relativeFilename, *bufferView, suffixToMimeType(suffix));
} }
} else if (!relativeFilename.empty()) { } else if (!relativeFilename.empty()) {
source = new ImageData(relativeFilename, relativeFilename); image = new ImageData(relativeFilename, relativeFilename);
std::string outputPath = outputFolder + relativeFilename; std::string outputPath = outputFolder + relativeFilename;
if (FileUtils::CopyFile(texture.fileLocation, outputPath)) { if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath)) {
if (verboseOutput) { if (verboseOutput) {
fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath); fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath);
} }
@ -396,15 +599,16 @@ ModelData *Raw2Gltf(
// reference, even if the copy failed. // reference, even if the copy failed.
} }
} }
if (!source) { if (!image) {
// fallback is tiny transparent gif // fallback is tiny transparent gif
source = new ImageData(textureName, "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="); image = new ImageData(textureName, "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=");
} }
const TextureData &texDat = *gltf->textures.hold( std::shared_ptr<TextureData> texDat = gltf->textures.hold(
new TextureData(textureName, defaultSampler, *gltf->images.hold(source))); new TextureData(textureName, defaultSampler, *gltf->images.hold(image)));
assert(texDat.ix == textureIndex); textureByIndicesKey.insert(std::make_pair(key, texDat));
} return texDat;
};
// //
// materials // materials
@ -416,42 +620,290 @@ ModelData *Raw2Gltf(
material.type == RAW_MATERIAL_TYPE_TRANSPARENT || material.type == RAW_MATERIAL_TYPE_TRANSPARENT ||
material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT; material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT;
// find a texture by usage and return it as a TextureData*, or nullptr if none exists. Vec3f emissiveFactor;
auto getTex = [&](RawTextureUsage usage) float emissiveIntensity;
{
// note that we depend on TextureData.ix == rawTexture's index const Vec3f dielectric(0.04f, 0.04f, 0.04f);
return (material.textures[usage] >= 0) ? gltf->textures.ptrs[material.textures[usage]].get() : nullptr; const float epsilon = 1e-6f;
// acquire the texture of a specific RawTextureUsage as *TextData, or nullptr if none exists
auto simpleTex = [&](RawTextureUsage usage) -> std::shared_ptr<TextureData> {
return (material.textures[usage] >= 0) ? getSimpleTexture(material.textures[usage], "simple") : nullptr;
};
// acquire derived texture of two RawTextureUsage as *TextData, or nullptr if neither exists
auto merge2Tex = [&](
const std::string tag,
RawTextureUsage u1,
RawTextureUsage u2,
const pixel_merger &combine,
bool outputHasAlpha
) -> std::shared_ptr<TextureData> {
return getDerivedTexture(
{ material.textures[u1], material.textures[u2] },
combine, tag, outputHasAlpha);
};
// acquire derived texture of two RawTextureUsage as *TextData, or nullptr if neither exists
auto merge3Tex = [&](
const std::string tag,
RawTextureUsage u1,
RawTextureUsage u2,
RawTextureUsage u3,
const pixel_merger &combine,
bool outputHasAlpha
) -> std::shared_ptr<TextureData> {
return getDerivedTexture(
{ material.textures[u1], material.textures[u2], material.textures[u3] },
combine, tag, outputHasAlpha);
};
auto getMaxComponent = [&](const Vec3f &color) {
return fmax(color.x, fmax(color.y, color.z));
};
auto getPerceivedBrightness = [&](const Vec3f &color) {
return sqrt(0.299 * color.x * color.x + 0.587 * color.y * color.y + 0.114 * color.z * color.z);
};
auto toVec3f = [&](const pixel &pix) -> const Vec3f {
return Vec3f(pix[0], pix[1], pix[2]);
};
auto toVec4f = [&](const pixel &pix) -> const Vec4f {
return Vec4f(pix[0], pix[1], pix[2], pix[3]);
}; };
std::shared_ptr<PBRMetallicRoughness> pbrMetRough; std::shared_ptr<PBRMetallicRoughness> pbrMetRough;
if (options.usePBRMetRough) { if (options.usePBRMetRough) {
pbrMetRough.reset(new PBRMetallicRoughness(getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor)); // albedo is a basic texture, no merging needed
std::shared_ptr<TextureData> baseColorTex, metRoughTex;
Vec4f diffuseFactor;
float metallic, roughness;
if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
/**
* PBR FBX Material -> PBR Met/Rough glTF.
*
* METALLIC and ROUGHNESS textures are packed in G and B channels of a rough/met texture.
* Other values translate directly.
*/
RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get();
// merge metallic into the blue channel and roughness into the green, of a new combinatory texture
metRoughTex = merge2Tex("met_rough",
RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_ROUGHNESS,
[&](const std::vector<const pixel *> pixels) -> pixel { return { 0, (*pixels[1])[0], (*pixels[0])[0], 0 }; },
false);
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
diffuseFactor = props->diffuseFactor;
metallic = props->metallic;
roughness = props->roughness;
emissiveFactor = props->emissiveFactor;
emissiveIntensity = props->emissiveIntensity;
} else {
/**
* Traditional FBX Material -> PBR Met/Rough glTF.
*
* Diffuse channel is used as base colour. No metallic/roughness texture is attempted. Constant
* metallic/roughness values are hard-coded.
*
* TODO: If we have specular & optional shininess map, we can generate a reasonable rough/met map.
*/
const RawTraditionalMatProps *props = ((RawTraditionalMatProps *) material.info.get());
diffuseFactor = props->diffuseFactor;
// TODO: make configurable on the command line, or do a better job guessing from other, supplied params
if (material.info->shadingModel == RAW_SHADING_MODEL_LAMBERT) {
metallic = 0.6f;
roughness = 1.0f;
} else {
metallic = 0.2f;
roughness = 0.6f;
} }
auto solveMetallic = [&](float pDiff, float pSpec, float oneMinusSpecularStrength)
{
if (pSpec < dielectric.x) {
return 0.0;
}
float a = dielectric.x;
float b = pDiff * oneMinusSpecularStrength / (1 - dielectric.x) + pSpec - 2 * dielectric.x;
float c = dielectric.x - pSpec;
float D = fmax(b * b - 4 * a * c, 0);
return fmax(0.0, fmin(1.0, (-b + sqrt(D)) / (2 * a)));
};
metRoughTex = merge3Tex("rough_met",
RAW_TEXTURE_USAGE_SPECULAR, RAW_TEXTURE_USAGE_SHININESS, RAW_TEXTURE_USAGE_DIFFUSE,
[&](const std::vector<const pixel *> pixels) -> pixel {
const Vec3f specular = pixels[0] ? toVec3f(*pixels[0]) : props->specularFactor;
float shininess = pixels[1] ? (*pixels[1])[0] : props->shininess;
const Vec4f diffuse = pixels[2] ? toVec4f(*pixels[2]) : props->diffuseFactor;
float pixelMet = solveMetallic(
getPerceivedBrightness(diffuse.xyz()),
getPerceivedBrightness(specular),
1 - getMaxComponent(specular));
float pixelRough = 1 - shininess;
return { 0, pixelRough, pixelMet, 0 };
}, false);
if (material.textures[RAW_TEXTURE_USAGE_DIFFUSE] >= 0) {
const RawTexture &diffuseTex = raw.GetTexture(material.textures[RAW_TEXTURE_USAGE_DIFFUSE]);
baseColorTex = merge2Tex("base_col", RAW_TEXTURE_USAGE_DIFFUSE, RAW_TEXTURE_USAGE_SPECULAR,
[&](const std::vector<const pixel *> pixels) -> pixel {
const Vec4f diffuse = pixels[0] ? toVec4f(*pixels[0]) : props->diffuseFactor;
const Vec3f specular = pixels[1] ? toVec3f(*pixels[1]) : props->specularFactor;
float oneMinus = 1 - getMaxComponent(specular);
float pixelMet = solveMetallic(
getPerceivedBrightness(diffuse.xyz()),
getPerceivedBrightness(specular),
oneMinus);
Vec3f fromDiffuse = diffuse.xyz() * (oneMinus / (1.0f - dielectric.x) / fmax(1.0f - pixelMet, epsilon));
Vec3f fromSpecular = specular - dielectric * (1.0f - pixelMet) * (1.0f / fmax(pixelMet, epsilon));
Vec3f baseColor = Vec3f::Lerp(fromDiffuse, fromSpecular, pixelMet * pixelMet);
return { baseColor[0], baseColor[1], baseColor[2], diffuse[3] };
}, diffuseTex.occlusion == RAW_TEXTURE_OCCLUSION_TRANSPARENT);
}
emissiveFactor = props->emissiveFactor;
emissiveIntensity = 1.0f;
}
pbrMetRough.reset(new PBRMetallicRoughness(baseColorTex.get(), metRoughTex.get(), diffuseFactor, metallic, roughness));
}
std::shared_ptr<PBRSpecularGlossiness> pbrSpecGloss; std::shared_ptr<PBRSpecularGlossiness> pbrSpecGloss;
if (options.usePBRSpecGloss) { if (options.usePBRSpecGloss) {
Vec4f diffuseFactor;
Vec3f specularFactor;
float glossiness;
std::shared_ptr<TextureData> specGlossTex;
std::shared_ptr<TextureData> diffuseTex;
const Vec3f dielectric(0.04f, 0.04f, 0.04f);
const float epsilon = 1e-6f;
if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
/**
* PBR FBX Material -> PBR Spec/Gloss glTF.
*
* TODO: A complete mess. Low priority.
*/
RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get();
// we can estimate spec/gloss from met/rough by between Vec4f(0.04, 0.04, 0.04) and baseColor
// according to metalness, and then taking gloss to be the inverse of roughness
specGlossTex = merge3Tex("specgloss",
RAW_TEXTURE_USAGE_ALBEDO, RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_ROUGHNESS,
[&](const std::vector<const pixel *> pixels) -> pixel {
Vec3f baseColor(1.0f);
if (pixels[0]) {
baseColor.x = (*pixels[0])[0];
baseColor.y = (*pixels[0])[1];
baseColor.z = (*pixels[0])[2];
}
float metallic = pixels[1] ? (*pixels[1])[0] : 1.0f;
float roughness = pixels[2] ? (*pixels[2])[0] : 1.0f;
Vec3f spec = Vec3f::Lerp(dielectric, baseColor, metallic);
return { spec[0], spec[1], spec[2], 1.0f - roughness };
}, false);
diffuseTex = merge2Tex("albedo",
RAW_TEXTURE_USAGE_ALBEDO, RAW_TEXTURE_USAGE_METALLIC,
[&](const std::vector<const pixel *> pixels) -> pixel {
Vec3f baseColor(1.0f);
float alpha = 1.0f;
if (pixels[0]) {
baseColor[0] = (*pixels[0])[0];
baseColor[1] = (*pixels[0])[1];
baseColor[2] = (*pixels[0])[2];
alpha = (*pixels[0])[3];
}
float metallic = pixels[1] ? (*pixels[1])[0] : 1.0f;
Vec3f spec = Vec3f::Lerp(dielectric, baseColor, metallic);
float maxSpecComp = fmax(fmax(spec.x, spec.y), spec.z);
// attenuate baseColor to get specgloss-compliant diffuse; for details consult
// https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_pbrSpecularGlossiness
Vec3f diffuse = baseColor * (1 - dielectric[0]) * (1 - metallic) * fmax(1.0f - maxSpecComp, epsilon);
return { diffuse[0], diffuse[1], diffuse[2], alpha };
}, true);
diffuseFactor = props->diffuseFactor;
specularFactor = Vec3f::Lerp(dielectric, props->diffuseFactor.xyz(), props->metallic);
glossiness = 1.0f - props->roughness;
} else {
/**
* Traditional FBX Material -> PBR Spec/Gloss glTF.
*
* TODO: A complete mess. Low priority.
*/
// TODO: this section a ludicrous over-simplifictation; we can surely do better.
const RawTraditionalMatProps *props = ((RawTraditionalMatProps *) material.info.get());
specGlossTex = merge2Tex("specgloss",
RAW_TEXTURE_USAGE_SPECULAR, RAW_TEXTURE_USAGE_SHININESS,
[&](const std::vector<const pixel *> pixels) -> pixel {
const auto &spec = *(pixels[0]);
const auto &shine = *(pixels[1]);
return { spec[0], spec[1], spec[2], shine[0] };
}, false);
diffuseTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
diffuseFactor = props->diffuseFactor;
specularFactor = props->specularFactor;
glossiness = props->shininess;
}
pbrSpecGloss.reset( pbrSpecGloss.reset(
new PBRSpecularGlossiness( new PBRSpecularGlossiness(
getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor, diffuseTex.get(), diffuseFactor, specGlossTex.get(), specularFactor, glossiness));
getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor, material.shininess));
} }
std::shared_ptr<KHRCommonMats> khrComMat; std::shared_ptr<KHRCommonMats> khrComMat;
if (options.useKHRMatCom) { if (options.useKHRMatCom) {
float shininess;
Vec3f ambientFactor, specularFactor;
Vec4f diffuseFactor;
std::shared_ptr<TextureData> diffuseTex;
auto type = KHRCommonMats::MaterialType::Constant; auto type = KHRCommonMats::MaterialType::Constant;
if (material.shadingModel == "Lambert") {
if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
/**
* PBR FBX Material -> KHR Common Materials glTF.
*
* TODO: We can use the specularFactor calculation below to generate a reasonable specular map, too.
*/
const RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get();
shininess = 1.0f - props->roughness;
ambientFactor = Vec3f(0.0f, 0.0f, 0.0f);
diffuseTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
diffuseFactor = props->diffuseFactor;
specularFactor = Vec3f::Lerp(Vec3f(0.04f, 0.04f, 0.04f), props->diffuseFactor.xyz(), props->metallic);
// render as Phong if there's any specularity, otherwise Lambert
type = (specularFactor.LengthSquared() > 1e-4) ?
KHRCommonMats::MaterialType::Phong : KHRCommonMats::MaterialType::Lambert;
// TODO: convert textures
} else {
/**
* Traditional FBX Material -> KHR Common Materials glTF.
*
* Should be in good shape. Essentially pass-through.
*/
const RawTraditionalMatProps *props = (RawTraditionalMatProps *) material.info.get();
shininess = props->shininess;
ambientFactor = props->ambientFactor;
diffuseTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
diffuseFactor = props->diffuseFactor;
specularFactor = props->specularFactor;
if (material.info->shadingModel == RAW_SHADING_MODEL_LAMBERT) {
type = KHRCommonMats::MaterialType::Lambert; type = KHRCommonMats::MaterialType::Lambert;
} else if (material.shadingModel == "Blinn") { } else if (material.info->shadingModel == RAW_SHADING_MODEL_BLINN) {
type = KHRCommonMats::MaterialType::Blinn; type = KHRCommonMats::MaterialType::Blinn;
} else if (material.shadingModel == "Phong") { } else if (material.info->shadingModel == RAW_SHADING_MODEL_PHONG) {
type = KHRCommonMats::MaterialType::Phong; type = KHRCommonMats::MaterialType::Phong;
} }
}
khrComMat.reset( khrComMat.reset(
new KHRCommonMats( new KHRCommonMats(type,
type, simpleTex(RAW_TEXTURE_USAGE_SHININESS).get(), shininess,
getTex(RAW_TEXTURE_USAGE_SHININESS), material.shininess, simpleTex(RAW_TEXTURE_USAGE_AMBIENT).get(), ambientFactor,
getTex(RAW_TEXTURE_USAGE_AMBIENT), material.ambientFactor, diffuseTex.get(), diffuseFactor,
getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor, simpleTex(RAW_TEXTURE_USAGE_SPECULAR).get(), specularFactor));
getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor));
} }
std::shared_ptr<KHRCmnConstantMaterial> khrCmnConstantMat; std::shared_ptr<KHRCmnConstantMaterial> khrCmnConstantMat;
@ -464,38 +916,27 @@ ModelData *Raw2Gltf(
std::shared_ptr<MaterialData> mData = gltf->materials.hold( std::shared_ptr<MaterialData> mData = gltf->materials.hold(
new MaterialData( new MaterialData(
material.name, isTransparent, getTex(RAW_TEXTURE_USAGE_NORMAL), material.name, isTransparent,
getTex(RAW_TEXTURE_USAGE_EMISSIVE), material.emissiveFactor, simpleTex(RAW_TEXTURE_USAGE_NORMAL).get(), simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get(),
emissiveFactor * emissiveIntensity,
khrComMat, khrCmnConstantMat, pbrMetRough, pbrSpecGloss)); khrComMat, khrCmnConstantMat, pbrMetRough, pbrSpecGloss));
materialsByName[materialHash(material)] = mData; materialsByName[materialHash(material)] = mData;
} }
//
// surfaces
//
// in GLTF 2.0, the structural limitation is that a node can
// only belong to a single mesh. A mesh can however contain any
// number of primitives, which are essentially little meshes.
//
// so each RawSurface turns into a primitive, and we sort them
// by root node using this map; one mesh per node.
for (size_t surfaceIndex = 0; surfaceIndex < materialModels.size(); surfaceIndex++) { for (size_t surfaceIndex = 0; surfaceIndex < materialModels.size(); surfaceIndex++) {
const RawModel &surfaceModel = materialModels[surfaceIndex]; const RawModel &surfaceModel = materialModels[surfaceIndex];
assert(surfaceModel.GetSurfaceCount() == 1); assert(surfaceModel.GetSurfaceCount() == 1);
const RawSurface &rawSurface = surfaceModel.GetSurface(0); const RawSurface &rawSurface = surfaceModel.GetSurface(0);
const int surfaceId = rawSurface.id;
const RawMaterial &rawMaterial = surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex); const RawMaterial &rawMaterial = surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex);
const MaterialData &mData = require(materialsByName, materialHash(rawMaterial)); const MaterialData &mData = require(materialsByName, materialHash(rawMaterial));
std::string nodeName = rawSurface.nodeName;
NodeData &meshNode = require(nodesByName, nodeName);
MeshData *mesh = nullptr; MeshData *mesh = nullptr;
auto meshIter = meshByNodeName.find(nodeName); auto meshIter = meshBySurfaceId.find(surfaceId);
if (meshIter != meshByNodeName.end()) { if (meshIter != meshBySurfaceId.end()) {
mesh = meshIter->second.get(); mesh = meshIter->second.get();
} else { } else {
@ -504,36 +945,10 @@ ModelData *Raw2Gltf(
defaultDeforms.push_back(channel.defaultDeform); defaultDeforms.push_back(channel.defaultDeform);
} }
auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name, defaultDeforms)); auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name, defaultDeforms));
meshByNodeName[nodeName] = meshPtr; meshBySurfaceId[surfaceId] = meshPtr;
meshNode.SetMesh(meshPtr->ix);
mesh = meshPtr.get(); mesh = meshPtr.get();
} }
//
// surface skin
//
if (!rawSurface.jointNames.empty()) {
if (meshNode.skin == -1) {
// glTF uses column-major matrices
std::vector<Mat4f> inverseBindMatrices;
for (const auto &inverseBindMatrice : rawSurface.inverseBindMatrices) {
inverseBindMatrices.push_back(inverseBindMatrice.Transpose());
}
std::vector<uint32_t> jointIndexes;
for (const auto &jointName : rawSurface.jointNames) {
jointIndexes.push_back(require(nodesByName, jointName).ix);
}
// Write out inverseBindMatrices
auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices);
auto skeletonRoot = require(nodesByName, rawSurface.skeletonRootName);
auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot));
meshNode.SetSkin(skin.ix);
}
}
std::shared_ptr<PrimitiveData> primitive; std::shared_ptr<PrimitiveData> primitive;
if (options.useDraco) { if (options.useDraco) {
int triangleCount = surfaceModel.GetTriangleCount(); int triangleCount = surfaceModel.GetTriangleCount();
@ -674,6 +1089,53 @@ ModelData *Raw2Gltf(
mesh->AddPrimitive(primitive); mesh->AddPrimitive(primitive);
} }
//
// Assign meshes to node
//
for (int i = 0; i < raw.GetNodeCount(); i++) {
const RawNode &node = raw.GetNode(i);
auto nodeData = gltf->nodes.ptrs[i];
//
// Assign mesh to node
//
if (node.surfaceId > 0)
{
int surfaceIndex = raw.GetSurfaceById(node.surfaceId);
const RawSurface &rawSurface = raw.GetSurface(surfaceIndex);
MeshData &meshData = require(meshBySurfaceId, rawSurface.id);
nodeData->SetMesh(meshData.ix);
//
// surface skin
//
if (!rawSurface.jointIds.empty()) {
if (nodeData->skin == -1) {
// glTF uses column-major matrices
std::vector<Mat4f> inverseBindMatrices;
for (const auto &inverseBindMatrice : rawSurface.inverseBindMatrices) {
inverseBindMatrices.push_back(inverseBindMatrice.Transpose());
}
std::vector<uint32_t> jointIndexes;
for (const auto &jointId : rawSurface.jointIds) {
jointIndexes.push_back(require(nodesById, jointId).ix);
}
// Write out inverseBindMatrices
auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices);
auto skeletonRoot = require(nodesById, rawSurface.skeletonRootId);
auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot));
nodeData->SetSkin(skin.ix);
}
}
}
}
// //
// cameras // cameras
// //
@ -698,16 +1160,16 @@ ModelData *Raw2Gltf(
} }
// Add the camera to the node hierarchy. // Add the camera to the node hierarchy.
auto iter = nodesByName.find(cam.nodeName); auto iter = nodesById.find(cam.nodeId);
if (iter == nodesByName.end()) { if (iter == nodesById.end()) {
fmt::printf("Warning: Camera node name %s does not exist.\n", cam.nodeName); fmt::printf("Warning: Camera node id %s does not exist.\n", cam.nodeId);
continue; continue;
} }
iter->second->AddCamera(cam.name); iter->second->SetCamera(camera.ix);
} }
} }
NodeData &rootNode = require(nodesByName, "RootNode"); NodeData &rootNode = require(nodesById, raw.GetRootNode());
const SceneData &rootScene = *gltf->scenes.hold(new SceneData(defaultSceneName, rootNode)); const SceneData &rootScene = *gltf->scenes.hold(new SceneData(defaultSceneName, rootNode));
if (options.outputBinary) { if (options.outputBinary) {

View File

@ -111,35 +111,25 @@ int RawModel::AddTexture(const std::string &name, const std::string &fileName, c
int RawModel::AddMaterial(const RawMaterial &material) int RawModel::AddMaterial(const RawMaterial &material)
{ {
return AddMaterial( return AddMaterial(material.name.c_str(), material.type, material.textures, material.info);
material.name.c_str(), material.shadingModel.c_str(), material.type, material.textures, material.ambientFactor,
material.diffuseFactor, material.specularFactor, material.emissiveFactor, material.shininess);
} }
int RawModel::AddMaterial( int RawModel::AddMaterial(
const char *name, const char *shadingModel, const RawMaterialType materialType, const char *name,
const int textures[RAW_TEXTURE_USAGE_MAX], const Vec3f ambientFactor, const RawMaterialType materialType,
const Vec4f diffuseFactor, const Vec3f specularFactor, const int textures[RAW_TEXTURE_USAGE_MAX],
const Vec3f emissiveFactor, float shinineness) std::shared_ptr<RawMatProps> materialInfo)
{ {
for (size_t i = 0; i < materials.size(); i++) { for (size_t i = 0; i < materials.size(); i++) {
if (materials[i].name != name) { if (materials[i].name != name) {
continue; continue;
} }
if (materials[i].shadingModel != shadingModel) {
continue;
}
if (materials[i].type != materialType) { if (materials[i].type != materialType) {
continue; continue;
} }
if (materials[i].ambientFactor != ambientFactor || if (*(materials[i].info) != *materialInfo) {
materials[i].diffuseFactor != diffuseFactor ||
materials[i].specularFactor != specularFactor ||
materials[i].emissiveFactor != emissiveFactor ||
materials[i].shininess != shinineness) {
continue; continue;
} }
bool match = true; bool match = true;
for (int j = 0; match && j < RAW_TEXTURE_USAGE_MAX; j++) { for (int j = 0; match && j < RAW_TEXTURE_USAGE_MAX; j++) {
match = match && (materials[i].textures[j] == textures[j]); match = match && (materials[i].textures[j] == textures[j]);
@ -151,13 +141,8 @@ int RawModel::AddMaterial(
RawMaterial material; RawMaterial material;
material.name = name; material.name = name;
material.shadingModel = shadingModel;
material.type = materialType; material.type = materialType;
material.ambientFactor = ambientFactor; material.info = materialInfo;
material.diffuseFactor = diffuseFactor;
material.specularFactor = specularFactor;
material.emissiveFactor = emissiveFactor;
material.shininess = shinineness;
for (int i = 0; i < RAW_TEXTURE_USAGE_MAX; i++) { for (int i = 0; i < RAW_TEXTURE_USAGE_MAX; i++) {
material.textures[i] = textures[i]; material.textures[i] = textures[i];
@ -180,18 +165,18 @@ int RawModel::AddSurface(const RawSurface &surface)
return (int) (surfaces.size() - 1); return (int) (surfaces.size() - 1);
} }
int RawModel::AddSurface(const char *name, const char *nodeName) int RawModel::AddSurface(const char *name, const long surfaceId)
{ {
assert(name[0] != '\0'); assert(name[0] != '\0');
for (size_t i = 0; i < surfaces.size(); i++) { for (size_t i = 0; i < surfaces.size(); i++) {
if (Gltf::StringUtils::CompareNoCase(surfaces[i].name, name) == 0) { if (surfaces[i].id == surfaceId) {
return (int) i; return (int) i;
} }
} }
RawSurface surface; RawSurface surface;
surface.id = surfaceId;
surface.name = name; surface.name = name;
surface.nodeName = nodeName;
surface.bounds.Clear(); surface.bounds.Clear();
surface.discrete = false; surface.discrete = false;
@ -208,7 +193,7 @@ int RawModel::AddAnimation(const RawAnimation &animation)
int RawModel::AddNode(const RawNode &node) int RawModel::AddNode(const RawNode &node)
{ {
for (size_t i = 0; i < nodes.size(); i++) { for (size_t i = 0; i < nodes.size(); i++) {
if (Gltf::StringUtils::CompareNoCase(nodes[i].name.c_str(), node.name) == 0) { if (nodes[i].id == node.id) {
return (int)i; return (int)i;
} }
} }
@ -218,12 +203,12 @@ int RawModel::AddNode(const RawNode &node)
} }
int RawModel::AddCameraPerspective( int RawModel::AddCameraPerspective(
const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ, const char *name, const long nodeId, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ,
const float farZ) const float farZ)
{ {
RawCamera camera; RawCamera camera;
camera.name = name; camera.name = name;
camera.nodeName = nodeName; camera.nodeId = nodeId;
camera.mode = RawCamera::CAMERA_MODE_PERSPECTIVE; camera.mode = RawCamera::CAMERA_MODE_PERSPECTIVE;
camera.perspective.aspectRatio = aspectRatio; camera.perspective.aspectRatio = aspectRatio;
camera.perspective.fovDegreesX = fovDegreesX; camera.perspective.fovDegreesX = fovDegreesX;
@ -235,11 +220,11 @@ int RawModel::AddCameraPerspective(
} }
int RawModel::AddCameraOrthographic( int RawModel::AddCameraOrthographic(
const char *name, const char *nodeName, const float magX, const float magY, const float nearZ, const float farZ) const char *name, const long nodeId, const float magX, const float magY, const float nearZ, const float farZ)
{ {
RawCamera camera; RawCamera camera;
camera.name = name; camera.name = name;
camera.nodeName = nodeName; camera.nodeId = nodeId;
camera.mode = RawCamera::CAMERA_MODE_ORTHOGRAPHIC; camera.mode = RawCamera::CAMERA_MODE_ORTHOGRAPHIC;
camera.orthographic.magX = magX; camera.orthographic.magX = magX;
camera.orthographic.magY = magY; camera.orthographic.magY = magY;
@ -249,20 +234,22 @@ int RawModel::AddCameraOrthographic(
return (int) cameras.size() - 1; return (int) cameras.size() - 1;
} }
int RawModel::AddNode(const char *name, const char *parentName) int RawModel::AddNode(const long id, const char *name, const long parentId)
{ {
assert(name[0] != '\0'); assert(name[0] != '\0');
for (size_t i = 0; i < nodes.size(); i++) { for (size_t i = 0; i < nodes.size(); i++) {
if (Gltf::StringUtils::CompareNoCase(nodes[i].name, name) == 0) { if (nodes[i].id == id ) {
return (int) i; return (int) i;
} }
} }
RawNode joint; RawNode joint;
joint.isJoint = false; joint.isJoint = false;
joint.id = id;
joint.name = name; joint.name = name;
joint.parentName = parentName; joint.parentId = parentId;
joint.surfaceId = 0;
joint.translation = Vec3f(0, 0, 0); joint.translation = Vec3f(0, 0, 0);
joint.rotation = Quatf(0, 0, 0, 1); joint.rotation = Quatf(0, 0, 0, 1);
joint.scale = Vec3f(1, 1, 1); joint.scale = Vec3f(1, 1, 1);
@ -281,7 +268,7 @@ void RawModel::Condense()
for (auto &triangle : triangles) { for (auto &triangle : triangles) {
const RawSurface &surface = oldSurfaces[triangle.surfaceIndex]; const RawSurface &surface = oldSurfaces[triangle.surfaceIndex];
const int surfaceIndex = AddSurface(surface.name.c_str(), surface.nodeName.c_str()); const int surfaceIndex = AddSurface(surface.name.c_str(), surface.id);
surfaces[surfaceIndex] = surface; surfaces[surfaceIndex] = surface;
triangle.surfaceIndex = surfaceIndex; triangle.surfaceIndex = surfaceIndex;
} }
@ -469,9 +456,9 @@ void RawModel::CreateMaterialModels(
RawSurface &rawSurface = model->GetSurface(surfaceIndex); RawSurface &rawSurface = model->GetSurface(surfaceIndex);
if (model->GetSurfaceCount() > prevSurfaceCount) { if (model->GetSurfaceCount() > prevSurfaceCount) {
const std::vector<std::string> &jointNames = surfaces[sortedTriangles[i].surfaceIndex].jointNames; const std::vector<long> &jointIds = surfaces[sortedTriangles[i].surfaceIndex].jointIds;
for (const auto &jointName : jointNames) { for (const auto &jointId : jointIds) {
const int nodeIndex = GetNodeByName(jointName.c_str()); const int nodeIndex = GetNodeById(jointId);
assert(nodeIndex != -1); assert(nodeIndex != -1);
model->AddNode(GetNode(nodeIndex)); model->AddNode(GetNode(nodeIndex));
} }
@ -529,10 +516,20 @@ void RawModel::CreateMaterialModels(
} }
} }
int RawModel::GetNodeByName(const char *name) const int RawModel::GetNodeById(const long nodeId) const
{ {
for (size_t i = 0; i < nodes.size(); i++) { for (size_t i = 0; i < nodes.size(); i++) {
if (nodes[i].name == name) { if (nodes[i].id == nodeId) {
return (int) i;
}
}
return -1;
}
int RawModel::GetSurfaceById(const long surfaceId) const
{
for (size_t i = 0; i < surfaces.size(); i++) {
if (surfaces[i].id == surfaceId) {
return (int)i; return (int)i;
} }
} }

View File

@ -95,8 +95,32 @@ struct RawTriangle
int surfaceIndex; int surfaceIndex;
}; };
enum RawShadingModel
{
RAW_SHADING_MODEL_UNKNOWN = -1,
RAW_SHADING_MODEL_CONSTANT,
RAW_SHADING_MODEL_LAMBERT,
RAW_SHADING_MODEL_BLINN,
RAW_SHADING_MODEL_PHONG,
RAW_SHADING_MODEL_PBR_MET_ROUGH,
RAW_SHADING_MODEL_MAX
};
static inline std::string Describe(RawShadingModel model) {
switch(model) {
case RAW_SHADING_MODEL_UNKNOWN: return "<unknown>";
case RAW_SHADING_MODEL_CONSTANT: return "Constant";
case RAW_SHADING_MODEL_LAMBERT: return "Lambert";
case RAW_SHADING_MODEL_BLINN: return "Blinn";
case RAW_SHADING_MODEL_PHONG: return "Phong";
case RAW_SHADING_MODEL_PBR_MET_ROUGH: return "Metallic/Roughness";
case RAW_SHADING_MODEL_MAX: default: return "<unknown>";
}
}
enum RawTextureUsage enum RawTextureUsage
{ {
RAW_TEXTURE_USAGE_NONE = -1,
RAW_TEXTURE_USAGE_AMBIENT, RAW_TEXTURE_USAGE_AMBIENT,
RAW_TEXTURE_USAGE_DIFFUSE, RAW_TEXTURE_USAGE_DIFFUSE,
RAW_TEXTURE_USAGE_NORMAL, RAW_TEXTURE_USAGE_NORMAL,
@ -104,32 +128,28 @@ enum RawTextureUsage
RAW_TEXTURE_USAGE_SHININESS, RAW_TEXTURE_USAGE_SHININESS,
RAW_TEXTURE_USAGE_EMISSIVE, RAW_TEXTURE_USAGE_EMISSIVE,
RAW_TEXTURE_USAGE_REFLECTION, RAW_TEXTURE_USAGE_REFLECTION,
RAW_TEXTURE_USAGE_ALBEDO,
RAW_TEXTURE_USAGE_OCCLUSION,
RAW_TEXTURE_USAGE_ROUGHNESS,
RAW_TEXTURE_USAGE_METALLIC,
RAW_TEXTURE_USAGE_MAX RAW_TEXTURE_USAGE_MAX
}; };
inline std::string DescribeTextureUsage(int usage) static inline std::string Describe(RawTextureUsage usage)
{ {
if (usage < 0) { switch (usage) {
return "<none>"; case RAW_TEXTURE_USAGE_NONE: return "<none>";
} case RAW_TEXTURE_USAGE_AMBIENT: return "ambient";
switch (static_cast<RawTextureUsage>(usage)) { case RAW_TEXTURE_USAGE_DIFFUSE: return "diffuse";
case RAW_TEXTURE_USAGE_AMBIENT: case RAW_TEXTURE_USAGE_NORMAL: return "normal";
return "ambient"; case RAW_TEXTURE_USAGE_SPECULAR: return "specuar";
case RAW_TEXTURE_USAGE_DIFFUSE: case RAW_TEXTURE_USAGE_SHININESS: return "shininess";
return "diffuse"; case RAW_TEXTURE_USAGE_EMISSIVE: return "emissive";
case RAW_TEXTURE_USAGE_NORMAL: case RAW_TEXTURE_USAGE_REFLECTION: return "reflection";
return "normal"; case RAW_TEXTURE_USAGE_OCCLUSION: return "occlusion";
case RAW_TEXTURE_USAGE_SPECULAR: case RAW_TEXTURE_USAGE_ROUGHNESS: return "roughness";
return "specuar"; case RAW_TEXTURE_USAGE_METALLIC: return "metallic";
case RAW_TEXTURE_USAGE_SHININESS: case RAW_TEXTURE_USAGE_MAX:default: return "unknown";
return "shininess";
case RAW_TEXTURE_USAGE_EMISSIVE:
return "emissive";
case RAW_TEXTURE_USAGE_REFLECTION:
return "reflection";
case RAW_TEXTURE_USAGE_MAX:
default:
return "unknown";
} }
}; };
@ -159,17 +179,90 @@ enum RawMaterialType
RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT, RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT,
}; };
struct RawMatProps {
explicit RawMatProps(RawShadingModel shadingModel)
: shadingModel(shadingModel)
{}
const RawShadingModel shadingModel;
virtual bool operator!=(const RawMatProps &other) const { return !(*this == other); }
virtual bool operator==(const RawMatProps &other) const { return shadingModel == other.shadingModel; };
};
struct RawTraditionalMatProps : RawMatProps {
RawTraditionalMatProps(
RawShadingModel shadingModel,
const Vec3f &&ambientFactor,
const Vec4f &&diffuseFactor,
const Vec3f &&emissiveFactor,
const Vec3f &&specularFactor,
const float shininess
) : RawMatProps(shadingModel),
ambientFactor(ambientFactor),
diffuseFactor(diffuseFactor),
emissiveFactor(emissiveFactor),
specularFactor(specularFactor),
shininess(shininess)
{}
const Vec3f ambientFactor;
const Vec4f diffuseFactor;
const Vec3f emissiveFactor;
const Vec3f specularFactor;
const float shininess;
bool operator==(const RawMatProps &other) const override {
if (RawMatProps::operator==(other)) {
const auto &typed = (RawTraditionalMatProps &) other;
return ambientFactor == typed.ambientFactor &&
diffuseFactor == typed.diffuseFactor &&
specularFactor == typed.specularFactor &&
emissiveFactor == typed.emissiveFactor &&
shininess == typed.shininess;
}
return false;
}
};
struct RawMetRoughMatProps : RawMatProps {
RawMetRoughMatProps(
RawShadingModel shadingModel,
const Vec4f &&diffuseFactor,
const Vec3f &&emissiveFactor,
float emissiveIntensity,
float metallic,
float roughness
) : RawMatProps(shadingModel),
diffuseFactor(diffuseFactor),
emissiveFactor(emissiveFactor),
emissiveIntensity(emissiveIntensity),
metallic(metallic),
roughness(roughness)
{}
const Vec4f diffuseFactor;
const Vec3f emissiveFactor;
const float emissiveIntensity;
const float metallic;
const float roughness;
bool operator==(const RawMatProps &other) const override {
if (RawMatProps::operator==(other)) {
const auto &typed = (RawMetRoughMatProps &) other;
return diffuseFactor == typed.diffuseFactor &&
emissiveFactor == typed.emissiveFactor &&
emissiveIntensity == typed.emissiveIntensity &&
metallic == typed.metallic &&
roughness == typed.roughness;
}
return false;
}
};
struct RawMaterial struct RawMaterial
{ {
std::string name; std::string name;
std::string shadingModel; // typically "Surface", "Anisotropic", "Blinn", "Lambert", "Phong", "Phone E"
RawMaterialType type; RawMaterialType type;
Vec3f ambientFactor; std::shared_ptr<RawMatProps> info;
Vec4f diffuseFactor;
Vec3f specularFactor;
Vec3f emissiveFactor;
float shininess;
int textures[RAW_TEXTURE_USAGE_MAX]; int textures[RAW_TEXTURE_USAGE_MAX];
}; };
@ -182,11 +275,11 @@ struct RawBlendChannel
struct RawSurface struct RawSurface
{ {
long id;
std::string name; // The name of this surface std::string name; // The name of this surface
std::string nodeName; // The node that links to this surface. long skeletonRootId; // The id of the root node of the skeleton.
std::string skeletonRootName; // The name of the root of the skeleton.
Bounds<float, 3> bounds; Bounds<float, 3> bounds;
std::vector<std::string> jointNames; std::vector<long> jointIds;
std::vector<Vec3f> jointGeometryMins; std::vector<Vec3f> jointGeometryMins;
std::vector<Vec3f> jointGeometryMaxs; std::vector<Vec3f> jointGeometryMaxs;
std::vector<Mat4f> inverseBindMatrices; std::vector<Mat4f> inverseBindMatrices;
@ -213,7 +306,7 @@ struct RawAnimation
struct RawCamera struct RawCamera
{ {
std::string name; std::string name;
std::string nodeName; long nodeId;
enum enum
{ {
@ -242,12 +335,14 @@ struct RawCamera
struct RawNode struct RawNode
{ {
bool isJoint; bool isJoint;
long id;
std::string name; std::string name;
std::string parentName; long parentId;
std::vector<std::string> childNames; std::vector<long> childIds;
Vec3f translation; Vec3f translation;
Quatf rotation; Quatf rotation;
Vec3f scale; Vec3f scale;
long surfaceId;
}; };
class RawModel class RawModel
@ -262,22 +357,20 @@ public:
int AddTexture(const std::string &name, const std::string &fileName, const std::string &fileLocation, RawTextureUsage usage); int AddTexture(const std::string &name, const std::string &fileName, const std::string &fileLocation, RawTextureUsage usage);
int AddMaterial(const RawMaterial &material); int AddMaterial(const RawMaterial &material);
int AddMaterial( int AddMaterial(
const char *name, const char *shadingModel, RawMaterialType materialType, const char *name, const RawMaterialType materialType, const int textures[RAW_TEXTURE_USAGE_MAX],
const int textures[RAW_TEXTURE_USAGE_MAX], Vec3f ambientFactor, std::shared_ptr<RawMatProps> materialInfo);
Vec4f diffuseFactor, Vec3f specularFactor,
Vec3f emissiveFactor, float shinineness);
int AddSurface(const RawSurface &suface); int AddSurface(const RawSurface &suface);
int AddSurface(const char *name, const char *nodeName); int AddSurface(const char *name, long surfaceId);
int AddAnimation(const RawAnimation &animation); int AddAnimation(const RawAnimation &animation);
int AddCameraPerspective( int AddCameraPerspective(
const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const char *name, const long nodeId, const float aspectRatio, const float fovDegreesX, const float fovDegreesY,
const float nearZ, const float farZ); const float nearZ, const float farZ);
int int
AddCameraOrthographic(const char *name, const char *nodeName, const float magX, const float magY, const float nearZ, const float farZ); AddCameraOrthographic(const char *name, const long nodeId, const float magX, const float magY, const float nearZ, const float farZ);
int AddNode(const RawNode &node); int AddNode(const RawNode &node);
int AddNode(const char *name, const char *parentName); int AddNode(const long id, const char *name, const long parentId);
void SetRootNode(const char *name) { rootNodeName = name; } void SetRootNode(const long nodeId) { rootNodeId = nodeId; }
const char *GetRootNode() const { return rootNodeName.c_str(); } const long GetRootNode() const { return rootNodeId; }
// Remove unused vertices, textures or materials after removing vertex attributes, textures, materials or surfaces. // Remove unused vertices, textures or materials after removing vertex attributes, textures, materials or surfaces.
void Condense(); void Condense();
@ -307,6 +400,7 @@ public:
int GetSurfaceCount() const { return (int) surfaces.size(); } int GetSurfaceCount() const { return (int) surfaces.size(); }
const RawSurface &GetSurface(const int index) const { return surfaces[index]; } const RawSurface &GetSurface(const int index) const { return surfaces[index]; }
RawSurface &GetSurface(const int index) { return surfaces[index]; } RawSurface &GetSurface(const int index) { return surfaces[index]; }
int GetSurfaceById(const long id) const;
// Iterate over the animations. // Iterate over the animations.
int GetAnimationCount() const { return (int) animations.size(); } int GetAnimationCount() const { return (int) animations.size(); }
@ -320,7 +414,7 @@ public:
int GetNodeCount() const { return (int) nodes.size(); } int GetNodeCount() const { return (int) nodes.size(); }
const RawNode &GetNode(const int index) const { return nodes[index]; } const RawNode &GetNode(const int index) const { return nodes[index]; }
RawNode &GetNode(const int index) { return nodes[index]; } RawNode &GetNode(const int index) { return nodes[index]; }
int GetNodeByName(const char *name) const; int GetNodeById(const long nodeId) const;
// Create individual attribute arrays. // Create individual attribute arrays.
// Returns true if the vertices store the particular attribute. // Returns true if the vertices store the particular attribute.
@ -334,7 +428,7 @@ public:
std::vector<RawModel> &materialModels, const int maxModelVertices, const int keepAttribs, const bool forceDiscrete) const; std::vector<RawModel> &materialModels, const int maxModelVertices, const int keepAttribs, const bool forceDiscrete) const;
private: private:
std::string rootNodeName; long rootNodeId;
int vertexAttributes; int vertexAttributes;
std::unordered_map<RawVertex, int, VertexHasher> vertexHash; std::unordered_map<RawVertex, int, VertexHasher> vertexHash;
std::vector<RawVertex> vertices; std::vector<RawVertex> vertices;

View File

@ -135,10 +135,11 @@ void to_json(json &j, const PBRSpecularGlossiness &d)
} }
PBRMetallicRoughness::PBRMetallicRoughness( PBRMetallicRoughness::PBRMetallicRoughness(
const TextureData *baseColorTexture, const Vec4f &baseolorFactor, const TextureData *baseColorTexture, const TextureData *metRoughTexture,
float metallic, float roughness) const Vec4f &baseColorFactor, float metallic, float roughness)
: baseColorTexture(Tex::ref(baseColorTexture)), : baseColorTexture(Tex::ref(baseColorTexture)),
baseColorFactor(baseolorFactor), metRoughTexture(Tex::ref(metRoughTexture)),
baseColorFactor(baseColorFactor),
metallic(metallic), metallic(metallic),
roughness(roughness) roughness(roughness)
{ {
@ -153,10 +154,14 @@ void to_json(json &j, const PBRMetallicRoughness &d)
if (d.baseColorFactor.LengthSquared() > 0) { if (d.baseColorFactor.LengthSquared() > 0) {
j["baseColorFactor"] = toStdVec(d.baseColorFactor); j["baseColorFactor"] = toStdVec(d.baseColorFactor);
} }
if (d.metallic != 1.0f) { if (d.metRoughTexture != nullptr) {
j["metallicRoughnessTexture"] = *d.metRoughTexture;
// if a texture is provided, throw away metallic/roughness values
j["roughnessFactor"] = 1.0f;
j["metallicFactor"] = 1.0f;
} else {
// without a texture, however, use metallic/roughness as constants
j["metallicFactor"] = d.metallic; j["metallicFactor"] = d.metallic;
}
if (d.roughness != 1.0f) {
j["roughnessFactor"] = d.roughness; j["roughnessFactor"] = d.roughness;
} }
} }

View File

@ -75,10 +75,11 @@ struct PBRSpecularGlossiness
struct PBRMetallicRoughness struct PBRMetallicRoughness
{ {
PBRMetallicRoughness( PBRMetallicRoughness(
const TextureData *baseColorTexture, const Vec4f &baseolorFactor, const TextureData *baseColorTexture, const TextureData *metRoughTexture,
float metallic = 0.1f, float roughness = 0.4f); const Vec4f &baseColorFactor, float metallic = 0.1f, float roughness = 0.6f);
std::unique_ptr<Tex> baseColorTexture; std::unique_ptr<Tex> baseColorTexture;
std::unique_ptr<Tex> metRoughTexture;
const Vec4f baseColorFactor; const Vec4f baseColorFactor;
const float metallic; const float metallic;
const float roughness; const float roughness;

View File

@ -20,7 +20,7 @@ NodeData::NodeData(
scale(scale), scale(scale),
children(), children(),
mesh(-1), mesh(-1),
cameraName(""), camera(-1),
skin(-1) skin(-1)
{ {
} }
@ -44,10 +44,10 @@ void NodeData::SetSkin(uint32_t skinIx)
skin = skinIx; skin = skinIx;
} }
void NodeData::AddCamera(std::string camera) void NodeData::SetCamera(uint32_t cameraIndex)
{ {
assert(!isJoint); assert(!isJoint);
cameraName = std::move(camera); camera = cameraIndex;
} }
json NodeData::serialize() const json NodeData::serialize() const
@ -75,8 +75,8 @@ json NodeData::serialize() const
if (skin >= 0) { if (skin >= 0) {
result["skin"] = skin; result["skin"] = skin;
} }
if (!cameraName.empty()) { if (camera >= 0) {
result["camera"] = cameraName; result["camera"] = camera;
} }
} }
return result; return result;

View File

@ -19,7 +19,7 @@ struct NodeData : Holdable
void AddChildNode(uint32_t childIx); void AddChildNode(uint32_t childIx);
void SetMesh(uint32_t meshIx); void SetMesh(uint32_t meshIx);
void SetSkin(uint32_t skinIx); void SetSkin(uint32_t skinIx);
void AddCamera(std::string camera); void SetCamera(uint32_t camera);
json serialize() const override; json serialize() const override;
@ -30,7 +30,7 @@ struct NodeData : Holdable
Vec3f scale; Vec3f scale;
std::vector<uint32_t> children; std::vector<uint32_t> children;
int32_t mesh; int32_t mesh;
std::string cameraName; int32_t camera;
int32_t skin; int32_t skin;
std::vector<std::string> skeletons; std::vector<std::string> skeletons;
}; };