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") 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 +161,7 @@ add_dependencies(FBX2glTF
MathFu MathFu
FiFoMap FiFoMap
Json Json
STB
CxxOpts CxxOpts
CPPCodec CPPCodec
Fmt Fmt
@ -182,6 +193,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

@ -88,46 +88,181 @@ private:
const FbxLayerElementArrayTemplate<int> *indices; const FbxLayerElementArrayTemplate<int> *indices;
}; };
class FbxMaterialAccess struct FbxMaterialInfo {
{ FbxMaterialInfo(const FbxString &name, const FbxString &shadingModel)
struct FbxMaterialProperties { : name(name),
FbxFileTexture *texAmbient {}; shadingModel(shadingModel)
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:
const FbxString name; const FbxString name;
const FbxString shadingModel; const FbxString shadingModel;
};
const struct FbxMaterialProperties props; struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
static constexpr const char *FBX_SHADER_METROUGH = "MetallicRoughness";
explicit FbxMaterialAccess( FbxRoughMetMaterialInfo(const FbxString &name, const FbxString &shadingModel)
const FbxSurfaceMaterial *fbxMaterial, const std::map<const FbxTexture *, FbxString> &textureNames) : : FbxMaterialInfo(name, shadingModel)
fbxMaterial(fbxMaterial), {}
name(fbxMaterial->GetName()), const FbxFileTexture *texColor {};
shadingModel(fbxMaterial->ShadingModel), FbxVector4 colBase {};
textureLocations(textureNames), const FbxFileTexture *texNormal {};
props(extractTextures()) 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() { FbxFileTexture *texAmbient {};
struct FbxMaterialProperties res; 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 // 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; FbxFileTexture *colTex, *facTex;
FbxVector4 vec; FbxVector4 vec;
@ -141,20 +276,20 @@ public:
return std::make_tuple(vec, facTex); return std::make_tuple(vec, facTex);
}; };
std::tie(res.colAmbient, res.texAmbient) = std::tie(res->colAmbient, res->texAmbient) =
handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor); handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
std::tie(res.colSpecular, res.texSpecular) = std::tie(res->colSpecular, res->texSpecular) =
handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor); handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
std::tie(res.colDiffuse, res.texDiffuse) = std::tie(res->colDiffuse, res->texDiffuse) =
handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor); handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
std::tie(res.colEmissive, res.texEmissive) = std::tie(res->colEmissive, res->texEmissive) =
handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor); handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor);
// the normal map can only ever be a map, ignore everything else // 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 // 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; // for transparency we just want a constant vector value;
FbxVector4 transparency; FbxVector4 transparency;
@ -169,73 +304,24 @@ public:
fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparencyFactor); 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 // 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; 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 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);
@ -293,10 +379,10 @@ 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;
}; };
class FbxSkinningAccess 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++) { 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, (int)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;
@ -727,19 +809,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];
@ -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 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);
} }

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,21 @@ struct GLTFData
Holder<SceneData> scenes; 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 * 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); auto iter = map.find(key);
assert(iter != map.end()); assert(iter != map.end());
@ -269,7 +282,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(
@ -293,6 +306,7 @@ ModelData *Raw2Gltf(
std::map<std::string, std::shared_ptr<NodeData>> nodesByName; 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<MaterialData>> materialsByName;
std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
std::map<long, std::shared_ptr<MeshData>> meshBySurfaceId; 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.
@ -378,24 +392,189 @@ ModelData *Raw2Gltf(
// //
// textures // 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++) { auto texIndicesKey = [&](std::vector<int> ixVec, std::string tag) -> std::string {
const RawTexture &texture = raw.GetTexture(textureIndex); std::string result = tag;
const std::string textureName = Gltf::StringUtils::GetFileBaseString(texture.name); for (int ix : ixVec) {
const std::string relativeFilename = Gltf::StringUtils::GetFileNameString(texture.fileLocation); 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) { 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);
} }
@ -405,15 +584,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
@ -425,47 +605,289 @@ 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
) -> 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; 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 }; });
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; 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 };
});
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( 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) {
auto type = KHRCommonMats::MaterialType::Constant; float shininess;
if (material.shadingModel == "Lambert") { Vec3f ambientFactor, specularFactor;
type = KHRCommonMats::MaterialType::Lambert; Vec4f diffuseFactor;
} else if (material.shadingModel == "Blinn") { std::shared_ptr<TextureData> diffuseTex;
type = KHRCommonMats::MaterialType::Blinn; auto type = KHRCommonMats::MaterialType::Constant;
} else if (material.shadingModel == "Phong") {
type = KHRCommonMats::MaterialType::Phong; 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( 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<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, pbrMetRough, pbrSpecGloss)); khrComMat, pbrMetRough, pbrSpecGloss));
materialsByName[materialHash(material)] = mData; 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) 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]);
@ -150,14 +140,9 @@ int RawModel::AddMaterial(
} }
RawMaterial material; RawMaterial material;
material.name = name; material.name = name;
material.shadingModel = shadingModel; material.type = materialType;
material.type = materialType; material.info = materialInfo;
material.ambientFactor = ambientFactor;
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];

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,18 +179,91 @@ 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; RawMaterialType type;
std::string shadingModel; // typically "Surface", "Anisotropic", "Blinn", "Lambert", "Phong", "Phone E" std::shared_ptr<RawMatProps> info;
RawMaterialType type; int textures[RAW_TEXTURE_USAGE_MAX];
Vec3f ambientFactor;
Vec4f diffuseFactor;
Vec3f specularFactor;
Vec3f emissiveFactor;
float shininess;
int textures[RAW_TEXTURE_USAGE_MAX];
}; };
struct RawBlendChannel struct RawBlendChannel
@ -263,10 +356,8 @@ 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, long surfaceId); int AddSurface(const char *name, long surfaceId);
int AddAnimation(const RawAnimation &animation); int AddAnimation(const RawAnimation &animation);

View File

@ -126,10 +126,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)
{ {
@ -144,10 +145,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

@ -70,10 +70,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;