Support for Stingray PBS material definitions (#47)

This adds the first FBX PBR import path. Materials that have been
exported via the Stingray PBS preset should be picked up as native
metallic/roughness, and exported essentially 1:1 to the glTF output.

In more detail, this commit:
- (Re)introduces the STB header libraries as a dependency. We currently
use it for reading and writing images. In time we may need a more
dedicated PNG compression library.
- Generalizes FbxMaterialAccess to return different subclasses of
  FbxMaterialInfo; currently FbxRoughMetMaterialInfo and
  FbxTraditionalMaterialInfo.
  - FbxTraditionalMaterialInfo is populated from the canonical
    FbxSurfaceMaterial classes.
  - FbxRoughMetMaterialInfo is currently populated through the Stingray
    PBS set of properties, further documented in the code.
- RawMaterial was in turn generalized to feature a pluggable,
  type-specific RawMatProps struct; current implementations are,
  unsurprisingly, RawTraditionalMatProps and RawMetRoughMatProps. These
  are basically just lists of per-surface constants, e.g. diffuseFactor or
  roughness.
- In the third phase, glTF generation, the bulk of the changes are
  concerned with creating packed textures of the type needed by e.g. the
  metallic-roughness struct, where one colour channel holds roughness and
  the other metallic. This is done with a somewhat pluggable "map source
  pixels to destination pixel" mechanism. More work will likely be needed
  here in the future to accomodate more demanding mappings.

There's also a lot of code to convert from one representation to
another. The most useful, but also the least well-supported conversion,
is from old workflow (diffuse, specular, shininess) to
metallic/roughness. Going from PBR spec/gloss to PBR met/rough is hard
enough, but we go one step sillier and treat shininess as if it were
glossiness, which it certainly isn't. More work is needed here! But it's
still a fun proof of concept of sorts, and perhaps for some people it's
useful to just get *something* into the PBR world.
This commit is contained in:
Pär Winzell 2017-11-30 22:22:31 -08:00 committed by Par Winzell
parent e2e0d741a2
commit fdf7bb3336
8 changed files with 903 additions and 263 deletions

View File

@ -76,6 +76,16 @@ ExternalProject_Add(Json
)
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
ExternalProject_Add(CPPCodec
PREFIX cppcodec
@ -151,6 +161,7 @@ add_dependencies(FBX2glTF
MathFu
FiFoMap
Json
STB
CxxOpts
CPPCodec
Fmt
@ -182,6 +193,7 @@ target_include_directories(FBX2glTF PUBLIC
${FIFO_MAP_INCLUDE_DIR}
${JSON_INCLUDE_DIR}
${CXXOPTS_INCLUDE_DIR}
${STB_INCLUDE_DIR}
${CPPCODEC_INCLUDE_DIR}
${FMT_INCLUDE_DIR}
)

View File

@ -44,9 +44,10 @@ Usage:
--khr-materials-common (WIP) Use KHR_materials_common extensions to
specify Unlit/Lambert/Blinn/Phong shaders.
--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
(WIP) Experimentally fill in the
(WIP) Very experimentally employ the
KHR_materials_pbrSpecularGlossiness extension.
--blend-shape-normals Include blend shape normals, if reported
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
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
Phong, with simpler and more direct illumination and shading models. These modes
are largely incompatible — for example, textures in the old workflow often
contain baked lighting, which would arise naturally in a PBR environment.
By contrast, FBX's material support remains largely in the older world of
Lambert and Phong, with simpler and more direct illumination and shading
models. These modes are inherently incompatible — for example, textures in the
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:
- Emissive constants and textures
- Occlusion 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
emerge naturally from the fundamental colour of the material and any ambient
lighting present.
- Diffuse — the material's direction-agnostic, non-specular reflection,
and additionally, with Blinn/Phong:
- 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.)
#### Exporting as Unlit/Lambert/Phong
Increasingly with PBR materials, these properties are just left at zero or
default values in the FBX. But when they're there, and they're how you want the
glTF materials generated, one option is to use the --khr-materials-common
command line switch, with the awareness that this incurs a required dependency
on the glTF extension `KHR_materials_common`.
If you have a model was constructed using the traditional workflow, you may
choose to export it using the --khr-materials-common switch. This incurs a
dependency on the glTF extension 'KHR_materials_common'; a client that accepts
that extension is making a promise it'll do its best to render i.e. Lambert or
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
ratification process, and is furthermore likely to change names.**
#### Exporting as Metallic-Roughness PBR
Given the command line flag --pbr-metallic-roughness, we accept glTF 2.0's PBR
mode, but we do so very partially, filling in a couple of reasonable constants
for metalness and roughness and using the diffuse texture, if it exists, as the
`base colour` texture.
Given the command line flag --pbr-metallic-roughness, we throw ourselves into
the warm embrace of glTF 2.0's PBR preference.
More work is needed to harness the power of glTF's 2.0's materials. The biggest
issue here is the lack of any obviously emerging standards to complement FBX
itself. It's not clear what format an artist can export their PBR materials on,
and when they can, how to communicate this information well to `FBX2glTF`.
As mentioned above, there is lilttle consensus in the world on how PBR should be
represented in FBX. At present, we support only one format: Stingray PBS. This
is a featue that comes bundled with Maya, and any PBR model exported through
that route should be digested propertly by FBX2glTF.
(*Stingray PBS* support is
[high on the TODO list](https://github.com/facebookincubator/FBX2glTF/issues/12).)
(A happy note: Allegorithmic's Susbstance Painter also exports Stingray PBS,
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
The tool will optionally apply [Draco](https://github.com/google/draco)

View File

@ -88,46 +88,181 @@ private:
const FbxLayerElementArrayTemplate<int> *indices;
};
class FbxMaterialAccess
{
struct FbxMaterialProperties {
FbxFileTexture *texAmbient {};
FbxVector4 colAmbient {};
FbxFileTexture *texSpecular {};
FbxVector4 colSpecular {};
FbxFileTexture *texDiffuse {};
FbxVector4 colDiffuse {};
FbxFileTexture *texEmissive {};
FbxVector4 colEmissive {};
FbxFileTexture *texNormal {};
FbxFileTexture *texShininess {};
FbxDouble shininess {};
};
private:
const FbxSurfaceMaterial *fbxMaterial;
const std::map<const FbxTexture *, FbxString> &textureLocations;
public:
struct FbxMaterialInfo {
FbxMaterialInfo(const FbxString &name, const FbxString &shadingModel)
: name(name),
shadingModel(shadingModel)
{}
const FbxString name;
const FbxString shadingModel;
};
const struct FbxMaterialProperties props;
struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
static constexpr const char *FBX_SHADER_METROUGH = "MetallicRoughness";
explicit FbxMaterialAccess(
const FbxSurfaceMaterial *fbxMaterial, const std::map<const FbxTexture *, FbxString> &textureNames) :
fbxMaterial(fbxMaterial),
name(fbxMaterial->GetName()),
shadingModel(fbxMaterial->ShadingModel),
textureLocations(textureNames),
props(extractTextures())
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)
{
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)
{}
struct FbxMaterialProperties extractTextures() {
struct FbxMaterialProperties res;
FbxFileTexture *texAmbient {};
FbxVector4 colAmbient {};
FbxFileTexture *texSpecular {};
FbxVector4 colSpecular {};
FbxFileTexture *texDiffuse {};
FbxVector4 colDiffuse {};
FbxFileTexture *texEmissive {};
FbxVector4 colEmissive {};
FbxFileTexture *texNormal {};
FbxFileTexture *texShininess {};
FbxDouble shininess {};
static std::unique_ptr<FbxTraditionalMaterialInfo> From(
FbxSurfaceMaterial *fbxMaterial,
const std::map<const FbxTexture *, FbxString> &textureLocations)
{
auto getSurfaceScalar = [&](const char *propName) -> std::tuple<FbxDouble, FbxFileTexture *> {
const FbxProperty prop = fbxMaterial->FindProperty(propName);
FbxDouble val(0);
FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
tex = nullptr;
}
if (tex == nullptr && prop.IsValid()) {
val = prop.Get<FbxDouble>();
}
return std::make_tuple(val, tex);
};
auto getSurfaceVector = [&](const char *propName) -> std::tuple<FbxDouble3, FbxFileTexture *> {
const FbxProperty prop = fbxMaterial->FindProperty(propName);
FbxDouble3 val(1, 1, 1);
FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
tex = nullptr;
}
if (tex == nullptr && prop.IsValid()) {
val = prop.Get<FbxDouble3>();
}
return std::make_tuple(val, tex);
};
auto getSurfaceValues = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *, FbxFileTexture *> {
const FbxProperty colProp = fbxMaterial->FindProperty(colName);
const FbxProperty facProp = fbxMaterial->FindProperty(facName);
FbxDouble3 colorVal(1, 1, 1);
FbxDouble factorVal(1);
FbxFileTexture *colTex = colProp.GetSrcObject<FbxFileTexture>();
if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) {
colTex = nullptr;
}
if (colTex == nullptr && colProp.IsValid()) {
colorVal = colProp.Get<FbxDouble3>();
}
FbxFileTexture *facTex = facProp.GetSrcObject<FbxFileTexture>();
if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) {
facTex = nullptr;
}
if (facTex == nullptr && facProp.IsValid()) {
factorVal = facProp.Get<FbxDouble>();
}
auto val = FbxVector4(
colorVal[0] * factorVal,
colorVal[1] * factorVal,
colorVal[2] * factorVal,
factorVal);
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) {
auto handleBasicProperty = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *>{
FbxFileTexture *colTex, *facTex;
FbxVector4 vec;
@ -141,20 +276,20 @@ public:
return std::make_tuple(vec, facTex);
};
std::tie(res.colAmbient, res.texAmbient) =
std::tie(res->colAmbient, res->texAmbient) =
handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
std::tie(res.colSpecular, res.texSpecular) =
std::tie(res->colSpecular, res->texSpecular) =
handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
std::tie(res.colDiffuse, res.texDiffuse) =
std::tie(res->colDiffuse, res->texDiffuse) =
handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
std::tie(res.colEmissive, res.texEmissive) =
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);
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);
std::tie(res->shininess, res->texShininess) = getSurfaceScalar(FbxSurfaceMaterial::sShininess);
// for transparency we just want a constant vector value;
FbxVector4 transparency;
@ -169,73 +304,24 @@ public:
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;
res->colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2])/3.0;
return res;
}
std::tuple<FbxDouble, FbxFileTexture *> getSurfaceScalar(const char *propName) const
{
const FbxProperty prop = fbxMaterial->FindProperty(propName);
FbxDouble val(0);
FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
tex = nullptr;
}
if (tex == nullptr && prop.IsValid()) {
val = prop.Get<FbxDouble>();
}
return std::make_tuple(val, tex);
}
std::tuple<FbxDouble3, FbxFileTexture *> getSurfaceVector(const char *propName) const
{
const FbxProperty prop = fbxMaterial->FindProperty(propName);
FbxDouble3 val(1, 1, 1);
FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
tex = nullptr;
}
if (tex == nullptr && prop.IsValid()) {
val = prop.Get<FbxDouble3>();
}
return std::make_tuple(val, tex);
}
std::tuple<FbxVector4, FbxFileTexture *, FbxFileTexture *> getSurfaceValues(const char *colName, const char *facName) const
{
const FbxProperty colProp = fbxMaterial->FindProperty(colName);
const FbxProperty facProp = fbxMaterial->FindProperty(facName);
FbxDouble3 colorVal(1, 1, 1);
FbxDouble factorVal(1);
FbxFileTexture *colTex = colProp.GetSrcObject<FbxFileTexture>();
if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) {
colTex = nullptr;
}
if (colTex == nullptr && colProp.IsValid()) {
colorVal = colProp.Get<FbxDouble3>();
}
FbxFileTexture *facTex = facProp.GetSrcObject<FbxFileTexture>();
if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) {
facTex = nullptr;
}
if (facTex == nullptr && facProp.IsValid()) {
factorVal = facProp.Get<FbxDouble>();
}
auto val = FbxVector4(
colorVal[0] * factorVal,
colorVal[1] * factorVal,
colorVal[2] * factorVal,
factorVal);
return std::make_tuple(val, colTex, facTex);
};
};
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
{
public:
@ -273,14 +359,14 @@ public:
}
auto summary = summaries[materialNum];
if (summary == nullptr) {
summary = summaries[materialNum] = std::make_shared<FbxMaterialAccess>(
summary = summaries[materialNum] = GetMaterialInfo(
mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum),
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) {
const int materialNum = indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
@ -293,10 +379,10 @@ public:
}
private:
FbxGeometryElement::EMappingMode mappingMode;
std::vector<std::shared_ptr<FbxMaterialAccess>> summaries {};
const FbxMesh *mesh;
const FbxLayerElementArrayTemplate<int> *indices;
FbxGeometryElement::EMappingMode mappingMode;
std::vector<std::shared_ptr<FbxMaterialInfo>> summaries {};
const FbxMesh *mesh;
const FbxLayerElementArrayTemplate<int> *indices;
};
class FbxSkinningAccess
@ -699,27 +785,23 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
for (int polygonIndex = 0; polygonIndex < pMesh->GetPolygonCount(); polygonIndex++) {
FBX_ASSERT(pMesh->GetPolygonSize(polygonIndex) == 3);
const std::shared_ptr<FbxMaterialAccess> fbxMaterial = materials.GetMaterial(polygonIndex);
const std::shared_ptr<FbxMaterialInfo> fbxMaterial = materials.GetMaterial(polygonIndex);
int textures[RAW_TEXTURE_USAGE_MAX];
std::fill_n(textures, (int)RAW_TEXTURE_USAGE_MAX, -1);
std::fill_n(textures, (int) RAW_TEXTURE_USAGE_MAX, -1);
FbxString shadingModel, materialName;
FbxVector4 ambient, specular, diffuse, emissive;
FbxDouble shininess;
std::shared_ptr<RawMatProps> rawMatProps;
FbxString materialName;
if (fbxMaterial == nullptr) {
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 {
materialName = fbxMaterial->name;
shadingModel = fbxMaterial->shadingModel;
const auto &matProps = fbxMaterial->props;
const auto maybeAddTexture = [&](FbxFileTexture *tex, RawTextureUsage usage) {
const auto maybeAddTexture = [&](const FbxFileTexture *tex, RawTextureUsage usage) {
if (tex != nullptr) {
// dig out the inferred filename from the textureLocations map
FbxString inferredPath = textureLocations.find(tex)->second;
@ -727,19 +809,44 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
}
};
ambient = matProps.colAmbient;
maybeAddTexture(matProps.texAmbient, RAW_TEXTURE_USAGE_AMBIENT);
specular = matProps.colSpecular;
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);
std::shared_ptr<RawMatProps> matInfo;
if (fbxMaterial->shadingModel == FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH) {
FbxRoughMetMaterialInfo *fbxMatInfo = static_cast<FbxRoughMetMaterialInfo *>(fbxMaterial.get());
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;
maybeAddTexture(matProps.texShininess, RAW_TEXTURE_USAGE_SHININESS);
FbxTraditionalMaterialInfo *fbxMatInfo = static_cast<FbxTraditionalMaterialInfo *>(fbxMaterial.get());
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];
@ -867,9 +974,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
}
const RawMaterialType materialType = GetMaterialType(raw, textures, vertexTransparency, skinning.IsSkinned());
const int rawMaterialIndex = raw.AddMaterial(
materialName, shadingModel, materialType, textures,
toVec3f(ambient), toVec4f(diffuse), toVec3f(specular), toVec3f(emissive), shininess);
const int rawMaterialIndex = raw.AddMaterial(materialName, materialType, textures, rawMatProps);
raw.AddTriangle(rawVertexIndices[0], rawVertexIndices[1], rawVertexIndices[2], rawMaterialIndex, rawSurfaceIndex);
}

View File

@ -12,6 +12,11 @@
#include <iostream>
#include <fstream>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#include "FBX2glTF.h"
#include "utils/String_Utils.h"
#include "utils/Image_Utils.h"
@ -217,13 +222,21 @@ struct GLTFData
Holder<SceneData> scenes;
};
template <class T>
static void WriteToVectorContext(void *context, void *data, int size) {
std::vector<T> *vec = static_cast<std::vector<T> *>(context);
for (int ii = 0; ii < size; ii ++) {
vec->emplace_back(((T *) data)[ii]);
}
}
/**
* 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
* classes are guaranteed to stick around for the duration of the process.
*/
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());
@ -269,7 +282,7 @@ ModelData *Raw2Gltf(
for (int i = 0; i < raw.GetMaterialCount(); i++) {
fmt::printf(
"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()) {
fmt::printf(
@ -293,6 +306,7 @@ ModelData *Raw2Gltf(
std::map<std::string, std::shared_ptr<NodeData>> nodesByName;
std::map<std::string, std::shared_ptr<MaterialData>> materialsByName;
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.
@ -378,24 +392,189 @@ ModelData *Raw2Gltf(
//
// textures
//
typedef std::array<float, 4> pixel; // pixel components are floats in [0, 1]
typedef std::function<pixel(const std::vector<const pixel *>)> pixel_merger;
for (int textureIndex = 0; textureIndex < raw.GetTextureCount(); textureIndex++) {
const RawTexture &texture = raw.GetTexture(textureIndex);
const std::string textureName = Gltf::StringUtils::GetFileBaseString(texture.name);
const std::string relativeFilename = Gltf::StringUtils::GetFileNameString(texture.fileLocation);
auto texIndicesKey = [&](std::vector<int> ixVec, std::string tag) -> std::string {
std::string result = tag;
for (int ix : ixVec) {
result += "_";
result += ix;
}
return result;
};
ImageData *source = nullptr;
/**
* 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
) -> 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, 4);
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?
std::vector<uint8_t > mergedPixels(4 * width * height);
std::vector<pixel> pixels(texes.size());
std::vector<const pixel *> pixelPointers(texes.size());
for (int ii = 0; ii < mergedPixels.size(); ii += 4) {
pixels.clear();
for (int jj = 0; jj < texes.size(); jj ++) {
const TexInfo &tex = texes[jj];
if (tex.pixels != nullptr) {
pixels[jj] = { tex.pixels[ii+0]/255.0f, tex.pixels[ii+1]/255.0f, tex.pixels[ii+2]/255.0f, tex.pixels[ii+3]/255.0f };
} else {
// absent textures are to be treated as all ones
pixels[jj] = { 1.0f, 1.0f, 1.0f, 1.0f };
}
pixelPointers[jj] = &pixels[jj];
}
const pixel &merged = combine(pixelPointers);
for (int jj = 0; jj < 4; jj ++) {
mergedPixels[ii + jj] = static_cast<uint8_t>(fmax(0, fmin(255.0f, merged[jj] * 255.0f)));
}
}
bool png = true;
std::vector<char> imgBuffer;
int res;
if (png) {
res = stbi_write_png_to_func(WriteToVectorContext<char>, &imgBuffer, width, height, 4, mergedPixels.data(), width * 4);
} else {
res = stbi_write_jpg_to_func(WriteToVectorContext<char>, &imgBuffer, width, height, 4, mergedPixels.data(), 80);
}
if (!res) {
fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename);
return nullptr;
}
ImageData *image;
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) {
std::string suffix = Gltf::StringUtils::GetFileSuffixString(texture.fileLocation);
source = new ImageData(relativeFilename, *bufferView, suffixToMimeType(suffix));
std::string suffix = Gltf::StringUtils::GetFileSuffixString(rawTexture.fileLocation);
image = new ImageData(relativeFilename, *bufferView, suffixToMimeType(suffix));
}
} else if (!relativeFilename.empty()) {
source = new ImageData(relativeFilename, relativeFilename);
image = new ImageData(relativeFilename, relativeFilename);
std::string outputPath = outputFolder + relativeFilename;
if (FileUtils::CopyFile(texture.fileLocation, outputPath)) {
if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath)) {
if (verboseOutput) {
fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath);
}
@ -405,15 +584,16 @@ ModelData *Raw2Gltf(
// reference, even if the copy failed.
}
}
if (!source) {
if (!image) {
// 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(
new TextureData(textureName, defaultSampler, *gltf->images.hold(source)));
assert(texDat.ix == textureIndex);
}
std::shared_ptr<TextureData> texDat = gltf->textures.hold(
new TextureData(textureName, defaultSampler, *gltf->images.hold(image)));
textureByIndicesKey.insert(std::make_pair(key, texDat));
return texDat;
};
//
// materials
@ -425,47 +605,289 @@ ModelData *Raw2Gltf(
material.type == RAW_MATERIAL_TYPE_TRANSPARENT ||
material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT;
// find a texture by usage and return it as a TextureData*, or nullptr if none exists.
auto getTex = [&](RawTextureUsage usage)
{
// note that we depend on TextureData.ix == rawTexture's index
return (material.textures[usage] >= 0) ? gltf->textures.ptrs[material.textures[usage]].get() : nullptr;
Vec3f emissiveFactor;
float emissiveIntensity;
const Vec3f dielectric(0.04f, 0.04f, 0.04f);
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
) -> std::shared_ptr<TextureData> {
return getDerivedTexture({ material.textures[u1], material.textures[u2] }, combine, tag);
};
// 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
) -> std::shared_ptr<TextureData> {
return getDerivedTexture({ material.textures[u1], material.textures[u2], material.textures[u3] }, combine, tag);
};
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;
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 }; });
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 };
});
if (material.textures[RAW_TEXTURE_USAGE_DIFFUSE] >= 0) {
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.0 - dielectric.x) / fmax(1.0 - pixelMet, epsilon));
Vec3f fromSpecular = specular - dielectric * (1.0 - pixelMet) * (1.0 / fmax(pixelMet, epsilon));
Vec3f baseColor = Vec3f::Lerp(fromDiffuse, fromSpecular, pixelMet * pixelMet);
return { baseColor[0], baseColor[1], baseColor[2], diffuse[3] };
});
}
emissiveFactor = props->emissiveFactor;
emissiveIntensity = 1.0f;
}
pbrMetRough.reset(new PBRMetallicRoughness(baseColorTex.get(), metRoughTex.get(), diffuseFactor, metallic, roughness));
}
std::shared_ptr<PBRSpecularGlossiness> pbrSpecGloss;
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 };
});
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 };
});
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] };
});
diffuseTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
diffuseFactor = props->diffuseFactor;
specularFactor = props->specularFactor;
glossiness = props->shininess;
}
pbrSpecGloss.reset(
new PBRSpecularGlossiness(
getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor,
getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor, material.shininess));
diffuseTex.get(), diffuseFactor, specGlossTex.get(), specularFactor, glossiness));
}
std::shared_ptr<KHRCommonMats> khrComMat;
if (options.useKHRMatCom) {
auto type = KHRCommonMats::MaterialType::Constant;
if (material.shadingModel == "Lambert") {
type = KHRCommonMats::MaterialType::Lambert;
} else if (material.shadingModel == "Blinn") {
type = KHRCommonMats::MaterialType::Blinn;
} else if (material.shadingModel == "Phong") {
type = KHRCommonMats::MaterialType::Phong;
float shininess;
Vec3f ambientFactor, specularFactor;
Vec4f diffuseFactor;
std::shared_ptr<TextureData> diffuseTex;
auto type = KHRCommonMats::MaterialType::Constant;
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;
} else if (material.info->shadingModel == RAW_SHADING_MODEL_BLINN) {
type = KHRCommonMats::MaterialType::Blinn;
} else if (material.info->shadingModel == RAW_SHADING_MODEL_PHONG) {
type = KHRCommonMats::MaterialType::Phong;
}
}
khrComMat.reset(
new KHRCommonMats(
type,
getTex(RAW_TEXTURE_USAGE_SHININESS), material.shininess,
getTex(RAW_TEXTURE_USAGE_AMBIENT), material.ambientFactor,
getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor,
getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor));
new KHRCommonMats(type,
simpleTex(RAW_TEXTURE_USAGE_SHININESS).get(), shininess,
simpleTex(RAW_TEXTURE_USAGE_AMBIENT).get(), ambientFactor,
diffuseTex.get(), diffuseFactor,
simpleTex(RAW_TEXTURE_USAGE_SPECULAR).get(), specularFactor));
}
std::shared_ptr<MaterialData> mData = gltf->materials.hold(
new MaterialData(
material.name, isTransparent, getTex(RAW_TEXTURE_USAGE_NORMAL),
getTex(RAW_TEXTURE_USAGE_EMISSIVE), material.emissiveFactor,
material.name, isTransparent,
simpleTex(RAW_TEXTURE_USAGE_NORMAL).get(), simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get(),
emissiveFactor * emissiveIntensity,
khrComMat, pbrMetRough, pbrSpecGloss));
materialsByName[materialHash(material)] = mData;
}

View File

@ -111,35 +111,25 @@ int RawModel::AddTexture(const std::string &name, const std::string &fileName, c
int RawModel::AddMaterial(const RawMaterial &material)
{
return AddMaterial(
material.name.c_str(), material.shadingModel.c_str(), material.type, material.textures, material.ambientFactor,
material.diffuseFactor, material.specularFactor, material.emissiveFactor, material.shininess);
return AddMaterial(material.name.c_str(), material.type, material.textures, material.info);
}
int RawModel::AddMaterial(
const char *name, const char *shadingModel, const RawMaterialType materialType,
const int textures[RAW_TEXTURE_USAGE_MAX], const Vec3f ambientFactor,
const Vec4f diffuseFactor, const Vec3f specularFactor,
const Vec3f emissiveFactor, float shinineness)
const char *name,
const RawMaterialType materialType,
const int textures[RAW_TEXTURE_USAGE_MAX],
std::shared_ptr<RawMatProps> materialInfo)
{
for (size_t i = 0; i < materials.size(); i++) {
if (materials[i].name != name) {
continue;
}
if (materials[i].shadingModel != shadingModel) {
continue;
}
if (materials[i].type != materialType) {
continue;
}
if (materials[i].ambientFactor != ambientFactor ||
materials[i].diffuseFactor != diffuseFactor ||
materials[i].specularFactor != specularFactor ||
materials[i].emissiveFactor != emissiveFactor ||
materials[i].shininess != shinineness) {
if (*(materials[i].info) != *materialInfo) {
continue;
}
bool match = true;
for (int j = 0; match && j < RAW_TEXTURE_USAGE_MAX; j++) {
match = match && (materials[i].textures[j] == textures[j]);
@ -150,14 +140,9 @@ int RawModel::AddMaterial(
}
RawMaterial material;
material.name = name;
material.shadingModel = shadingModel;
material.type = materialType;
material.ambientFactor = ambientFactor;
material.diffuseFactor = diffuseFactor;
material.specularFactor = specularFactor;
material.emissiveFactor = emissiveFactor;
material.shininess = shinineness;
material.name = name;
material.type = materialType;
material.info = materialInfo;
for (int i = 0; i < RAW_TEXTURE_USAGE_MAX; i++) {
material.textures[i] = textures[i];

View File

@ -95,8 +95,32 @@ struct RawTriangle
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
{
RAW_TEXTURE_USAGE_NONE = -1,
RAW_TEXTURE_USAGE_AMBIENT,
RAW_TEXTURE_USAGE_DIFFUSE,
RAW_TEXTURE_USAGE_NORMAL,
@ -104,32 +128,28 @@ enum RawTextureUsage
RAW_TEXTURE_USAGE_SHININESS,
RAW_TEXTURE_USAGE_EMISSIVE,
RAW_TEXTURE_USAGE_REFLECTION,
RAW_TEXTURE_USAGE_ALBEDO,
RAW_TEXTURE_USAGE_OCCLUSION,
RAW_TEXTURE_USAGE_ROUGHNESS,
RAW_TEXTURE_USAGE_METALLIC,
RAW_TEXTURE_USAGE_MAX
};
inline std::string DescribeTextureUsage(int usage)
static inline std::string Describe(RawTextureUsage usage)
{
if (usage < 0) {
return "<none>";
}
switch (static_cast<RawTextureUsage>(usage)) {
case RAW_TEXTURE_USAGE_AMBIENT:
return "ambient";
case RAW_TEXTURE_USAGE_DIFFUSE:
return "diffuse";
case RAW_TEXTURE_USAGE_NORMAL:
return "normal";
case RAW_TEXTURE_USAGE_SPECULAR:
return "specuar";
case RAW_TEXTURE_USAGE_SHININESS:
return "shininess";
case RAW_TEXTURE_USAGE_EMISSIVE:
return "emissive";
case RAW_TEXTURE_USAGE_REFLECTION:
return "reflection";
case RAW_TEXTURE_USAGE_MAX:
default:
return "unknown";
switch (usage) {
case RAW_TEXTURE_USAGE_NONE: return "<none>";
case RAW_TEXTURE_USAGE_AMBIENT: return "ambient";
case RAW_TEXTURE_USAGE_DIFFUSE: return "diffuse";
case RAW_TEXTURE_USAGE_NORMAL: return "normal";
case RAW_TEXTURE_USAGE_SPECULAR: return "specuar";
case RAW_TEXTURE_USAGE_SHININESS: return "shininess";
case RAW_TEXTURE_USAGE_EMISSIVE: return "emissive";
case RAW_TEXTURE_USAGE_REFLECTION: return "reflection";
case RAW_TEXTURE_USAGE_OCCLUSION: return "occlusion";
case RAW_TEXTURE_USAGE_ROUGHNESS: return "roughness";
case RAW_TEXTURE_USAGE_METALLIC: return "metallic";
case RAW_TEXTURE_USAGE_MAX:default: return "unknown";
}
};
@ -159,18 +179,91 @@ enum RawMaterialType
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
{
std::string name;
std::string shadingModel; // typically "Surface", "Anisotropic", "Blinn", "Lambert", "Phong", "Phone E"
RawMaterialType type;
Vec3f ambientFactor;
Vec4f diffuseFactor;
Vec3f specularFactor;
Vec3f emissiveFactor;
float shininess;
int textures[RAW_TEXTURE_USAGE_MAX];
std::string name;
RawMaterialType type;
std::shared_ptr<RawMatProps> info;
int textures[RAW_TEXTURE_USAGE_MAX];
};
struct RawBlendChannel
@ -263,10 +356,8 @@ public:
int AddTexture(const std::string &name, const std::string &fileName, const std::string &fileLocation, RawTextureUsage usage);
int AddMaterial(const RawMaterial &material);
int AddMaterial(
const char *name, const char *shadingModel, RawMaterialType materialType,
const int textures[RAW_TEXTURE_USAGE_MAX], Vec3f ambientFactor,
Vec4f diffuseFactor, Vec3f specularFactor,
Vec3f emissiveFactor, float shinineness);
const char *name, const RawMaterialType materialType, const int textures[RAW_TEXTURE_USAGE_MAX],
std::shared_ptr<RawMatProps> materialInfo);
int AddSurface(const RawSurface &suface);
int AddSurface(const char *name, long surfaceId);
int AddAnimation(const RawAnimation &animation);

View File

@ -126,10 +126,11 @@ void to_json(json &j, const PBRSpecularGlossiness &d)
}
PBRMetallicRoughness::PBRMetallicRoughness(
const TextureData *baseColorTexture, const Vec4f &baseolorFactor,
float metallic, float roughness)
const TextureData *baseColorTexture, const TextureData *metRoughTexture,
const Vec4f &baseColorFactor, float metallic, float roughness)
: baseColorTexture(Tex::ref(baseColorTexture)),
baseColorFactor(baseolorFactor),
metRoughTexture(Tex::ref(metRoughTexture)),
baseColorFactor(baseColorFactor),
metallic(metallic),
roughness(roughness)
{
@ -144,10 +145,14 @@ void to_json(json &j, const PBRMetallicRoughness &d)
if (d.baseColorFactor.LengthSquared() > 0) {
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;
}
if (d.roughness != 1.0f) {
j["roughnessFactor"] = d.roughness;
}
}

View File

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