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 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;