MaterialXLoader.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. import {
  2. FileLoader, Loader, ImageBitmapLoader, Texture, 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. const uri = filePrefix + this.value;
  295. if ( this.materialX.textureCache.has( uri ) ) {
  296. return this.materialX.textureCache.get( uri );
  297. }
  298. let loader = this.materialX.textureLoader;
  299. if ( uri ) {
  300. const handler = this.materialX.manager.getHandler( uri );
  301. if ( handler !== null ) loader = handler;
  302. }
  303. const texture = new Texture();
  304. texture.wrapS = texture.wrapT = RepeatWrapping;
  305. this.materialX.textureCache.set( uri, texture );
  306. loader.load( uri, function ( imageBitmap ) {
  307. texture.image = imageBitmap;
  308. texture.needsUpdate = true;
  309. } );
  310. return texture;
  311. }
  312. getClassFromType( type ) {
  313. let nodeClass = null;
  314. if ( type === 'integer' ) nodeClass = int;
  315. else if ( type === 'float' ) nodeClass = float;
  316. else if ( type === 'vector2' ) nodeClass = vec2;
  317. else if ( type === 'vector3' ) nodeClass = vec3;
  318. else if ( type === 'vector4' || type === 'color4' ) nodeClass = vec4;
  319. else if ( type === 'color3' ) nodeClass = color;
  320. else if ( type === 'boolean' ) nodeClass = bool;
  321. return nodeClass;
  322. }
  323. getNode( out = null ) {
  324. let node = this.node;
  325. if ( node !== null && out === null ) {
  326. return node;
  327. }
  328. // Handle <input name="texcoord" type="vector2" ... />
  329. if (
  330. this.element === 'input' &&
  331. this.name === 'texcoord' &&
  332. this.type === 'vector2'
  333. ) {
  334. // Try to get index from defaultgeomprop (e.g., "UV0" => 0)
  335. let index = 0;
  336. const defaultGeomProp = this.getAttribute( 'defaultgeomprop' );
  337. if ( defaultGeomProp && /^UV(\d+)$/.test( defaultGeomProp ) ) {
  338. index = parseInt( defaultGeomProp.match( /^UV(\d+)$/ )[ 1 ], 10 );
  339. }
  340. node = uv( index );
  341. }
  342. // Multi-output support for separate/separate3
  343. if (
  344. ( this.element === 'separate3' || this.element === 'separate2' || this.element === 'separate4' ) &&
  345. out && typeof out === 'string' && out.startsWith( 'out' )
  346. ) {
  347. const inNode = this.getNodeByName( 'in' );
  348. return mx_separate( inNode, out );
  349. }
  350. //
  351. const type = this.type;
  352. if ( this.isConst ) {
  353. const nodeClass = this.getClassFromType( type );
  354. node = nodeClass( ...this.getVector() );
  355. } else if ( this.hasReference ) {
  356. if ( this.element === 'output' && this.output && out === null ) {
  357. out = this.output;
  358. }
  359. node = this.materialX.getMaterialXNode( this.referencePath ).getNode( out );
  360. } else {
  361. const element = this.element;
  362. if ( element === 'convert' ) {
  363. const nodeClass = this.getClassFromType( type );
  364. node = nodeClass( this.getNodeByName( 'in' ) );
  365. } else if ( element === 'constant' ) {
  366. node = this.getNodeByName( 'value' );
  367. } else if ( element === 'position' ) {
  368. const space = this.getAttribute( 'space' );
  369. node = space === 'world' ? positionWorld : positionLocal;
  370. } else if ( element === 'normal' ) {
  371. const space = this.getAttribute( 'space' );
  372. node = space === 'world' ? normalWorld : normalLocal;
  373. } else if ( element === 'tangent' ) {
  374. const space = this.getAttribute( 'space' );
  375. node = space === 'world' ? tangentWorld : tangentLocal;
  376. } else if ( element === 'texcoord' ) {
  377. const indexNode = this.getChildByName( 'index' );
  378. const index = indexNode ? parseInt( indexNode.value ) : 0;
  379. node = uv( index );
  380. } else if ( element === 'geomcolor' ) {
  381. const indexNode = this.getChildByName( 'index' );
  382. const index = indexNode ? parseInt( indexNode.value ) : 0;
  383. node = vertexColor( index );
  384. } else if ( element === 'tiledimage' ) {
  385. const file = this.getChildByName( 'file' );
  386. const textureFile = file.getTexture();
  387. const uvTiling = mx_transform_uv( ...this.getNodesByNames( [ 'uvtiling', 'uvoffset' ] ) );
  388. node = texture( textureFile, uvTiling );
  389. const colorSpaceNode = file.getColorSpaceNode();
  390. if ( colorSpaceNode ) {
  391. node = colorSpaceNode( node );
  392. }
  393. } else if ( element === 'image' ) {
  394. const file = this.getChildByName( 'file' );
  395. const uvNode = this.getNodeByName( 'texcoord' );
  396. const textureFile = file.getTexture();
  397. node = texture( textureFile, uvNode );
  398. const colorSpaceNode = file.getColorSpaceNode();
  399. if ( colorSpaceNode ) {
  400. node = colorSpaceNode( node );
  401. }
  402. } else if ( MtlXLibrary[ element ] !== undefined ) {
  403. const nodeElement = MtlXLibrary[ element ];
  404. if ( ! nodeElement ) {
  405. throw new Error( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
  406. }
  407. if ( ! nodeElement.nodeFunc ) {
  408. throw new Error( `THREE.MaterialXLoader: Unexpected node 2 ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
  409. }
  410. if ( out !== null ) {
  411. node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ), out );
  412. } else {
  413. node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ) );
  414. }
  415. }
  416. }
  417. //
  418. if ( node === null ) {
  419. console.warn( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
  420. node = float( 0 );
  421. }
  422. //
  423. const nodeToTypeClass = this.getClassFromType( type );
  424. if ( nodeToTypeClass !== null ) {
  425. node = nodeToTypeClass( node );
  426. } else {
  427. console.warn( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
  428. node = float( 0 );
  429. }
  430. node.name = this.name;
  431. this.node = node;
  432. return node;
  433. }
  434. getChildByName( name ) {
  435. for ( const input of this.children ) {
  436. if ( input.name === name ) {
  437. return input;
  438. }
  439. }
  440. }
  441. getNodes() {
  442. const nodes = {};
  443. for ( const input of this.children ) {
  444. const node = input.getNode();
  445. nodes[ node.name ] = node;
  446. }
  447. return nodes;
  448. }
  449. getNodeByName( name ) {
  450. const child = this.getChildByName( name );
  451. return child ? child.getNode( child.output ) : undefined;
  452. }
  453. getNodesByNames( ...names ) {
  454. const nodes = [];
  455. for ( const name of names ) {
  456. const node = this.getNodeByName( name );
  457. if ( node ) nodes.push( node );
  458. }
  459. return nodes;
  460. }
  461. getValue() {
  462. return this.value.trim();
  463. }
  464. getVector() {
  465. const vector = [];
  466. for ( const val of this.getValue().split( /[,|\s]/ ) ) {
  467. if ( val !== '' ) {
  468. vector.push( Number( val.trim() ) );
  469. }
  470. }
  471. return vector;
  472. }
  473. getAttribute( name ) {
  474. return this.nodeXML.getAttribute( name );
  475. }
  476. getRecursiveAttribute( name ) {
  477. let attribute = this.nodeXML.getAttribute( name );
  478. if ( attribute === null && this.parent !== null ) {
  479. attribute = this.parent.getRecursiveAttribute( name );
  480. }
  481. return attribute;
  482. }
  483. setStandardSurfaceToGltfPBR( material ) {
  484. const inputs = this.getNodes();
  485. //
  486. let colorNode = null;
  487. if ( inputs.base && inputs.base_color ) colorNode = mul( inputs.base, inputs.base_color );
  488. else if ( inputs.base ) colorNode = inputs.base;
  489. else if ( inputs.base_color ) colorNode = inputs.base_color;
  490. //
  491. let opacityNode = null;
  492. if ( inputs.opacity ) opacityNode = inputs.opacity;
  493. //
  494. let roughnessNode = null;
  495. if ( inputs.specular_roughness ) roughnessNode = inputs.specular_roughness;
  496. //
  497. let metalnessNode = null;
  498. if ( inputs.metalness ) metalnessNode = inputs.metalness;
  499. //
  500. let specularIntensityNode = null;
  501. if ( inputs.specular ) specularIntensityNode = inputs.specular;
  502. //
  503. let specularColorNode = null;
  504. if ( inputs.specular_color ) specularColorNode = inputs.specular_color;
  505. //
  506. let iorNode = null;
  507. if ( inputs.ior ) iorNode = inputs.ior;
  508. //
  509. let anisotropyNode = null;
  510. let anisotropyRotationNode = null;
  511. if ( inputs.specular_anisotropy ) anisotropyNode = inputs.specular_anisotropy;
  512. if ( inputs.specular_rotation ) anisotropyRotationNode = inputs.specular_rotation;
  513. //
  514. let transmissionNode = null;
  515. let transmissionColorNode = null;
  516. if ( inputs.transmission ) transmissionNode = inputs.transmission;
  517. if ( inputs.transmission_color ) transmissionColorNode = inputs.transmission_color;
  518. //
  519. let thinFilmThicknessNode = null;
  520. let thinFilmIorNode = null;
  521. if ( inputs.thin_film_thickness ) thinFilmThicknessNode = inputs.thin_film_thickness;
  522. if ( inputs.thin_film_ior ) {
  523. // Clamp IOR to valid range for Three.js (1.0 to 2.333)
  524. thinFilmIorNode = clamp( inputs.thin_film_ior, float( 1.0 ), float( 2.333 ) );
  525. }
  526. //
  527. let sheenNode = null;
  528. let sheenColorNode = null;
  529. let sheenRoughnessNode = null;
  530. if ( inputs.sheen ) sheenNode = inputs.sheen;
  531. if ( inputs.sheen_color ) sheenColorNode = inputs.sheen_color;
  532. if ( inputs.sheen_roughness ) sheenRoughnessNode = inputs.sheen_roughness;
  533. //
  534. let clearcoatNode = null;
  535. let clearcoatRoughnessNode = null;
  536. if ( inputs.coat ) clearcoatNode = inputs.coat;
  537. if ( inputs.coat_roughness ) clearcoatRoughnessNode = inputs.coat_roughness;
  538. if ( inputs.coat_color ) {
  539. colorNode = colorNode ? mul( colorNode, inputs.coat_color ) : colorNode;
  540. }
  541. //
  542. let normalNode = null;
  543. if ( inputs.normal ) normalNode = inputs.normal;
  544. //
  545. let emissiveNode = null;
  546. if ( inputs.emission ) emissiveNode = inputs.emission;
  547. if ( inputs.emissionColor ) {
  548. emissiveNode = emissiveNode ? mul( emissiveNode, inputs.emissionColor ) : emissiveNode;
  549. }
  550. //
  551. material.colorNode = colorNode || color( 0.8, 0.8, 0.8 );
  552. material.opacityNode = opacityNode || float( 1.0 );
  553. material.roughnessNode = roughnessNode || float( 0.2 );
  554. material.metalnessNode = metalnessNode || float( 0 );
  555. material.specularIntensityNode = specularIntensityNode || float( 0.5 );
  556. material.specularColorNode = specularColorNode || color( 1.0, 1.0, 1.0 );
  557. material.iorNode = iorNode || float( 1.5 );
  558. material.anisotropyNode = anisotropyNode || float( 0 );
  559. material.anisotropyRotationNode = anisotropyRotationNode || float( 0 );
  560. material.transmissionNode = transmissionNode || float( 0 );
  561. material.transmissionColorNode = transmissionColorNode || color( 1.0, 1.0, 1.0 );
  562. material.thinFilmThicknessNode = thinFilmThicknessNode || float( 0 );
  563. material.thinFilmIorNode = thinFilmIorNode || float( 1.5 );
  564. material.sheenNode = sheenNode || float( 0 );
  565. material.sheenColorNode = sheenColorNode || color( 1.0, 1.0, 1.0 );
  566. material.sheenRoughnessNode = sheenRoughnessNode || float( 0.5 );
  567. material.clearcoatNode = clearcoatNode || float( 0 );
  568. material.clearcoatRoughnessNode = clearcoatRoughnessNode || float( 0 );
  569. if ( normalNode ) material.normalNode = normalNode;
  570. if ( emissiveNode ) material.emissiveNode = emissiveNode;
  571. // Auto-enable iridescence when thin film parameters are present
  572. if ( thinFilmThicknessNode && thinFilmThicknessNode.value !== undefined && thinFilmThicknessNode.value > 0 ) {
  573. material.iridescence = 1.0;
  574. }
  575. if ( opacityNode !== null ) {
  576. material.transparent = true;
  577. }
  578. if ( transmissionNode !== null ) {
  579. material.side = DoubleSide;
  580. material.transparent = true;
  581. }
  582. }
  583. /*setGltfPBR( material ) {
  584. const inputs = this.getNodes();
  585. console.log( inputs );
  586. }*/
  587. setMaterial( material ) {
  588. const element = this.element;
  589. if ( element === 'gltf_pbr' ) {
  590. //this.setGltfPBR( material );
  591. } else if ( element === 'standard_surface' ) {
  592. this.setStandardSurfaceToGltfPBR( material );
  593. }
  594. }
  595. toBasicMaterial() {
  596. const material = new MeshBasicNodeMaterial();
  597. material.name = this.name;
  598. for ( const nodeX of this.children.toReversed() ) {
  599. if ( nodeX.name === 'out' ) {
  600. material.colorNode = nodeX.getNode();
  601. break;
  602. }
  603. }
  604. return material;
  605. }
  606. toPhysicalMaterial() {
  607. const material = new MeshPhysicalNodeMaterial();
  608. material.name = this.name;
  609. for ( const nodeX of this.children ) {
  610. const shaderProperties = this.materialX.getMaterialXNode( nodeX.nodeName );
  611. shaderProperties.setMaterial( material );
  612. }
  613. return material;
  614. }
  615. toMaterials() {
  616. const materials = {};
  617. let isUnlit = true;
  618. for ( const nodeX of this.children ) {
  619. if ( nodeX.element === 'surfacematerial' ) {
  620. const material = nodeX.toPhysicalMaterial();
  621. materials[ material.name ] = material;
  622. isUnlit = false;
  623. }
  624. }
  625. if ( isUnlit ) {
  626. for ( const nodeX of this.children ) {
  627. if ( nodeX.element === 'nodegraph' ) {
  628. const material = nodeX.toBasicMaterial();
  629. materials[ material.name ] = material;
  630. }
  631. }
  632. }
  633. return materials;
  634. }
  635. add( materialXNode ) {
  636. materialXNode.parent = this;
  637. this.children.push( materialXNode );
  638. }
  639. }
  640. class MaterialX {
  641. constructor( manager, path ) {
  642. this.manager = manager;
  643. this.path = path;
  644. this.resourcePath = '';
  645. this.nodesXLib = new Map();
  646. //this.nodesXRefLib = new WeakMap();
  647. this.textureLoader = new ImageBitmapLoader( manager );
  648. this.textureLoader.setOptions( { imageOrientation: 'flipY' } );
  649. this.textureCache = new Map();
  650. }
  651. addMaterialXNode( materialXNode ) {
  652. this.nodesXLib.set( materialXNode.nodePath, materialXNode );
  653. }
  654. /*getMaterialXNodeFromXML( xmlNode ) {
  655. return this.nodesXRefLib.get( xmlNode );
  656. }*/
  657. getMaterialXNode( ...names ) {
  658. return this.nodesXLib.get( names.join( '/' ) );
  659. }
  660. parseNode( nodeXML, nodePath = '' ) {
  661. const materialXNode = new MaterialXNode( this, nodeXML, nodePath );
  662. if ( materialXNode.nodePath ) this.addMaterialXNode( materialXNode );
  663. for ( const childNodeXML of nodeXML.children ) {
  664. const childMXNode = this.parseNode( childNodeXML, materialXNode.nodePath );
  665. materialXNode.add( childMXNode );
  666. }
  667. return materialXNode;
  668. }
  669. parse( text ) {
  670. const rootXML = new DOMParser().parseFromString( text, 'application/xml' ).documentElement;
  671. this.textureLoader.setPath( this.path );
  672. //
  673. const materials = this.parseNode( rootXML ).toMaterials();
  674. return { materials };
  675. }
  676. }
  677. export { MaterialXLoader };
粤ICP备19079148号