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:
parent
e2e0d741a2
commit
fdf7bb3336
|
@ -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}
|
||||
)
|
||||
|
|
65
README.md
65
README.md
|
@ -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)
|
||||
|
|
367
src/Fbx2Raw.cpp
367
src/Fbx2Raw.cpp
|
@ -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);
|
||||
}
|
||||
|
|
504
src/Raw2Gltf.cpp
504
src/Raw2Gltf.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
163
src/RawModel.h
163
src/RawModel.h
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue