| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661 |
- import { DoubleSide } from 'three/webgpu';
- import { MaterialXLogCodes } from './MaterialXLog.js';
- import { float, color, mul, clamp, vec2, cos, sin, pow, mix, element, transformNormalToView } from 'three/tsl';
- const mappedStandardSurfaceInputs = new Set( [
- 'base',
- 'base_color',
- 'specular_roughness',
- 'metalness',
- 'specular',
- 'specular_color',
- 'specular_anisotropy',
- 'specular_rotation',
- 'transmission',
- 'transmission_color',
- 'transmission_depth',
- 'thin_film_thickness',
- 'thin_film_IOR',
- 'sheen',
- 'sheen_color',
- 'sheen_roughness',
- 'coat',
- 'coat_color',
- 'coat_roughness',
- 'coat_normal',
- 'normal',
- 'opacity',
- 'specular_IOR',
- 'emission',
- 'emission_color',
- ] );
- const mappedGltfPbrInputs = new Set( [
- 'base_color',
- 'occlusion',
- 'roughness',
- 'metallic',
- 'normal',
- 'transmission',
- 'specular',
- 'specular_color',
- 'ior',
- 'alpha',
- 'alpha_mode',
- 'alpha_cutoff',
- 'iridescence',
- 'iridescence_ior',
- 'iridescence_thickness',
- 'sheen_color',
- 'sheen_roughness',
- 'clearcoat',
- 'clearcoat_roughness',
- 'clearcoat_normal',
- 'emissive',
- 'emissive_strength',
- 'attenuation_distance',
- 'attenuation_color',
- 'thickness',
- 'dispersion',
- 'anisotropy_strength',
- 'anisotropy_rotation',
- ] );
- const mappedOpenPbrInputs = new Set( [
- 'base_weight',
- 'base_color',
- 'specular_weight',
- 'specular_color',
- 'specular_roughness',
- 'base_metalness',
- 'specular_roughness_anisotropy',
- 'specular_ior',
- 'coat_weight',
- 'coat_ior',
- 'coat_color',
- 'coat_roughness',
- 'geometry_coat_normal',
- 'fuzz_weight',
- 'fuzz_color',
- 'fuzz_roughness',
- 'transmission_weight',
- 'transmission_color',
- 'transmission_depth',
- 'transmission_dispersion_scale',
- 'transmission_dispersion_abbe_number',
- 'geometry_normal',
- 'geometry_opacity',
- 'geometry_thin_walled',
- 'thin_film_weight',
- 'thin_film_thickness',
- 'thin_film_ior',
- 'emission_color',
- 'emission_luminance',
- ] );
- function warnIgnoredInputs( inputs, mappedInputs, log, surfaceCategory, nodeName ) {
- for ( const inputName of Object.keys( inputs ) ) {
- if ( mappedInputs.has( inputName ) === false ) {
- log.add(
- MaterialXLogCodes.IGNORED_SURFACE_INPUT,
- `${surfaceCategory} input "${inputName}" is currently ignored in MaterialX translation.`,
- nodeName,
- );
- }
- }
- }
- function hasNodeValue( value ) {
- return value !== undefined && value !== null;
- }
- function getConstNumber( node ) {
- if ( typeof node === 'number' ) return node;
- if ( ! node || typeof node !== 'object' ) return null;
- let cursor = node;
- const visited = new Set();
- while ( cursor && typeof cursor === 'object' ) {
- if ( visited.has( cursor ) ) break;
- visited.add( cursor );
- if ( typeof cursor.value === 'number' ) return cursor.value;
- cursor = cursor.node;
- }
- return null;
- }
- function isConstNear( node, target, epsilon = 1e-6 ) {
- const value = getConstNumber( node );
- if ( value === null ) return false;
- return Math.abs( value - target ) <= epsilon;
- }
- function isEffectivelyZero( node, epsilon = 1e-6 ) {
- return isConstNear( node, 0, epsilon );
- }
- function isEffectivelyOne( node, epsilon = 1e-6 ) {
- return isConstNear( node, 1, epsilon );
- }
- function isEnabledWeightNode( node ) {
- if ( hasNodeValue( node ) === false ) return false;
- return isEffectivelyZero( node ) === false;
- }
- function setAnisotropy( material, strengthNode, rotationNode ) {
- if ( ! hasNodeValue( strengthNode ) && ! hasNodeValue( rotationNode ) ) return;
- if ( isEffectivelyZero( strengthNode ) && isEffectivelyZero( rotationNode ) ) return;
- const strength = hasNodeValue( strengthNode ) ? strengthNode : float( 0 );
- const rotation = hasNodeValue( rotationNode ) ? rotationNode : float( 0 );
- material.anisotropyNode = vec2( cos( rotation ), sin( rotation ) ).mul( strength );
- material.anisotropyRotationNode = rotation;
- }
- function setTransmissionFlags( material, transmissionNode, opacityNode, allowOpacityTransparency = true ) {
- if ( allowOpacityTransparency && hasNodeValue( opacityNode ) && isEffectivelyOne( opacityNode ) === false ) {
- material.transparent = true;
- }
- if ( isEnabledWeightNode( transmissionNode ) ) {
- material.side = DoubleSide;
- material.transparent = true;
- }
- }
- function toAttenuationDistance( distanceNode, hasAttenuationColorInput ) {
- if ( hasNodeValue( distanceNode ) ) return distanceNode;
- // When attenuation tint is authored without a distance, default to a
- // finite value so absorption tinting is visible.
- return hasAttenuationColorInput ? float( 1 ) : undefined;
- }
- function buildGltfOpacityNode( alphaNode, alphaModeNode ) {
- const alphaMode = getConstNumber( alphaModeNode );
- const roundedMode = alphaMode === null ? 2 : Math.round( alphaMode );
- const alpha = alphaNode ?? float( 1 );
- if ( roundedMode === 0 ) return float( 1 );
- if ( roundedMode === 1 ) return alpha;
- return alpha;
- }
- function applyStandardSurface( material, inputs, log, nodeName ) {
- let colorNode = null;
- if ( inputs.base && inputs.base_color ) colorNode = mul( inputs.base, inputs.base_color );
- else if ( inputs.base ) colorNode = inputs.base;
- else if ( inputs.base_color ) colorNode = inputs.base_color;
- if ( inputs.coat_color ) {
- colorNode = colorNode ? mul( colorNode, inputs.coat_color ) : colorNode;
- }
- const roughnessNode = inputs.specular_roughness;
- const opacityNode = hasNodeValue( inputs.opacity ) ? element( inputs.opacity, 0 ) : undefined;
- const transmissionNode = inputs.transmission;
- const transmissionColorNode = inputs.transmission_color;
- const transmissionEnabled = isEnabledWeightNode( transmissionNode );
- const sheenEnabled = isEnabledWeightNode( inputs.sheen );
- const clearcoatEnabled = isEnabledWeightNode( inputs.coat );
- let emissiveNode = inputs.emission;
- const emissionColorNode = inputs.emission_color;
- if ( hasNodeValue( emissionColorNode ) ) {
- emissiveNode = emissiveNode ? mul( emissiveNode, emissionColorNode ) : emissionColorNode;
- }
- const thinFilmThicknessNode = inputs.thin_film_thickness;
- const thinFilmIorNode = clamp( inputs.thin_film_IOR || float( 1.5 ), float( 1.0 ), float( 2.333 ) );
- const thinFilmEnabled = isEnabledWeightNode( thinFilmThicknessNode );
- const transmissionTintNode = hasNodeValue( transmissionColorNode ) ? transmissionColorNode : color( 1, 1, 1 );
- if ( transmissionEnabled ) {
- const baseForTransmissionMix = colorNode || color( 0.8, 0.8, 0.8 );
- // Suppress diffuse/base tint as transmission ramps up.
- colorNode = mix( baseForTransmissionMix, transmissionTintNode, transmissionNode );
- }
- material.colorNode = colorNode || color( 0.8, 0.8, 0.8 );
- if ( hasNodeValue( opacityNode ) && isEffectivelyOne( opacityNode ) === false ) {
- material.opacityNode = opacityNode;
- }
- material.roughnessNode = roughnessNode || float( 0.2 );
- if ( hasNodeValue( inputs.metalness ) && isEffectivelyZero( inputs.metalness ) === false ) {
- material.metalnessNode = inputs.metalness;
- }
- if ( hasNodeValue( inputs.specular ) && isEffectivelyOne( inputs.specular ) === false ) {
- material.specularIntensityNode = inputs.specular;
- }
- material.specularColorNode = inputs.specular_color || color( 1, 1, 1 );
- const iorNode = inputs.specular_IOR;
- if ( hasNodeValue( iorNode ) && isConstNear( iorNode, 1.5 ) === false ) {
- material.iorNode = iorNode;
- }
- setAnisotropy( material, inputs.specular_anisotropy, inputs.specular_rotation );
- if ( transmissionEnabled ) {
- material.transmissionNode = transmissionNode;
- if ( hasNodeValue( transmissionColorNode ) ) material.transmissionColorNode = transmissionColorNode;
- if ( hasNodeValue( inputs.transmission_depth ) && isEffectivelyZero( inputs.transmission_depth ) === false ) {
- material.thicknessNode = inputs.transmission_depth;
- } else {
- // Keep transmissive standard_surface materials volumetric when
- // transmission_depth is omitted or authored as zero.
- material.thickness = 1;
- }
- }
- if ( thinFilmEnabled ) {
- material.iridescenceThicknessNode = thinFilmThicknessNode;
- material.iridescenceIORNode = thinFilmIorNode;
- material.iridescenceNode = float( 1 );
- }
- const sheenColor = inputs.sheen_color || color( 1, 1, 1 );
- if ( sheenEnabled ) {
- const sheenRoughness = hasNodeValue( inputs.sheen_roughness ) ? inputs.sheen_roughness : float( 0.3 );
- material.sheenNode = mul( inputs.sheen, sheenColor );
- material.sheenRoughnessNode = sheenRoughness;
- }
- if ( clearcoatEnabled ) {
- material.clearcoatNode = inputs.coat;
- if ( hasNodeValue( inputs.coat_roughness ) ) {
- material.clearcoatRoughnessNode = inputs.coat_roughness;
- }
- }
- if ( clearcoatEnabled && hasNodeValue( inputs.coat_normal ) ) {
- material.clearcoatNormalNode = transformNormalToView( inputs.coat_normal );
- }
- if ( hasNodeValue( inputs.normal ) ) material.normalNode = transformNormalToView( inputs.normal );
- if ( hasNodeValue( emissiveNode ) ) material.emissiveNode = emissiveNode;
- setTransmissionFlags( material, transmissionNode, opacityNode );
- warnIgnoredInputs( inputs, mappedStandardSurfaceInputs, log, 'standard_surface', nodeName );
- }
- function applyGltfPbrSurface( material, inputs, log, nodeName ) {
- const alphaModeLiteral = getConstNumber( inputs.alpha_mode );
- const alphaMode = alphaModeLiteral === null ? 2 : Math.round( alphaModeLiteral );
- const isAlphaMaskMode = alphaMode === 1;
- const isAlphaBlendMode = alphaMode === 2;
- const opacityNode = buildGltfOpacityNode( inputs.alpha, inputs.alpha_mode );
- const alphaCutoffNode = inputs.alpha_cutoff ?? float( 0.5 );
- const hasAttenuationColorInput = Object.prototype.hasOwnProperty.call( inputs, 'attenuation_color' );
- const transmissionEnabled = isEnabledWeightNode( inputs.transmission );
- const clearcoatEnabled = isEnabledWeightNode( inputs.clearcoat );
- const sheenEnabled = hasNodeValue( inputs.sheen_color ) || isEnabledWeightNode( inputs.sheen_roughness );
- const iridescenceEnabled = isEnabledWeightNode( inputs.iridescence );
- material.colorNode = inputs.base_color || color( 1, 1, 1 );
- if ( hasNodeValue( inputs.occlusion ) ) material.aoNode = inputs.occlusion;
- material.roughnessNode = inputs.roughness || float( 1 );
- material.metalnessNode = inputs.metallic || float( 1 );
- if ( hasNodeValue( inputs.specular ) && isEffectivelyOne( inputs.specular ) === false ) {
- material.specularIntensityNode = inputs.specular;
- }
- material.specularColorNode = inputs.specular_color || color( 1, 1, 1 );
- if ( hasNodeValue( inputs.ior ) && isConstNear( inputs.ior, 1.5 ) === false ) {
- material.iorNode = inputs.ior;
- }
- if ( hasNodeValue( opacityNode ) && isEffectivelyOne( opacityNode ) === false ) {
- material.opacityNode = opacityNode;
- }
- if ( isAlphaMaskMode ) {
- material.alphaTestNode = alphaCutoffNode;
- const alphaCutoff = getConstNumber( alphaCutoffNode );
- if ( alphaCutoff !== null ) material.alphaTest = alphaCutoff;
- }
- if ( transmissionEnabled ) {
- material.transmissionNode = inputs.transmission;
- }
- if ( clearcoatEnabled ) {
- material.clearcoatNode = inputs.clearcoat;
- if ( hasNodeValue( inputs.clearcoat_roughness ) && isEffectivelyZero( inputs.clearcoat_roughness ) === false ) {
- material.clearcoatRoughnessNode = inputs.clearcoat_roughness;
- }
- }
- if ( sheenEnabled ) {
- const sheenColor = hasNodeValue( inputs.sheen_color ) ? inputs.sheen_color : color( 0, 0, 0 );
- const sheenRoughness = hasNodeValue( inputs.sheen_roughness ) ? inputs.sheen_roughness : float( 0 );
- material.sheenRoughnessNode = sheenRoughness;
- material.sheenNode = sheenColor;
- }
- if ( iridescenceEnabled ) {
- material.iridescenceNode = inputs.iridescence;
- if ( hasNodeValue( inputs.iridescence_ior ) && isConstNear( inputs.iridescence_ior, 1.3 ) === false ) {
- material.iridescenceIORNode = inputs.iridescence_ior;
- }
- if ( hasNodeValue( inputs.iridescence_thickness ) && isConstNear( inputs.iridescence_thickness, 100 ) === false ) {
- material.iridescenceThicknessNode = inputs.iridescence_thickness;
- }
- }
- material.attenuationDistanceNode = toAttenuationDistance( inputs.attenuation_distance, hasAttenuationColorInput );
- material.attenuationColorNode = inputs.attenuation_color || color( 1, 1, 1 );
- if ( hasNodeValue( inputs.thickness ) ) {
- if ( isEffectivelyZero( inputs.thickness ) === false ) {
- material.thicknessNode = inputs.thickness;
- }
- } else if ( transmissionEnabled ) {
- // Keep transmissive glTF materials volumetric even when thickness is omitted.
- material.thickness = 1;
- }
- if ( hasNodeValue( inputs.dispersion ) && isEffectivelyZero( inputs.dispersion ) === false ) {
- material.dispersionNode = inputs.dispersion;
- }
- const anisotropyStrength = inputs.anisotropy_strength;
- const anisotropyRotation = inputs.anisotropy_rotation;
- setAnisotropy( material, anisotropyStrength, anisotropyRotation );
- if ( hasNodeValue( inputs.normal ) ) material.normalNode = transformNormalToView( inputs.normal );
- if ( hasNodeValue( inputs.clearcoat_normal ) ) {
- material.clearcoatNormalNode = transformNormalToView( inputs.clearcoat_normal );
- }
- if ( hasNodeValue( inputs.emissive ) && hasNodeValue( inputs.emissive_strength ) )
- material.emissiveNode = mul( inputs.emissive, inputs.emissive_strength );
- else if ( hasNodeValue( inputs.emissive ) ) material.emissiveNode = inputs.emissive;
- setTransmissionFlags( material, inputs.transmission, opacityNode, isAlphaBlendMode );
- warnIgnoredInputs( inputs, mappedGltfPbrInputs, log, 'gltf_pbr', nodeName );
- }
- function applyOpenPbrSurface( material, inputs, log, nodeName ) {
- const baseWeight = inputs.base_weight || float( 1 );
- const baseColor = inputs.base_color || color( 0.8, 0.8, 0.8 );
- const coatEnabled = isEnabledWeightNode( inputs.coat_weight );
- const fuzzEnabled = isEnabledWeightNode( inputs.fuzz_weight );
- const transmissionEnabled = isEnabledWeightNode( inputs.transmission_weight );
- const thinFilmEnabled = isEnabledWeightNode( inputs.thin_film_weight );
- material.colorNode = mul( baseWeight, baseColor );
- if ( hasNodeValue( inputs.base_metalness ) && isEffectivelyZero( inputs.base_metalness ) === false ) {
- material.metalnessNode = inputs.base_metalness;
- }
- material.roughnessNode = inputs.specular_roughness || float( 0.3 );
- if ( hasNodeValue( inputs.specular_weight ) && isEffectivelyOne( inputs.specular_weight ) === false ) {
- material.specularIntensityNode = inputs.specular_weight;
- }
- material.specularColorNode = inputs.specular_color || color( 1, 1, 1 );
- const openPbrIorNode = inputs.specular_ior;
- if ( hasNodeValue( openPbrIorNode ) && isConstNear( openPbrIorNode, 1.5 ) === false ) {
- material.iorNode = openPbrIorNode;
- }
- setAnisotropy( material, inputs.specular_roughness_anisotropy, float( 0 ) );
- if ( coatEnabled ) {
- const coatWeightNode = inputs.coat_weight || float( 0 );
- if ( hasNodeValue( inputs.coat_ior ) ) {
- const coatIorNode = inputs.coat_ior;
- const coatIorMinusOne = coatIorNode.sub( float( 1 ) );
- const coatIorPlusOne = coatIorNode.add( float( 1 ) );
- const coatF0Node = coatIorMinusOne.div( coatIorPlusOne );
- const normalizedClearcoatNode = coatF0Node.mul( coatF0Node ).div( float( 0.04 ) );
- material.clearcoatNode = clamp( coatWeightNode.mul( normalizedClearcoatNode ), float( 0 ), float( 1 ) );
- } else {
- material.clearcoatNode = coatWeightNode;
- }
- if ( hasNodeValue( inputs.coat_roughness ) && isEffectivelyZero( inputs.coat_roughness ) === false ) {
- material.clearcoatRoughnessNode = inputs.coat_roughness;
- }
- if ( hasNodeValue( inputs.geometry_coat_normal ) ) {
- material.clearcoatNormalNode = transformNormalToView( inputs.geometry_coat_normal );
- }
- }
- const fuzzWeight = inputs.fuzz_weight || float( 0 );
- const fuzzColor = inputs.fuzz_color || color( 1, 1, 1 );
- if ( fuzzEnabled ) {
- material.sheenNode = mul( fuzzWeight, fuzzColor );
- if ( hasNodeValue( inputs.fuzz_roughness ) ) {
- material.sheenRoughnessNode = pow( inputs.fuzz_roughness, float( 1.5 ) );
- }
- }
- if ( transmissionEnabled ) {
- material.transmissionNode = inputs.transmission_weight;
- if ( hasNodeValue( inputs.transmission_color ) ) material.attenuationColorNode = inputs.transmission_color;
- }
- const transmissionDepthNode = inputs.transmission_depth;
- if ( transmissionEnabled && hasNodeValue( transmissionDepthNode ) && isEffectivelyZero( transmissionDepthNode ) === false ) {
- material.thicknessNode = hasNodeValue( inputs.geometry_thin_walled )
- ? inputs.geometry_thin_walled.select( float( 0 ), transmissionDepthNode )
- : transmissionDepthNode;
- material.attenuationDistanceNode = transmissionDepthNode;
- } else if ( transmissionEnabled ) {
- // Keep transmissive OpenPBR materials volumetric even when depth is omitted.
- material.thickness = 1;
- }
- const transmissionDispersionAbbe = inputs.transmission_dispersion_abbe_number || float( 20 );
- if ( transmissionEnabled && hasNodeValue( inputs.transmission_dispersion_scale ) && isEffectivelyZero( inputs.transmission_dispersion_scale ) === false ) {
- material.dispersionNode = inputs.transmission_dispersion_scale.mul( float( 20 ) ).div( transmissionDispersionAbbe );
- }
- if ( hasNodeValue( inputs.geometry_opacity ) && isEffectivelyOne( inputs.geometry_opacity ) === false ) {
- material.opacityNode = inputs.geometry_opacity;
- }
- if ( hasNodeValue( inputs.geometry_normal ) ) material.normalNode = transformNormalToView( inputs.geometry_normal );
- if ( thinFilmEnabled ) {
- material.iridescenceNode = inputs.thin_film_weight;
- if ( hasNodeValue( inputs.thin_film_thickness ) && isConstNear( inputs.thin_film_thickness, 0.5 ) === false ) {
- material.iridescenceThicknessNode = inputs.thin_film_thickness.mul( float( 1000 ) );
- }
- if ( hasNodeValue( inputs.thin_film_ior ) && isConstNear( inputs.thin_film_ior, 1.4 ) === false ) {
- material.iridescenceIORNode = inputs.thin_film_ior;
- }
- }
- const emissionColor = hasNodeValue( inputs.emission_color ) ? inputs.emission_color : color( 1, 1, 1 );
- const emissionLuminance = hasNodeValue( inputs.emission_luminance ) ? inputs.emission_luminance : float( 0 );
- if ( isEffectivelyZero( emissionLuminance ) === false ) {
- material.emissiveNode = mul( emissionColor, emissionLuminance );
- }
- if ( hasNodeValue( inputs.geometry_opacity ) && isEffectivelyOne( inputs.geometry_opacity ) === false ) material.transparent = true;
- if ( transmissionEnabled ) material.transparent = true;
- setTransmissionFlags( material, inputs.transmission_weight, inputs.geometry_opacity );
- warnIgnoredInputs( inputs, mappedOpenPbrInputs, log, 'open_pbr_surface', nodeName );
- }
- const MaterialXSurfaceMappings = {
- standard_surface: applyStandardSurface,
- gltf_pbr: applyGltfPbrSurface,
- open_pbr_surface: applyOpenPbrSurface,
- };
- const surfaceMapperRegistry = new Map( Object.entries( MaterialXSurfaceMappings ).map( ( [ category, apply ] ) => [
- category,
- { category, apply },
- ] ) );
- function getSurfaceMapper( category ) {
- return surfaceMapperRegistry.get( category );
- }
- function getSupportedSurfaceCategories() {
- return [ ...surfaceMapperRegistry.keys() ];
- }
- export {
- MaterialXSurfaceMappings,
- surfaceMapperRegistry,
- getSurfaceMapper,
- getSupportedSurfaceCategories,
- applyStandardSurface,
- applyGltfPbrSurface,
- applyOpenPbrSurface,
- mappedStandardSurfaceInputs,
- mappedGltfPbrInputs,
- mappedOpenPbrInputs,
- };
|