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:
parent
739ee5db94
commit
43b36587da
13
README.md
13
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,
|
(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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue