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.
This commit is contained in:
Chris Subagio 2020-06-02 12:26:02 -07:00
parent 739ee5db94
commit 43b36587da
2 changed files with 82 additions and 14 deletions

View File

@ -308,6 +308,19 @@ that route should be digested propertly by FBX2glTF.
(A happy note: Allegorithmic's Substance Painter also exports Stingray PBS, (A happy note: Allegorithmic's Substance Painter also exports Stingray PBS,
when hooked up to Maya.) 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 ## Draco Compression
The tool will optionally apply [Draco](https://github.com/google/draco) The tool will optionally apply [Draco](https://github.com/google/draco)

View File

@ -261,41 +261,96 @@ ModelData* Raw2Gltf(
*/ */
RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get(); 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 hasMetallicMap = material.textures[RAW_TEXTURE_USAGE_METALLIC] >= 0;
bool hasRoughnessMap = material.textures[RAW_TEXTURE_USAGE_ROUGHNESS] >= 0; bool hasRoughnessMap = material.textures[RAW_TEXTURE_USAGE_ROUGHNESS] >= 0;
bool hasOcclusionMap = material.textures[RAW_TEXTURE_USAGE_OCCLUSION] >= 0; bool hasOcclusionMap = material.textures[RAW_TEXTURE_USAGE_OCCLUSION] >= 0;
bool atLeastTwoMaps = hasMetallicMap ? (hasRoughnessMap || hasOcclusionMap)
: (hasRoughnessMap && hasMetallicMap); auto texturesAreSame = [&](RawTextureUsage a, RawTextureUsage b) -> bool {
if (!atLeastTwoMaps) { // note: at this point the usages will be different, so we can't just compare indexes
// this handles the case of 0 or 1 maps supplied 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 "<empty>";
}
};
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 aoMetRoughTex = hasMetallicMap
? simpleTex(RAW_TEXTURE_USAGE_METALLIC) ? simpleTex(RAW_TEXTURE_USAGE_METALLIC)
: (hasRoughnessMap : (hasRoughnessMap
? simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS) ? simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS)
: (hasOcclusionMap ? simpleTex(RAW_TEXTURE_USAGE_OCCLUSION) : nullptr)); : (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 { } else {
// otherwise merge occlusion into the red channel, metallic into blue channel, and /* otherwise we always have to create a new texture that merges
// roughness into the green, of a new combinatory texture * occlusion into the red channel
* roughness into the green
* metallic into blue channel
* with defaults for any unspecified channels
*/
aoMetRoughTex = textureBuilder.combine( aoMetRoughTex = textureBuilder.combine(
{ {
material.textures[RAW_TEXTURE_USAGE_OCCLUSION], material.textures[RAW_TEXTURE_USAGE_OCCLUSION],
material.textures[RAW_TEXTURE_USAGE_METALLIC],
material.textures[RAW_TEXTURE_USAGE_ROUGHNESS], material.textures[RAW_TEXTURE_USAGE_ROUGHNESS],
material.textures[RAW_TEXTURE_USAGE_METALLIC],
}, },
"ao_met_rough", "ao_met_rough",
[&](const std::vector<const TextureBuilder::pixel*> pixels) [&](const std::vector<const TextureBuilder::pixel*> pixels)
-> TextureBuilder::pixel { -> TextureBuilder::pixel {
const float occlusion = (*pixels[0])[0]; /**
const float metallic = (*pixels[1])[0] * (hasMetallicMap ? 1 : props->metallic); * note: we're picking the channels from the sources aligned with where they're going
const float roughness = * just in case they were authored that way. This makes an existing ORM texture
(*pixels[2])[0] * (hasRoughnessMap ? 1 : props->roughness); * "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, return {{occlusion,
props->invertRoughnessMap ? 1.0f - roughness : roughness, props->invertRoughnessMap ? 1.0f - roughness : roughness,
metallic, metallic,
1}}; 1}};
}, },
false); 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); baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
diffuseFactor = props->diffuseFactor; diffuseFactor = props->diffuseFactor;