-
-
Notifications
You must be signed in to change notification settings - Fork 145
/
metal-rough.ts
129 lines (110 loc) · 5.05 KB
/
metal-rough.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
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 {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const options = { ...METALROUGH_DEFAULTS, ..._options } as Required<MetalRoughOptions>;
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.`);
});
}