UNPKG

@gltf-transform/functions

Version:

Functions for common glTF modifications, written using the core API

126 lines (107 loc) 4.95 kB
import type { Document, Texture, Transform } from '@gltf-transform/core'; import { KHRMaterialsIOR, KHRMaterialsPBRSpecularGlossiness, KHRMaterialsSpecular, PBRSpecularGlossiness, } from '@gltf-transform/extensions'; import { createTransform, rewriteTexture } from './utils.js'; const NAME = 'metalRough'; export interface MetalRoughOptions {} const METALROUGH_DEFAULTS: Required<MetalRoughOptions> = {}; /** * Convert {@link Material}s from spec/gloss PBR workflow to metal/rough PBR workflow, * removing `KHR_materials_pbrSpecularGlossiness` and adding `KHR_materials_ior` and * `KHR_materials_specular`. The metal/rough PBR workflow is preferred for most use cases, * and is a prerequisite for other advanced PBR extensions provided by glTF. * * No options are currently implemented for this function. * * @category Transforms */ export function metalRough(_options: MetalRoughOptions = METALROUGH_DEFAULTS): Transform { return createTransform(NAME, async (doc: Document): Promise<void> => { const logger = doc.getLogger(); const extensionsUsed = doc .getRoot() .listExtensionsUsed() .map((ext) => ext.extensionName); if (!extensionsUsed.includes('KHR_materials_pbrSpecularGlossiness')) { logger.warn(`${NAME}: KHR_materials_pbrSpecularGlossiness not found on document.`); return; } const iorExtension = doc.createExtension(KHRMaterialsIOR); const specExtension = doc.createExtension(KHRMaterialsSpecular); const specGlossExtension = doc.createExtension(KHRMaterialsPBRSpecularGlossiness); const inputTextures = new Set<Texture | null>(); for (const material of doc.getRoot().listMaterials()) { const specGloss = material.getExtension<PBRSpecularGlossiness>('KHR_materials_pbrSpecularGlossiness'); if (!specGloss) continue; // Create specular extension. const specular = specExtension .createSpecular() .setSpecularFactor(1.0) .setSpecularColorFactor(specGloss.getSpecularFactor()); // Stash textures that might become unused, to check and clean up later. inputTextures.add(specGloss.getSpecularGlossinessTexture()); inputTextures.add(material.getBaseColorTexture()); inputTextures.add(material.getMetallicRoughnessTexture()); // Set up a metal/rough PBR material with IOR=Infinity (or 0), metallic=0. This // representation is precise and reliable, but perhaps less convenient for artists // than deriving a metalness value. Unfortunately we can't do that without imprecise // heuristics, and perhaps user tuning. // See: https://github.com/KhronosGroup/glTF/pull/1719#issuecomment-674365677 material .setBaseColorFactor(specGloss.getDiffuseFactor()) .setMetallicFactor(0) .setRoughnessFactor(1) .setExtension('KHR_materials_ior', iorExtension.createIOR().setIOR(1000)) .setExtension('KHR_materials_specular', specular); // Move diffuse -> baseColor. const diffuseTexture = specGloss.getDiffuseTexture(); if (diffuseTexture) { material.setBaseColorTexture(diffuseTexture); material.getBaseColorTextureInfo()!.copy(specGloss.getDiffuseTextureInfo()!); } // Move specular + gloss -> specular + roughness. const sgTexture = specGloss.getSpecularGlossinessTexture(); if (sgTexture) { // specularGlossiness -> specular. const sgTextureInfo = specGloss.getSpecularGlossinessTextureInfo()!; const specularTexture = doc.createTexture(); await rewriteTexture(sgTexture, specularTexture, (pixels, i, j) => { pixels.set(i, j, 3, 255); // Remove glossiness. }); specular.setSpecularTexture(specularTexture); specular.setSpecularColorTexture(specularTexture); specular.getSpecularTextureInfo()!.copy(sgTextureInfo); specular.getSpecularColorTextureInfo()!.copy(sgTextureInfo); // specularGlossiness -> roughness. const glossinessFactor = specGloss.getGlossinessFactor(); const metalRoughTexture = doc.createTexture(); await rewriteTexture(sgTexture, metalRoughTexture, (pixels, i, j) => { // Invert glossiness. const roughness = 255 - Math.round(pixels.get(i, j, 3) * glossinessFactor); pixels.set(i, j, 0, 0); pixels.set(i, j, 1, roughness); pixels.set(i, j, 2, 0); pixels.set(i, j, 3, 255); }); material.setMetallicRoughnessTexture(metalRoughTexture); material.getMetallicRoughnessTextureInfo()!.copy(sgTextureInfo); } else { specular.setSpecularColorFactor(specGloss.getSpecularFactor()); material.setRoughnessFactor(1 - specGloss.getGlossinessFactor()); } // Remove KHR_materials_pbrSpecularGlossiness from the material. material.setExtension('KHR_materials_pbrSpecularGlossiness', null); } // Remove KHR_materials_pbrSpecularGlossiness from the document. specGlossExtension.dispose(); // Clean up unused textures. for (const tex of inputTextures) { if (tex && tex.listParents().length === 1) tex.dispose(); } logger.debug(`${NAME}: Complete.`); }); }