From 43b36587da9010e84ceec69b5c1398b43bb2c225 Mon Sep 17 00:00:00 2001 From: Chris Subagio Date: Tue, 2 Jun 2020 12:26:02 -0700 Subject: [PATCH 1/2] Add support for prebuilt ORM textures Before this change, the texture assignments for Occlusion, Roughness, and Metalness in the Stingray PBR material path were assumed to be single channel images where the R should be used to build a merged ORM texture. This precluded the use of prebuilt ORM textures. This commit proposes a few changes: * if the same texture is detected in all 3 channels, assume it already is ORM and just pass through. * when combining textures, read R/G/B for O/R/M rather than R/R/R. This allows merging of prebuild ORM textures. --- README.md | 13 +++++++ src/gltf/Raw2Gltf.cpp | 83 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8abadab..bccb708 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,19 @@ that route should be digested propertly by FBX2glTF. (A happy note: Allegorithmic's Substance Painter also exports Stingray PBS, when hooked up to Maya.) +When processing PBR materials, this converter will always pack Occlusion, +Roughness, and Metallness into a single combined ORM texture, with each parameter +being in the R, G and, B channels respectively. If you specify a texture in any +of the Stingray material slots, a full ORM will be generated; if you leave all 3 +blank, then no ORM texture will be assigned. + +* Should you specify different textures for each, then they will + be merged into their respective channels. Any missing textures will be + defaulted to: Occlusion 1, Roughness 0, and Metallness 0. Note, all textures + must have the same dimensions. +* Should you specify the same texture in all 3 slots, then the texture will + be assumed to already be a packed ORM, and will be used as-is. + ## Draco Compression The tool will optionally apply [Draco](https://github.com/google/draco) diff --git a/src/gltf/Raw2Gltf.cpp b/src/gltf/Raw2Gltf.cpp index 0e2c592..3a16596 100644 --- a/src/gltf/Raw2Gltf.cpp +++ b/src/gltf/Raw2Gltf.cpp @@ -261,41 +261,96 @@ ModelData* Raw2Gltf( */ RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get(); - // determine if we need to generate a combined map + // determine if we need to generate a combined map, or if we only have 1 map to pass through bool hasMetallicMap = material.textures[RAW_TEXTURE_USAGE_METALLIC] >= 0; bool hasRoughnessMap = material.textures[RAW_TEXTURE_USAGE_ROUGHNESS] >= 0; bool hasOcclusionMap = material.textures[RAW_TEXTURE_USAGE_OCCLUSION] >= 0; - bool atLeastTwoMaps = hasMetallicMap ? (hasRoughnessMap || hasOcclusionMap) - : (hasRoughnessMap && hasMetallicMap); - if (!atLeastTwoMaps) { - // this handles the case of 0 or 1 maps supplied + + auto texturesAreSame = [&](RawTextureUsage a, RawTextureUsage b) -> bool { + // note: at this point the usages will be different, so we can't just compare indexes + return StringUtils::CompareNoCase( + raw.GetTexture(material.textures[a]).fileLocation, + raw.GetTexture(material.textures[b]).fileLocation ) == 0; + }; + + bool isPassThroughTexture = hasOcclusionMap && hasRoughnessMap && hasMetallicMap; + if (isPassThroughTexture) { + isPassThroughTexture = + texturesAreSame(RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_ROUGHNESS) && + texturesAreSame(RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_OCCLUSION); + } + + auto textureName = [&](RawTextureUsage usage){ + int index = material.textures[usage]; + if (index >= 0) { + return raw.GetTexture(index).name.c_str(); + } else { + return ""; + } + }; + + if (!(hasMetallicMap || hasRoughnessMap || hasOcclusionMap)) { + // no data, assume it's a material that just relies on the uniform properties + aoMetRoughTex = nullptr; + if (verboseOutput) { + fmt::printf("Material %s: no ORM textures detected\n", material.name.c_str() ); + } + } + else if (isPassThroughTexture) { + // this handles the case where the same map is assigned to all the channels aoMetRoughTex = hasMetallicMap ? simpleTex(RAW_TEXTURE_USAGE_METALLIC) : (hasRoughnessMap - ? simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS) - : (hasOcclusionMap ? simpleTex(RAW_TEXTURE_USAGE_OCCLUSION) : nullptr)); + ? simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS) + : (hasOcclusionMap + ? simpleTex(RAW_TEXTURE_USAGE_OCCLUSION) + : nullptr)); + if (verboseOutput) { + if (aoMetRoughTex) { + fmt::printf("Material %s: detected single ORM texture: %s\n", material.name.c_str(), aoMetRoughTex->name.c_str()); + } else { + fmt::printf("Material %s: no ORM textures detected\n", material.name.c_str() ); + } + } } else { - // otherwise merge occlusion into the red channel, metallic into blue channel, and - // roughness into the green, of a new combinatory texture + /* otherwise we always have to create a new texture that merges + * occlusion into the red channel + * roughness into the green + * metallic into blue channel + * with defaults for any unspecified channels + */ aoMetRoughTex = textureBuilder.combine( { material.textures[RAW_TEXTURE_USAGE_OCCLUSION], - material.textures[RAW_TEXTURE_USAGE_METALLIC], material.textures[RAW_TEXTURE_USAGE_ROUGHNESS], + material.textures[RAW_TEXTURE_USAGE_METALLIC], }, "ao_met_rough", [&](const std::vector pixels) -> TextureBuilder::pixel { - const float occlusion = (*pixels[0])[0]; - const float metallic = (*pixels[1])[0] * (hasMetallicMap ? 1 : props->metallic); - const float roughness = - (*pixels[2])[0] * (hasRoughnessMap ? 1 : props->roughness); + /** + * note: we're picking the channels from the sources aligned with where they're going + * just in case they were authored that way. This makes an existing ORM texture + * "pass through", and has no effect on a grey single type texture. + */ + const float occlusion = hasOcclusionMap ? (*pixels[0])[0] : 1; + const float roughness = (*pixels[1])[1] * (hasRoughnessMap ? 1 : props->roughness); + const float metallic = (*pixels[2])[2] * (hasMetallicMap ? 1 : props->metallic); return {{occlusion, props->invertRoughnessMap ? 1.0f - roughness : roughness, metallic, 1}}; }, false); + if ( aoMetRoughTex && verboseOutput ) { + fmt::printf("Material %s: detected multiple ORM textures, combined: [%s, %s, %s] into [%s]\n", + material.name.c_str(), + textureName(RAW_TEXTURE_USAGE_OCCLUSION), + textureName(RAW_TEXTURE_USAGE_ROUGHNESS), + textureName(RAW_TEXTURE_USAGE_METALLIC), + aoMetRoughTex->name.c_str() + ); + } } baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO); diffuseFactor = props->diffuseFactor; From 2b94b87a960749015edacb3f3dee757a7bb0cce9 Mon Sep 17 00:00:00 2001 From: Chris Subagio Date: Tue, 2 Jun 2020 12:27:33 -0700 Subject: [PATCH 2/2] adding common build / dev environment related ignores that came up after building the project as directed, (plus isolating Conan using pipenv), was left with these extraneous files that shouldn't be committed --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 7ae5cdd..6013d76 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,8 @@ npm/fbx2gltf/node_modules/ npm/tests/node_modules/ npm/tests/test/*.js npm/tests/test/*.js.map +build +sdk +.vscode +Pipfile +Pipfile.lock