Skip to content

Commit

Permalink
Clean house in material extensions.
Browse files Browse the repository at this point in the history
- KHR_materials_common never had a real life in the glTF 2.0 world. One
day we may see a new extension for Phong/Blinn/Lambert.
- PBR_specular_glossiness is a poor fit for PBS StingRay (the only real
source of PBR we have) and has no advantage over PBR_metallic_roughness.
- The conversion we were doing for traditional materials to PBR made no
sense. Revert to a very simple formula: diffuse -> baseColor, simple
reasonable constants for metallic & roughness.
  • Loading branch information
Par Winzell committed Feb 19, 2018
1 parent 67bb937 commit 7c0715c
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 420 deletions.
228 changes: 9 additions & 219 deletions src/Raw2Gltf.cpp
Expand Up @@ -624,13 +624,15 @@ ModelData *Raw2Gltf(
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;
};

TextureData *normalTexture = simpleTex(RAW_TEXTURE_USAGE_NORMAL).get();
TextureData *emissiveTexture = simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get();

// acquire derived texture of two RawTextureUsage as *TextData, or nullptr if neither exists
auto merge2Tex = [&](
const std::string tag,
Expand Down Expand Up @@ -658,19 +660,6 @@ ModelData *Raw2Gltf(
combine, tag, outputHasAlpha);
};

auto getMaxComponent = [&](const Vec3f &color) {
return fmax(color.x, fmax(color.y, color.z));
};
auto getPerceivedBrightness = [&](const Vec3f &color) {
return sqrt(0.299 * color.x * color.x + 0.587 * color.y * color.y + 0.114 * color.z * color.z);
};
auto toVec3f = [&](const pixel &pix) -> const Vec3f {
return Vec3f(pix[0], pix[1], pix[2]);
};
auto toVec4f = [&](const pixel &pix) -> const Vec4f {
return Vec4f(pix[0], pix[1], pix[2], pix[3]);
};

std::shared_ptr<PBRMetallicRoughness> pbrMetRough;
if (options.usePBRMetRough) {
// albedo is a basic texture, no merging needed
Expand Down Expand Up @@ -701,212 +690,25 @@ ModelData *Raw2Gltf(
/**
* Traditional FBX Material -> PBR Met/Rough glTF.
*
* Diffuse channel is used as base colour.
* Diffuse channel is used as base colour. Simple constants for metallic and roughness.
*/
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.8f;
} else {
metallic = 0.4f;
roughness = 0.6f;
}
auto solveMetallic = [&](float pDiff, float pSpec, float oneMinusSpecularStrength)
{
if (pSpec < dielectric.x) {
return 0.0;
}
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);

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 = Vec3f(0.4f, 0.4f, 0.4f) +
0.2f * (pixels[0] ? toVec3f(*pixels[0]) : props->specularFactor);
float shininess = pixels[1] ? (*pixels[1])[0] : props->shininess;
const Vec4f diffuse = pixels[2] ? toVec4f(*pixels[2]) : props->diffuseFactor;

float pixelMet = solveMetallic(
getPerceivedBrightness(diffuse.xyz()),
getPerceivedBrightness(specular),
1 - getMaxComponent(specular));
float pixelRough = 1 - shininess;

return { 0, pixelRough, pixelMet, 0 };
}, false);
if (material.textures[RAW_TEXTURE_USAGE_DIFFUSE] >= 0) {
const RawTexture &diffuseTex = raw.GetTexture(material.textures[RAW_TEXTURE_USAGE_DIFFUSE]);
baseColorTex = merge2Tex("base_col", RAW_TEXTURE_USAGE_DIFFUSE, RAW_TEXTURE_USAGE_SPECULAR,
[&](const std::vector<const pixel *> pixels) -> pixel {
const Vec4f diffuse = pixels[0] ? toVec4f(*pixels[0]) : props->diffuseFactor;
const Vec3f specular = Vec3f(0.4f, 0.4f, 0.4f) +
0.2f * (pixels[1] ? toVec3f(*pixels[1]) : props->specularFactor);

float oneMinus = 1 - getMaxComponent(specular);

float pixelMet = solveMetallic(
getPerceivedBrightness(diffuse.xyz()),
getPerceivedBrightness(specular),
oneMinus);

Vec3f fromDiffuse = diffuse.xyz() * (oneMinus / (1.0f - dielectric.x) / fmax(1.0f - pixelMet, epsilon));
Vec3f fromSpecular = specular - dielectric * (1.0f - pixelMet) * (1.0f / fmax(pixelMet, epsilon));
Vec3f baseColor = Vec3f::Lerp(fromDiffuse, fromSpecular, pixelMet * pixelMet);

return { baseColor[0], baseColor[1], baseColor[2], diffuse[3] };
}, diffuseTex.occlusion == RAW_TEXTURE_OCCLUSION_TRANSPARENT);
}
emissiveFactor = props->emissiveFactor;
emissiveIntensity = 1.0f;
}
pbrMetRough.reset(new PBRMetallicRoughness(baseColorTex.get(), metRoughTex.get(), diffuseFactor, metallic, roughness));
}

std::shared_ptr<PBRSpecularGlossiness> pbrSpecGloss;
if (options.usePBRSpecGloss) {
Vec4f diffuseFactor;
Vec3f specularFactor;
float glossiness;
std::shared_ptr<TextureData> specGlossTex;
std::shared_ptr<TextureData> diffuseTex;

const Vec3f dielectric(0.04f, 0.04f, 0.04f);
const float epsilon = 1e-6f;
if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
/**
* PBR FBX Material -> PBR Spec/Gloss glTF.
*
* TODO: A complete mess. Low priority.
*/
RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get();
// we can estimate spec/gloss from met/rough by between Vec4f(0.04, 0.04, 0.04) and baseColor
// according to metalness, and then taking gloss to be the inverse of roughness
specGlossTex = merge3Tex("specgloss",
RAW_TEXTURE_USAGE_ALBEDO, RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_ROUGHNESS,
[&](const std::vector<const pixel *> pixels) -> pixel {
Vec3f baseColor(1.0f);
if (pixels[0]) {
baseColor.x = (*pixels[0])[0];
baseColor.y = (*pixels[0])[1];
baseColor.z = (*pixels[0])[2];
}
float metallic = pixels[1] ? (*pixels[1])[0] : 1.0f;
float roughness = pixels[2] ? (*pixels[2])[0] : 1.0f;
Vec3f spec = Vec3f::Lerp(dielectric, baseColor, metallic);
return { spec[0], spec[1], spec[2], 1.0f - roughness };
}, false);
diffuseTex = merge2Tex("albedo",
RAW_TEXTURE_USAGE_ALBEDO, RAW_TEXTURE_USAGE_METALLIC,
[&](const std::vector<const pixel *> pixels) -> pixel {
Vec3f baseColor(1.0f);
float alpha = 1.0f;
if (pixels[0]) {
baseColor[0] = (*pixels[0])[0];
baseColor[1] = (*pixels[0])[1];
baseColor[2] = (*pixels[0])[2];
alpha = (*pixels[0])[3];
}
float metallic = pixels[1] ? (*pixels[1])[0] : 1.0f;
Vec3f spec = Vec3f::Lerp(dielectric, baseColor, metallic);
float maxSpecComp = fmax(fmax(spec.x, spec.y), spec.z);
// attenuate baseColor to get specgloss-compliant diffuse; for details consult
// https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_pbrSpecularGlossiness
Vec3f diffuse = baseColor * (1 - dielectric[0]) * (1 - metallic) * fmax(1.0f - maxSpecComp, epsilon);
return { diffuse[0], diffuse[1], diffuse[2], alpha };
}, true);
diffuseFactor = props->diffuseFactor;
specularFactor = Vec3f::Lerp(dielectric, props->diffuseFactor.xyz(), props->metallic);
glossiness = 1.0f - props->roughness;

} else {
/**
* Traditional FBX Material -> PBR Spec/Gloss glTF.
*
* TODO: A complete mess. Low priority.
*/
// TODO: this section a ludicrous over-simplifictation; we can surely do better.
const RawTraditionalMatProps *props = ((RawTraditionalMatProps *) material.info.get());
specGlossTex = merge2Tex("specgloss",
RAW_TEXTURE_USAGE_SPECULAR, RAW_TEXTURE_USAGE_SHININESS,
[&](const std::vector<const pixel *> pixels) -> pixel {
const auto &spec = *(pixels[0]);
const auto &shine = *(pixels[1]);
return { spec[0], spec[1], spec[2], shine[0] };
}, false);
diffuseTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
diffuseFactor = props->diffuseFactor;
specularFactor = props->specularFactor;
glossiness = props->shininess;
}

pbrSpecGloss.reset(
new PBRSpecularGlossiness(
diffuseTex.get(), diffuseFactor, specGlossTex.get(), specularFactor, glossiness));
}

TextureData *normalTexture = simpleTex(RAW_TEXTURE_USAGE_NORMAL).get();
TextureData *emissiveTexture = simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get();

std::shared_ptr<KHRCommonMats> khrComMat;
if (options.useKHRMatCom) {
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,
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<KHRCmnUnlitMaterial> khrCmnUnlitMat;
if (options.useKHRMatUnlit) {
Expand Down Expand Up @@ -938,7 +740,7 @@ ModelData *Raw2Gltf(
material.name, isTransparent,
normalTexture, emissiveTexture,
emissiveFactor * emissiveIntensity,
khrComMat, khrCmnUnlitMat, pbrMetRough, pbrSpecGloss));
khrCmnUnlitMat, pbrMetRough));
materialsByName[materialHash(material)] = mData;
}

Expand Down Expand Up @@ -1209,21 +1011,9 @@ ModelData *Raw2Gltf(

{
std::vector<std::string> extensionsUsed, extensionsRequired;
if (options.useKHRMatCom) {
extensionsUsed.push_back(KHR_MATERIALS_COMMON);
if (!options.usePBRSpecGloss && !options.usePBRMetRough) {
extensionsRequired.push_back(KHR_MATERIALS_COMMON);
}
}
if (options.useKHRMatUnlit) {
extensionsUsed.push_back(KHR_MATERIALS_CMN_UNLIT);
}
if (options.usePBRSpecGloss) {
extensionsUsed.push_back(KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS);
if (!options.useKHRMatCom && !options.usePBRMetRough) {
extensionsRequired.push_back(KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS);
}
}
if (options.useDraco) {
extensionsUsed.push_back(KHR_DRACO_MESH_COMPRESSION);
extensionsRequired.push_back(KHR_DRACO_MESH_COMPRESSION);
Expand Down
32 changes: 15 additions & 17 deletions src/Raw2Gltf.h
Expand Up @@ -28,12 +28,10 @@ using json = nlohmann::basic_json<workaround_fifo_map>;
#include "FBX2glTF.h"
#include "RawModel.h"

static const std::string KHR_DRACO_MESH_COMPRESSION = "KHR_draco_mesh_compression";
static const std::string KHR_MATERIALS_COMMON = "KHR_materials_common";
static const std::string KHR_MATERIALS_CMN_UNLIT = "KHR_materials_unlit";
static const std::string KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS = "KHR_materials_pbrSpecularGlossiness";
const std::string KHR_DRACO_MESH_COMPRESSION = "KHR_draco_mesh_compression";
const std::string KHR_MATERIALS_CMN_UNLIT = "KHR_materials_unlit";

static const std::string extBufferFilename = "buffer.bin";
const std::string extBufferFilename = "buffer.bin";

struct ComponentType {
// OpenGL Datatype enums
Expand All @@ -52,8 +50,8 @@ struct ComponentType {
const unsigned int size;
};

static const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2};
static const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4};
const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2};
const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4};

// Map our low-level data types for glTF output
struct GLType {
Expand Down Expand Up @@ -101,16 +99,16 @@ struct GLType {
const std::string dataType;
};

static const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"};
static const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"};
static const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"};
static const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"};
static const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"};
static const GLType GLT_VEC4I = {CT_USHORT, 4, "VEC4"};
static const GLType GLT_MAT2F = {CT_USHORT, 4, "MAT2"};
static const GLType GLT_MAT3F = {CT_USHORT, 9, "MAT3"};
static const GLType GLT_MAT4F = {CT_FLOAT, 16, "MAT4"};
static const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"};
const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"};
const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"};
const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"};
const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"};
const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"};
const GLType GLT_VEC4I = {CT_USHORT, 4, "VEC4"};
const GLType GLT_MAT2F = {CT_USHORT, 4, "MAT2"};
const GLType GLT_MAT3F = {CT_USHORT, 9, "MAT3"};
const GLType GLT_MAT4F = {CT_FLOAT, 16, "MAT4"};
const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"};

/**
* The base of any indexed glTF entity.
Expand Down
15 changes: 11 additions & 4 deletions src/RawModel.cpp
Expand Up @@ -337,6 +337,8 @@ void RawModel::TransformGeometry(ComputeNormalsOption normals)
case BROKEN:
case ALWAYS:
size_t computedNormalsCount = this->CalculateNormals(normals == ComputeNormalsOption::BROKEN);
vertexAttributes |= RAW_VERTEX_ATTRIBUTE_NORMAL;

if (verboseOutput) {
if (normals == ComputeNormalsOption::BROKEN) {
fmt::printf("Repaired %lu empty normals.\n", computedNormalsCount);
Expand Down Expand Up @@ -572,13 +574,18 @@ Vec3f RawModel::getFaceNormal(int verts[3]) const

const Vec3f e0 = vertices[verts[(index + 1) % 3]].position - vertices[verts[index]].position;
const Vec3f e1 = vertices[verts[(index + 2) % 3]].position - vertices[verts[index]].position;

if (e0.LengthSquared() < FLT_MIN || e1.LengthSquared() < FLT_MIN) {
return Vec3f { 0.0f };
}
auto result = Vec3f::CrossProduct(e0, e1);
if (result.LengthSquared() < FLT_MIN) {
auto resultLengthSquared = result.LengthSquared();
if (resultLengthSquared < FLT_MIN) {
return Vec3f { 0.0f };
}
result.Normalize();
return result;
float edgeDot = std::max(-1.0f, std::min(1.0f, Vec3f::DotProduct(e0, e1)));
float angle = acos(edgeDot);
float area = resultLengthSquared / 2.0f;
return result.Normalized() * angle * area;
}

size_t RawModel::CalculateNormals(bool onlyBroken)
Expand Down

0 comments on commit 7c0715c

Please sign in to comment.