MaterialXLoader.js 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. import {
  2. FileLoader, Loader, TextureLoader, RepeatWrapping, MeshBasicNodeMaterial,
  3. MeshPhysicalNodeMaterial, DoubleSide,
  4. } from 'three/webgpu';
  5. import {
  6. float, bool, int, vec2, vec3, vec4, color, texture,
  7. positionLocal, positionWorld, uv, vertexColor,
  8. normalLocal, normalWorld, tangentLocal, tangentWorld,
  9. mul, abs, sign, floor, ceil, round, sin, cos, tan,
  10. asin, acos, sqrt, exp, clamp, min, max, normalize, length, dot, cross, normalMap,
  11. remap, smoothstep, luminance, mx_rgbtohsv, mx_hsvtorgb,
  12. mix, saturation, transpose, determinant, inverse, log, reflect, refract, element,
  13. mx_ramplr, mx_ramptb, mx_splitlr, mx_splittb,
  14. mx_fractal_noise_float, mx_noise_float, mx_cell_noise_float, mx_worley_noise_float,
  15. mx_transform_uv,
  16. mx_safepower, mx_contrast,
  17. mx_srgb_texture_to_lin_rec709,
  18. mx_add, mx_atan2, mx_divide, mx_modulo, mx_multiply, mx_power, mx_subtract,
  19. mx_timer, mx_frame, mat3, mx_ramp4,
  20. mx_invert, mx_ifgreater, mx_ifgreatereq, mx_ifequal, distance,
  21. mx_separate, mx_place2d, mx_rotate2d, mx_rotate3d, mx_heighttonormal,
  22. mx_unifiednoise2d, mx_unifiednoise3d
  23. } from 'three/tsl';
  24. const colorSpaceLib = {
  25. mx_srgb_texture_to_lin_rec709
  26. };
  27. class MXElement {
  28. constructor( name, nodeFunc, params = [] ) {
  29. this.name = name;
  30. this.nodeFunc = nodeFunc;
  31. this.params = params;
  32. }
  33. }
  34. // Ref: https://github.com/mrdoob/three.js/issues/24674
  35. // Enhanced separate node to support multi-output referencing (outx, outy, outz, outw)
  36. // Type/arity-aware MaterialX node wrappers
  37. const MXElements = [
  38. // << Math >>
  39. new MXElement( 'add', mx_add, [ 'in1', 'in2' ] ),
  40. new MXElement( 'subtract', mx_subtract, [ 'in1', 'in2' ] ),
  41. new MXElement( 'multiply', mx_multiply, [ 'in1', 'in2' ] ),
  42. new MXElement( 'divide', mx_divide, [ 'in1', 'in2' ] ),
  43. new MXElement( 'modulo', mx_modulo, [ 'in1', 'in2' ] ),
  44. new MXElement( 'absval', abs, [ 'in1', 'in2' ] ),
  45. new MXElement( 'sign', sign, [ 'in1', 'in2' ] ),
  46. new MXElement( 'floor', floor, [ 'in1', 'in2' ] ),
  47. new MXElement( 'ceil', ceil, [ 'in1', 'in2' ] ),
  48. new MXElement( 'round', round, [ 'in1', 'in2' ] ),
  49. new MXElement( 'power', mx_power, [ 'in1', 'in2' ] ),
  50. new MXElement( 'sin', sin, [ 'in' ] ),
  51. new MXElement( 'cos', cos, [ 'in' ] ),
  52. new MXElement( 'tan', tan, [ 'in' ] ),
  53. new MXElement( 'asin', asin, [ 'in' ] ),
  54. new MXElement( 'acos', acos, [ 'in' ] ),
  55. new MXElement( 'atan2', mx_atan2, [ 'in1', 'in2' ] ),
  56. new MXElement( 'sqrt', sqrt, [ 'in' ] ),
  57. new MXElement( 'ln', log, [ 'in' ] ),
  58. new MXElement( 'exp', exp, [ 'in' ] ),
  59. new MXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ),
  60. new MXElement( 'min', min, [ 'in1', 'in2' ] ),
  61. new MXElement( 'max', max, [ 'in1', 'in2' ] ),
  62. new MXElement( 'normalize', normalize, [ 'in' ] ),
  63. new MXElement( 'magnitude', length, [ 'in1', 'in2' ] ),
  64. new MXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ),
  65. new MXElement( 'crossproduct', cross, [ 'in' ] ),
  66. new MXElement( 'distance', distance, [ 'in1', 'in2' ] ),
  67. new MXElement( 'invert', mx_invert, [ 'in', 'amount' ] ),
  68. //new MtlXElement( 'transformpoint', ... ),
  69. //new MtlXElement( 'transformvector', ... ),
  70. //new MtlXElement( 'transformnormal', ... ),
  71. new MXElement( 'transformmatrix', mul, [ 'in1', 'in2' ] ),
  72. new MXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ),
  73. new MXElement( 'transpose', transpose, [ 'in' ] ),
  74. new MXElement( 'determinant', determinant, [ 'in' ] ),
  75. new MXElement( 'invertmatrix', inverse, [ 'in' ] ),
  76. new MXElement( 'creatematrix', mat3, [ 'in1', 'in2', 'in3' ] ),
  77. //new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ),
  78. //new MtlXElement( 'rotate3d', ... ),
  79. //new MtlXElement( 'arrayappend', ... ),
  80. //new MtlXElement( 'dot', ... ),
  81. new MXElement( 'length', length, [ 'in' ] ),
  82. new MXElement( 'crossproduct', cross, [ 'in1', 'in2' ] ),
  83. new MXElement( 'floor', floor, [ 'in' ] ),
  84. new MXElement( 'ceil', ceil, [ 'in' ] ),
  85. // << Adjustment >>
  86. new MXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ),
  87. new MXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ),
  88. //new MtlXElement( 'curveadjust', ... ),
  89. //new MtlXElement( 'curvelookup', ... ),
  90. new MXElement( 'luminance', luminance, [ 'in', 'lumacoeffs' ] ),
  91. new MXElement( 'rgbtohsv', mx_rgbtohsv, [ 'in' ] ),
  92. new MXElement( 'hsvtorgb', mx_hsvtorgb, [ 'in' ] ),
  93. // << Mix >>
  94. new MXElement( 'mix', mix, [ 'bg', 'fg', 'mix' ] ),
  95. // << Channel >>
  96. new MXElement( 'combine2', vec2, [ 'in1', 'in2' ] ),
  97. new MXElement( 'combine3', vec3, [ 'in1', 'in2', 'in3' ] ),
  98. new MXElement( 'combine4', vec4, [ 'in1', 'in2', 'in3', 'in4' ] ),
  99. // << Procedural >>
  100. new MXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ),
  101. new MXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ),
  102. new MXElement( 'ramp4', mx_ramp4, [ 'valuetl', 'valuetr', 'valuebl', 'valuebr', 'texcoord' ] ),
  103. new MXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ),
  104. new MXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ),
  105. new MXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ),
  106. new MXElement( 'noise3d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ),
  107. new MXElement( 'fractal3d', mx_fractal_noise_float, [ 'position', 'octaves', 'lacunarity', 'diminish', 'amplitude' ] ),
  108. new MXElement( 'cellnoise2d', mx_cell_noise_float, [ 'texcoord' ] ),
  109. new MXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ),
  110. new MXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ),
  111. new MXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ),
  112. new MXElement( 'unifiednoise2d', mx_unifiednoise2d, [ 'type', 'texcoord', 'freq', 'offset', 'jitter', 'outmin', 'outmax', 'clampoutput', 'octaves', 'lacunarity', 'diminish' ] ),
  113. new MXElement( 'unifiednoise3d', mx_unifiednoise3d, [ 'type', 'texcoord', 'freq', 'offset', 'jitter', 'outmin', 'outmax', 'clampoutput', 'octaves', 'lacunarity', 'diminish' ] ),
  114. // << Supplemental >>
  115. //new MtlXElement( 'tiledimage', ... ),
  116. //new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ),
  117. //new MtlXElement( 'ramp4', ... ),
  118. new MXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset', 'operationorder' ] ),
  119. new MXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ),
  120. new MXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ),
  121. //new MtlXElement( 'hsvadjust', ... ),
  122. new MXElement( 'saturate', saturation, [ 'in', 'amount' ] ),
  123. new MXElement( 'extract', element, [ 'in', 'index' ] ),
  124. new MXElement( 'separate2', mx_separate, [ 'in' ] ),
  125. new MXElement( 'separate3', mx_separate, [ 'in' ] ),
  126. new MXElement( 'separate4', mx_separate, [ 'in' ] ),
  127. new MXElement( 'reflect', reflect, [ 'in', 'normal' ] ),
  128. new MXElement( 'refract', refract, [ 'in', 'normal', 'ior' ] ),
  129. new MXElement( 'time', mx_timer ),
  130. new MXElement( 'frame', mx_frame ),
  131. new MXElement( 'ifgreater', mx_ifgreater, [ 'value1', 'value2', 'in1', 'in2' ] ),
  132. new MXElement( 'ifgreatereq', mx_ifgreatereq, [ 'value1', 'value2', 'in1', 'in2' ] ),
  133. new MXElement( 'ifequal', mx_ifequal, [ 'value1', 'value2', 'in1', 'in2' ] ),
  134. // Placeholder implementations for unsupported nodes
  135. new MXElement( 'rotate2d', mx_rotate2d, [ 'in', 'amount' ] ),
  136. new MXElement( 'rotate3d', mx_rotate3d, [ 'in', 'amount', 'axis' ] ),
  137. new MXElement( 'heighttonormal', mx_heighttonormal, [ 'in', 'scale', 'texcoord' ] ),
  138. ];
  139. const MtlXLibrary = {};
  140. MXElements.forEach( element => MtlXLibrary[ element.name ] = element );
  141. /**
  142. * A loader for the MaterialX format.
  143. *
  144. * The node materials loaded with this loader can only be used with {@link WebGPURenderer}.
  145. *
  146. * ```js
  147. * const loader = new MaterialXLoader().setPath( SAMPLE_PATH );
  148. * const materials = await loader.loadAsync( 'standard_surface_brass_tiled.mtlx' );
  149. * ```
  150. *
  151. * @augments Loader
  152. * @three_import import { MaterialXLoader } from 'three/addons/loaders/MaterialXLoader.js';
  153. */
  154. class MaterialXLoader extends Loader {
  155. /**
  156. * Constructs a new MaterialX loader.
  157. *
  158. * @param {LoadingManager} [manager] - The loading manager.
  159. */
  160. constructor( manager ) {
  161. super( manager );
  162. }
  163. /**
  164. * Starts loading from the given URL and passes the loaded MaterialX asset
  165. * to the `onLoad()` callback.
  166. *
  167. * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
  168. * @param {function(Object<string,NodeMaterial>)} onLoad - Executed when the loading process has been finished.
  169. * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
  170. * @param {onErrorCallback} onError - Executed when errors occur.
  171. * @return {MaterialXLoader} A reference to this loader.
  172. */
  173. load( url, onLoad, onProgress, onError ) {
  174. const _onError = function ( e ) {
  175. if ( onError ) {
  176. onError( e );
  177. } else {
  178. console.error( e );
  179. }
  180. };
  181. new FileLoader( this.manager )
  182. .setPath( this.path )
  183. .load( url, async ( text ) => {
  184. try {
  185. onLoad( this.parse( text ) );
  186. } catch ( e ) {
  187. _onError( e );
  188. }
  189. }, onProgress, _onError );
  190. return this;
  191. }
  192. /**
  193. * Parses the given MaterialX data and returns the resulting materials.
  194. *
  195. * Supported standard_surface inputs:
  196. * - base, base_color: Base color/albedo
  197. * - opacity: Alpha/transparency
  198. * - specular_roughness: Surface roughness
  199. * - metalness: Metallic property
  200. * - specular: Specular reflection intensity
  201. * - specular_color: Specular reflection color
  202. * - ior: Index of refraction
  203. * - specular_anisotropy, specular_rotation: Anisotropic reflection
  204. * - transmission, transmission_color: Transmission properties
  205. * - thin_film_thickness, thin_film_ior: Thin film interference
  206. * - sheen, sheen_color, sheen_roughness: Sheen properties
  207. * - normal: Normal map
  208. * - coat, coat_roughness, coat_color: Clearcoat properties
  209. * - emission, emissionColor: Emission properties
  210. *
  211. * @param {string} text - The raw MaterialX data as a string.
  212. * @return {Object<string,NodeMaterial>} A dictionary holding the parse node materials.
  213. */
  214. parse( text ) {
  215. return new MaterialX( this.manager, this.path ).parse( text );
  216. }
  217. }
  218. class MaterialXNode {
  219. constructor( materialX, nodeXML, nodePath = '' ) {
  220. if ( ! materialX || typeof materialX !== 'object' ) {
  221. console.warn( 'MaterialXNode: materialX argument is not an object!', { materialX, nodeXML, nodePath } );
  222. }
  223. this.materialX = materialX;
  224. this.nodeXML = nodeXML;
  225. this.nodePath = nodePath ? nodePath + '/' + this.name : this.name;
  226. this.parent = null;
  227. this.node = null;
  228. this.children = [];
  229. }
  230. get element() {
  231. return this.nodeXML.nodeName;
  232. }
  233. get nodeGraph() {
  234. return this.getAttribute( 'nodegraph' );
  235. }
  236. get nodeName() {
  237. return this.getAttribute( 'nodename' );
  238. }
  239. get interfaceName() {
  240. return this.getAttribute( 'interfacename' );
  241. }
  242. get output() {
  243. return this.getAttribute( 'output' );
  244. }
  245. get name() {
  246. return this.getAttribute( 'name' );
  247. }
  248. get type() {
  249. return this.getAttribute( 'type' );
  250. }
  251. get value() {
  252. return this.getAttribute( 'value' );
  253. }
  254. getNodeGraph() {
  255. let nodeX = this;
  256. while ( nodeX !== null ) {
  257. if ( nodeX.element === 'nodegraph' ) {
  258. break;
  259. }
  260. nodeX = nodeX.parent;
  261. }
  262. return nodeX;
  263. }
  264. getRoot() {
  265. let nodeX = this;
  266. while ( nodeX.parent !== null ) {
  267. nodeX = nodeX.parent;
  268. }
  269. return nodeX;
  270. }
  271. get referencePath() {
  272. let referencePath = null;
  273. if ( this.nodeGraph !== null && this.output !== null ) {
  274. referencePath = this.nodeGraph + '/' + this.output;
  275. } else if ( this.nodeName !== null || this.interfaceName !== null ) {
  276. referencePath = this.getNodeGraph().nodePath + '/' + ( this.nodeName || this.interfaceName );
  277. }
  278. return referencePath;
  279. }
  280. get hasReference() {
  281. return this.referencePath !== null;
  282. }
  283. get isConst() {
  284. return this.element === 'input' && this.value !== null && this.type !== 'filename';
  285. }
  286. getColorSpaceNode() {
  287. const csSource = this.getAttribute( 'colorspace' );
  288. const csTarget = this.getRoot().getAttribute( 'colorspace' );
  289. const nodeName = `mx_${ csSource }_to_${ csTarget }`;
  290. return colorSpaceLib[ nodeName ];
  291. }
  292. getTexture() {
  293. const filePrefix = this.getRecursiveAttribute( 'fileprefix' ) || '';
  294. let loader = this.materialX.textureLoader;
  295. const uri = filePrefix + this.value;
  296. if ( uri ) {
  297. const handler = this.materialX.manager.getHandler( uri );
  298. if ( handler !== null ) loader = handler;
  299. }
  300. const texture = loader.load( uri );
  301. texture.wrapS = texture.wrapT = RepeatWrapping;
  302. texture.flipY = false;
  303. return texture;
  304. }
  305. getClassFromType( type ) {
  306. let nodeClass = null;
  307. if ( type === 'integer' ) nodeClass = int;
  308. else if ( type === 'float' ) nodeClass = float;
  309. else if ( type === 'vector2' ) nodeClass = vec2;
  310. else if ( type === 'vector3' ) nodeClass = vec3;
  311. else if ( type === 'vector4' || type === 'color4' ) nodeClass = vec4;
  312. else if ( type === 'color3' ) nodeClass = color;
  313. else if ( type === 'boolean' ) nodeClass = bool;
  314. return nodeClass;
  315. }
  316. getNode( out = null ) {
  317. let node = this.node;
  318. if ( node !== null && out === null ) {
  319. return node;
  320. }
  321. // Handle <input name="texcoord" type="vector2" ... />
  322. if (
  323. this.element === 'input' &&
  324. this.name === 'texcoord' &&
  325. this.type === 'vector2'
  326. ) {
  327. // Try to get index from defaultgeomprop (e.g., "UV0" => 0)
  328. let index = 0;
  329. const defaultGeomProp = this.getAttribute( 'defaultgeomprop' );
  330. if ( defaultGeomProp && /^UV(\d+)$/.test( defaultGeomProp ) ) {
  331. index = parseInt( defaultGeomProp.match( /^UV(\d+)$/ )[ 1 ], 10 );
  332. }
  333. node = uv( index );
  334. }
  335. // Multi-output support for separate/separate3
  336. if (
  337. ( this.element === 'separate3' || this.element === 'separate2' || this.element === 'separate4' ) &&
  338. out && typeof out === 'string' && out.startsWith( 'out' )
  339. ) {
  340. const inNode = this.getNodeByName( 'in' );
  341. return mx_separate( inNode, out );
  342. }
  343. //
  344. const type = this.type;
  345. if ( this.isConst ) {
  346. const nodeClass = this.getClassFromType( type );
  347. node = nodeClass( ...this.getVector() );
  348. } else if ( this.hasReference ) {
  349. if ( this.element === 'output' && this.output && out === null ) {
  350. out = this.output;
  351. }
  352. node = this.materialX.getMaterialXNode( this.referencePath ).getNode( out );
  353. } else {
  354. const element = this.element;
  355. if ( element === 'convert' ) {
  356. const nodeClass = this.getClassFromType( type );
  357. node = nodeClass( this.getNodeByName( 'in' ) );
  358. } else if ( element === 'constant' ) {
  359. node = this.getNodeByName( 'value' );
  360. } else if ( element === 'position' ) {
  361. const space = this.getAttribute( 'space' );
  362. node = space === 'world' ? positionWorld : positionLocal;
  363. } else if ( element === 'normal' ) {
  364. const space = this.getAttribute( 'space' );
  365. node = space === 'world' ? normalWorld : normalLocal;
  366. } else if ( element === 'tangent' ) {
  367. const space = this.getAttribute( 'space' );
  368. node = space === 'world' ? tangentWorld : tangentLocal;
  369. } else if ( element === 'texcoord' ) {
  370. const indexNode = this.getChildByName( 'index' );
  371. const index = indexNode ? parseInt( indexNode.value ) : 0;
  372. node = uv( index );
  373. } else if ( element === 'geomcolor' ) {
  374. const indexNode = this.getChildByName( 'index' );
  375. const index = indexNode ? parseInt( indexNode.value ) : 0;
  376. node = vertexColor( index );
  377. } else if ( element === 'tiledimage' ) {
  378. const file = this.getChildByName( 'file' );
  379. const textureFile = file.getTexture();
  380. const uvTiling = mx_transform_uv( ...this.getNodesByNames( [ 'uvtiling', 'uvoffset' ] ) );
  381. node = texture( textureFile, uvTiling );
  382. const colorSpaceNode = file.getColorSpaceNode();
  383. if ( colorSpaceNode ) {
  384. node = colorSpaceNode( node );
  385. }
  386. } else if ( element === 'image' ) {
  387. const file = this.getChildByName( 'file' );
  388. const uvNode = this.getNodeByName( 'texcoord' );
  389. const textureFile = file.getTexture();
  390. node = texture( textureFile, uvNode );
  391. const colorSpaceNode = file.getColorSpaceNode();
  392. if ( colorSpaceNode ) {
  393. node = colorSpaceNode( node );
  394. }
  395. } else if ( MtlXLibrary[ element ] !== undefined ) {
  396. const nodeElement = MtlXLibrary[ element ];
  397. if ( ! nodeElement ) {
  398. throw new Error( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
  399. }
  400. if ( ! nodeElement.nodeFunc ) {
  401. throw new Error( `THREE.MaterialXLoader: Unexpected node 2 ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
  402. }
  403. if ( out !== null ) {
  404. node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ), out );
  405. } else {
  406. node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ) );
  407. }
  408. }
  409. }
  410. //
  411. if ( node === null ) {
  412. console.warn( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
  413. node = float( 0 );
  414. }
  415. //
  416. const nodeToTypeClass = this.getClassFromType( type );
  417. if ( nodeToTypeClass !== null ) {
  418. node = nodeToTypeClass( node );
  419. } else {
  420. console.warn( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
  421. node = float( 0 );
  422. }
  423. node.name = this.name;
  424. this.node = node;
  425. return node;
  426. }
  427. getChildByName( name ) {
  428. for ( const input of this.children ) {
  429. if ( input.name === name ) {
  430. return input;
  431. }
  432. }
  433. }
  434. getNodes() {
  435. const nodes = {};
  436. for ( const input of this.children ) {
  437. const node = input.getNode();
  438. nodes[ node.name ] = node;
  439. }
  440. return nodes;
  441. }
  442. getNodeByName( name ) {
  443. const child = this.getChildByName( name );
  444. return child ? child.getNode( child.output ) : undefined;
  445. }
  446. getNodesByNames( ...names ) {
  447. const nodes = [];
  448. for ( const name of names ) {
  449. const node = this.getNodeByName( name );
  450. if ( node ) nodes.push( node );
  451. }
  452. return nodes;
  453. }
  454. getValue() {
  455. return this.value.trim();
  456. }
  457. getVector() {
  458. const vector = [];
  459. for ( const val of this.getValue().split( /[,|\s]/ ) ) {
  460. if ( val !== '' ) {
  461. vector.push( Number( val.trim() ) );
  462. }
  463. }
  464. return vector;
  465. }
  466. getAttribute( name ) {
  467. return this.nodeXML.getAttribute( name );
  468. }
  469. getRecursiveAttribute( name ) {
  470. let attribute = this.nodeXML.getAttribute( name );
  471. if ( attribute === null && this.parent !== null ) {
  472. attribute = this.parent.getRecursiveAttribute( name );
  473. }
  474. return attribute;
  475. }
  476. setStandardSurfaceToGltfPBR( material ) {
  477. const inputs = this.getNodes();
  478. //
  479. let colorNode = null;
  480. if ( inputs.base && inputs.base_color ) colorNode = mul( inputs.base, inputs.base_color );
  481. else if ( inputs.base ) colorNode = inputs.base;
  482. else if ( inputs.base_color ) colorNode = inputs.base_color;
  483. //
  484. let opacityNode = null;
  485. if ( inputs.opacity ) opacityNode = inputs.opacity;
  486. //
  487. let roughnessNode = null;
  488. if ( inputs.specular_roughness ) roughnessNode = inputs.specular_roughness;
  489. //
  490. let metalnessNode = null;
  491. if ( inputs.metalness ) metalnessNode = inputs.metalness;
  492. //
  493. let specularIntensityNode = null;
  494. if ( inputs.specular ) specularIntensityNode = inputs.specular;
  495. //
  496. let specularColorNode = null;
  497. if ( inputs.specular_color ) specularColorNode = inputs.specular_color;
  498. //
  499. let iorNode = null;
  500. if ( inputs.ior ) iorNode = inputs.ior;
  501. //
  502. let anisotropyNode = null;
  503. let anisotropyRotationNode = null;
  504. if ( inputs.specular_anisotropy ) anisotropyNode = inputs.specular_anisotropy;
  505. if ( inputs.specular_rotation ) anisotropyRotationNode = inputs.specular_rotation;
  506. //
  507. let transmissionNode = null;
  508. let transmissionColorNode = null;
  509. if ( inputs.transmission ) transmissionNode = inputs.transmission;
  510. if ( inputs.transmission_color ) transmissionColorNode = inputs.transmission_color;
  511. //
  512. let thinFilmThicknessNode = null;
  513. let thinFilmIorNode = null;
  514. if ( inputs.thin_film_thickness ) thinFilmThicknessNode = inputs.thin_film_thickness;
  515. if ( inputs.thin_film_ior ) {
  516. // Clamp IOR to valid range for Three.js (1.0 to 2.333)
  517. thinFilmIorNode = clamp( inputs.thin_film_ior, float( 1.0 ), float( 2.333 ) );
  518. }
  519. //
  520. let sheenNode = null;
  521. let sheenColorNode = null;
  522. let sheenRoughnessNode = null;
  523. if ( inputs.sheen ) sheenNode = inputs.sheen;
  524. if ( inputs.sheen_color ) sheenColorNode = inputs.sheen_color;
  525. if ( inputs.sheen_roughness ) sheenRoughnessNode = inputs.sheen_roughness;
  526. //
  527. let clearcoatNode = null;
  528. let clearcoatRoughnessNode = null;
  529. if ( inputs.coat ) clearcoatNode = inputs.coat;
  530. if ( inputs.coat_roughness ) clearcoatRoughnessNode = inputs.coat_roughness;
  531. if ( inputs.coat_color ) {
  532. colorNode = colorNode ? mul( colorNode, inputs.coat_color ) : colorNode;
  533. }
  534. //
  535. let normalNode = null;
  536. if ( inputs.normal ) normalNode = inputs.normal;
  537. //
  538. let emissiveNode = null;
  539. if ( inputs.emission ) emissiveNode = inputs.emission;
  540. if ( inputs.emissionColor ) {
  541. emissiveNode = emissiveNode ? mul( emissiveNode, inputs.emissionColor ) : emissiveNode;
  542. }
  543. //
  544. material.colorNode = colorNode || color( 0.8, 0.8, 0.8 );
  545. material.opacityNode = opacityNode || float( 1.0 );
  546. material.roughnessNode = roughnessNode || float( 0.2 );
  547. material.metalnessNode = metalnessNode || float( 0 );
  548. material.specularIntensityNode = specularIntensityNode || float( 0.5 );
  549. material.specularColorNode = specularColorNode || color( 1.0, 1.0, 1.0 );
  550. material.iorNode = iorNode || float( 1.5 );
  551. material.anisotropyNode = anisotropyNode || float( 0 );
  552. material.anisotropyRotationNode = anisotropyRotationNode || float( 0 );
  553. material.transmissionNode = transmissionNode || float( 0 );
  554. material.transmissionColorNode = transmissionColorNode || color( 1.0, 1.0, 1.0 );
  555. material.thinFilmThicknessNode = thinFilmThicknessNode || float( 0 );
  556. material.thinFilmIorNode = thinFilmIorNode || float( 1.5 );
  557. material.sheenNode = sheenNode || float( 0 );
  558. material.sheenColorNode = sheenColorNode || color( 1.0, 1.0, 1.0 );
  559. material.sheenRoughnessNode = sheenRoughnessNode || float( 0.5 );
  560. material.clearcoatNode = clearcoatNode || float( 0 );
  561. material.clearcoatRoughnessNode = clearcoatRoughnessNode || float( 0 );
  562. if ( normalNode ) material.normalNode = normalNode;
  563. if ( emissiveNode ) material.emissiveNode = emissiveNode;
  564. // Auto-enable iridescence when thin film parameters are present
  565. if ( thinFilmThicknessNode && thinFilmThicknessNode.value !== undefined && thinFilmThicknessNode.value > 0 ) {
  566. material.iridescence = 1.0;
  567. }
  568. if ( opacityNode !== null ) {
  569. material.transparent = true;
  570. }
  571. if ( transmissionNode !== null ) {
  572. material.side = DoubleSide;
  573. material.transparent = true;
  574. }
  575. }
  576. /*setGltfPBR( material ) {
  577. const inputs = this.getNodes();
  578. console.log( inputs );
  579. }*/
  580. setMaterial( material ) {
  581. const element = this.element;
  582. if ( element === 'gltf_pbr' ) {
  583. //this.setGltfPBR( material );
  584. } else if ( element === 'standard_surface' ) {
  585. this.setStandardSurfaceToGltfPBR( material );
  586. }
  587. }
  588. toBasicMaterial() {
  589. const material = new MeshBasicNodeMaterial();
  590. material.name = this.name;
  591. for ( const nodeX of this.children.toReversed() ) {
  592. if ( nodeX.name === 'out' ) {
  593. material.colorNode = nodeX.getNode();
  594. break;
  595. }
  596. }
  597. return material;
  598. }
  599. toPhysicalMaterial() {
  600. const material = new MeshPhysicalNodeMaterial();
  601. material.name = this.name;
  602. for ( const nodeX of this.children ) {
  603. const shaderProperties = this.materialX.getMaterialXNode( nodeX.nodeName );
  604. shaderProperties.setMaterial( material );
  605. }
  606. return material;
  607. }
  608. toMaterials() {
  609. const materials = {};
  610. let isUnlit = true;
  611. for ( const nodeX of this.children ) {
  612. if ( nodeX.element === 'surfacematerial' ) {
  613. const material = nodeX.toPhysicalMaterial();
  614. materials[ material.name ] = material;
  615. isUnlit = false;
  616. }
  617. }
  618. if ( isUnlit ) {
  619. for ( const nodeX of this.children ) {
  620. if ( nodeX.element === 'nodegraph' ) {
  621. const material = nodeX.toBasicMaterial();
  622. materials[ material.name ] = material;
  623. }
  624. }
  625. }
  626. return materials;
  627. }
  628. add( materialXNode ) {
  629. materialXNode.parent = this;
  630. this.children.push( materialXNode );
  631. }
  632. }
  633. class MaterialX {
  634. constructor( manager, path ) {
  635. this.manager = manager;
  636. this.path = path;
  637. this.resourcePath = '';
  638. this.nodesXLib = new Map();
  639. //this.nodesXRefLib = new WeakMap();
  640. this.textureLoader = new TextureLoader( manager );
  641. }
  642. addMaterialXNode( materialXNode ) {
  643. this.nodesXLib.set( materialXNode.nodePath, materialXNode );
  644. }
  645. /*getMaterialXNodeFromXML( xmlNode ) {
  646. return this.nodesXRefLib.get( xmlNode );
  647. }*/
  648. getMaterialXNode( ...names ) {
  649. return this.nodesXLib.get( names.join( '/' ) );
  650. }
  651. parseNode( nodeXML, nodePath = '' ) {
  652. const materialXNode = new MaterialXNode( this, nodeXML, nodePath );
  653. if ( materialXNode.nodePath ) this.addMaterialXNode( materialXNode );
  654. for ( const childNodeXML of nodeXML.children ) {
  655. const childMXNode = this.parseNode( childNodeXML, materialXNode.nodePath );
  656. materialXNode.add( childMXNode );
  657. }
  658. return materialXNode;
  659. }
  660. parse( text ) {
  661. const rootXML = new DOMParser().parseFromString( text, 'application/xml' ).documentElement;
  662. this.textureLoader.setPath( this.path );
  663. //
  664. const materials = this.parseNode( rootXML ).toMaterials();
  665. return { materials };
  666. }
  667. }
  668. export { MaterialXLoader };
粤ICP备19079148号