Browse Source

Improved MaterialX import support to align with Blender's MaterialX exporter. (#31439)

* add determinant and inverse TSL nodes.

* add new materialx node support: ln, transform matrix, transpose, determinant, invert matrix, creatematrix, length, crossproduct, floor, place2d, reflect, refract, ifgreater, ifgreatereq, ifequal, rotate2d, rotate3d, heighttonormal

* remove unused vars.

* add materialx support for opacity, specular,  specularColor, ior.

* add MaterialX support: thin film, sheen, anisotropy, transmission

* MaterialX improvements: auto transparent, auto iridescence, clamp thin film ior.

* materialx improvements: transmission implies double-sided materials.

* reorg nodes into MaterialXNodes rather half and half in MaterialXLoader.

* adopt sunag's suggestions, remove unnecessary special mx-functions.

* materialx improvement: get time working + demo.

* removed unused variables from MaterialXNodes.

* materialx: add support for ramp4 nodes.

* materialx: add unifiednoise2d, unifiednoise3d

* fix `nodeProxyIntent`

* async load samples

* Update webgpu_loader_materialx.jpg

* Update webgpu_loader_materialx.html

* fix warnings

* updates

* cleanup

---------

Co-authored-by: sunag <sunagbrasil@gmail.com>
Ben Houston 7 months ago
parent
commit
fe5f4b298d

+ 217 - 30
examples/jsm/loaders/MaterialXLoader.js

@@ -1,20 +1,26 @@
-import { FileLoader, Loader, TextureLoader, RepeatWrapping, MeshBasicNodeMaterial, MeshPhysicalNodeMaterial } from 'three/webgpu';
+import {
+	FileLoader, Loader, TextureLoader, RepeatWrapping, MeshBasicNodeMaterial,
+	MeshPhysicalNodeMaterial, DoubleSide,
+} from 'three/webgpu';
 
 import {
 	float, bool, int, vec2, vec3, vec4, color, texture,
 	positionLocal, positionWorld, uv, vertexColor,
 	normalLocal, normalWorld, tangentLocal, tangentWorld,
-	add, sub, mul, div, mod, abs, sign, floor, ceil, round, pow, sin, cos, tan,
-	asin, acos, atan2, sqrt, exp, clamp, min, max, normalize, length, dot, cross, normalMap,
+	mul, abs, sign, floor, ceil, round, sin, cos, tan,
+	asin, acos, sqrt, exp, clamp, min, max, normalize, length, dot, cross, normalMap,
 	remap, smoothstep, luminance, mx_rgbtohsv, mx_hsvtorgb,
-	mix, split,
+	mix, saturation, transpose, determinant, inverse, log, reflect, refract, element,
 	mx_ramplr, mx_ramptb, mx_splitlr, mx_splittb,
 	mx_fractal_noise_float, mx_noise_float, mx_cell_noise_float, mx_worley_noise_float,
 	mx_transform_uv,
 	mx_safepower, mx_contrast,
 	mx_srgb_texture_to_lin_rec709,
-	saturation,
-	timerLocal, frameId
+	mx_add, mx_atan2, mx_divide, mx_modulo, mx_multiply, mx_power, mx_subtract,
+	mx_timer, mx_frame, mat3, mx_ramp4,
+	mx_invert, mx_ifgreater, mx_ifgreatereq, mx_ifequal, distance,
+	mx_separate, mx_place2d, mx_rotate2d, mx_rotate3d, mx_heighttonormal,
+	mx_unifiednoise2d, mx_unifiednoise3d
 } from 'three/tsl';
 
 const colorSpaceLib = {
@@ -35,19 +41,9 @@ class MXElement {
 
 // Ref: https://github.com/mrdoob/three.js/issues/24674
 
-const mx_add = ( in1, in2 = float( 0 ) ) => add( in1, in2 );
-const mx_subtract = ( in1, in2 = float( 0 ) ) => sub( in1, in2 );
-const mx_multiply = ( in1, in2 = float( 1 ) ) => mul( in1, in2 );
-const mx_divide = ( in1, in2 = float( 1 ) ) => div( in1, in2 );
-const mx_modulo = ( in1, in2 = float( 1 ) ) => mod( in1, in2 );
-const mx_power = ( in1, in2 = float( 1 ) ) => pow( in1, in2 );
-const mx_atan2 = ( in1 = float( 0 ), in2 = float( 1 ) ) => atan2( in1, in2 );
-const mx_timer = () => timerLocal();
-const mx_frame = () => frameId;
-const mx_invert = ( in1, amount = float( 1 ) ) => sub( amount, in1 );
+// Enhanced separate node to support multi-output referencing (outx, outy, outz, outw)
 
-const separate = ( in1, channel ) => split( in1, channel.at( - 1 ) );
-const extract = ( in1, index ) => in1.element( index );
+// Type/arity-aware MaterialX node wrappers
 
 const MXElements = [
 
@@ -70,7 +66,7 @@ const MXElements = [
 	new MXElement( 'acos', acos, [ 'in' ] ),
 	new MXElement( 'atan2', mx_atan2, [ 'in1', 'in2' ] ),
 	new MXElement( 'sqrt', sqrt, [ 'in' ] ),
-	//new MtlXElement( 'ln', ... ),
+	new MXElement( 'ln', log, [ 'in' ] ),
 	new MXElement( 'exp', exp, [ 'in' ] ),
 	new MXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ),
 	new MXElement( 'min', min, [ 'in1', 'in2' ] ),
@@ -79,20 +75,27 @@ const MXElements = [
 	new MXElement( 'magnitude', length, [ 'in1', 'in2' ] ),
 	new MXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ),
 	new MXElement( 'crossproduct', cross, [ 'in' ] ),
+	new MXElement( 'distance', distance, [ 'in1', 'in2' ] ),
 	new MXElement( 'invert', mx_invert, [ 'in', 'amount' ] ),
 	//new MtlXElement( 'transformpoint', ... ),
 	//new MtlXElement( 'transformvector', ... ),
 	//new MtlXElement( 'transformnormal', ... ),
-	//new MtlXElement( 'transformmatrix', ... ),
+	new MXElement( 'transformmatrix', mul, [ 'in1', 'in2' ] ),
 	new MXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ),
-	//new MtlXElement( 'transpose', ... ),
-	//new MtlXElement( 'determinant', ... ),
-	//new MtlXElement( 'invertmatrix', ... ),
+	new MXElement( 'transpose', transpose, [ 'in' ] ),
+	new MXElement( 'determinant', determinant, [ 'in' ] ),
+	new MXElement( 'invertmatrix', inverse, [ 'in' ] ),
+	new MXElement( 'creatematrix', mat3, [ 'in1', 'in2', 'in3' ] ),
 	//new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ),
 	//new MtlXElement( 'rotate3d', ... ),
 	//new MtlXElement( 'arrayappend', ... ),
 	//new MtlXElement( 'dot', ... ),
 
+	new MXElement( 'length', length, [ 'in' ] ),
+	new MXElement( 'crossproduct', cross, [ 'in1', 'in2' ] ),
+	new MXElement( 'floor', floor, [ 'in' ] ),
+	new MXElement( 'ceil', ceil, [ 'in' ] ),
+
 	// << Adjustment >>
 	new MXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ),
 	new MXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ),
@@ -113,6 +116,7 @@ const MXElements = [
 	// << Procedural >>
 	new MXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ),
 	new MXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ),
+	new MXElement( 'ramp4', mx_ramp4, [ 'valuetl', 'valuetr', 'valuebl', 'valuebr', 'texcoord' ] ),
 	new MXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ),
 	new MXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ),
 	new MXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ),
@@ -122,23 +126,34 @@ const MXElements = [
 	new MXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ),
 	new MXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ),
 	new MXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ),
-
+	new MXElement( 'unifiednoise2d', mx_unifiednoise2d, [ 'type', 'texcoord', 'freq', 'offset', 'jitter', 'outmin', 'outmax', 'clampoutput', 'octaves', 'lacunarity', 'diminish' ] ),
+	new MXElement( 'unifiednoise3d', mx_unifiednoise3d, [ 'type', 'texcoord', 'freq', 'offset', 'jitter', 'outmin', 'outmax', 'clampoutput', 'octaves', 'lacunarity', 'diminish' ] ),
 	// << Supplemental >>
 	//new MtlXElement( 'tiledimage', ... ),
 	//new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ),
 	//new MtlXElement( 'ramp4', ... ),
-	//new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ),
+	new MXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset', 'operationorder' ] ),
 	new MXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ),
 	new MXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ),
 	//new MtlXElement( 'hsvadjust', ... ),
 	new MXElement( 'saturate', saturation, [ 'in', 'amount' ] ),
-	new MXElement( 'extract', extract, [ 'in', 'index' ] ),
-	new MXElement( 'separate2', separate, [ 'in' ] ),
-	new MXElement( 'separate3', separate, [ 'in' ] ),
-	new MXElement( 'separate4', separate, [ 'in' ] ),
+	new MXElement( 'extract', element, [ 'in', 'index' ] ),
+	new MXElement( 'separate2', mx_separate, [ 'in' ] ),
+	new MXElement( 'separate3', mx_separate, [ 'in' ] ),
+	new MXElement( 'separate4', mx_separate, [ 'in' ] ),
+	new MXElement( 'reflect', reflect, [ 'in', 'normal' ] ),
+	new MXElement( 'refract', refract, [ 'in', 'normal', 'ior' ] ),
 
 	new MXElement( 'time', mx_timer ),
-	new MXElement( 'frame', mx_frame )
+	new MXElement( 'frame', mx_frame ),
+	new MXElement( 'ifgreater', mx_ifgreater, [ 'value1', 'value2', 'in1', 'in2' ] ),
+	new MXElement( 'ifgreatereq', mx_ifgreatereq, [ 'value1', 'value2', 'in1', 'in2' ] ),
+	new MXElement( 'ifequal', mx_ifequal, [ 'value1', 'value2', 'in1', 'in2' ] ),
+
+	// Placeholder implementations for unsupported nodes
+	new MXElement( 'rotate2d', mx_rotate2d, [ 'in', 'amount' ] ),
+	new MXElement( 'rotate3d', mx_rotate3d, [ 'in', 'amount', 'axis' ] ),
+	new MXElement( 'heighttonormal', mx_heighttonormal, [ 'in', 'scale', 'texcoord' ] ),
 
 ];
 
@@ -220,6 +235,22 @@ class MaterialXLoader extends Loader {
 	/**
 	 * Parses the given MaterialX data and returns the resulting materials.
 	 *
+	 * Supported standard_surface inputs:
+	 * - base, base_color: Base color/albedo
+	 * - opacity: Alpha/transparency
+	 * - specular_roughness: Surface roughness
+	 * - metalness: Metallic property
+	 * - specular: Specular reflection intensity
+	 * - specular_color: Specular reflection color
+	 * - ior: Index of refraction
+	 * - specular_anisotropy, specular_rotation: Anisotropic reflection
+	 * - transmission, transmission_color: Transmission properties
+	 * - thin_film_thickness, thin_film_ior: Thin film interference
+	 * - sheen, sheen_color, sheen_roughness: Sheen properties
+	 * - normal: Normal map
+	 * - coat, coat_roughness, coat_color: Clearcoat properties
+	 * - emission, emissionColor: Emission properties
+	 *
 	 * @param {string} text - The raw MaterialX data as a string.
 	 * @return {Object<string,NodeMaterial>} A dictionary holding the parse node materials.
 	 */
@@ -235,6 +266,12 @@ class MaterialXNode {
 
 	constructor( materialX, nodeXML, nodePath = '' ) {
 
+		if ( ! materialX || typeof materialX !== 'object' ) {
+
+			console.warn( 'MaterialXNode: materialX argument is not an object!', { materialX, nodeXML, nodePath } );
+
+		}
+
 		this.materialX = materialX;
 		this.nodeXML = nodeXML;
 		this.nodePath = nodePath ? nodePath + '/' + this.name : this.name;
@@ -418,6 +455,37 @@ class MaterialXNode {
 
 		}
 
+		// Handle <input name="texcoord" type="vector2" ... />
+		if (
+			this.element === 'input' &&
+			this.name === 'texcoord' &&
+			this.type === 'vector2'
+		) {
+
+			// Try to get index from defaultgeomprop (e.g., "UV0" => 0)
+			let index = 0;
+			const defaultGeomProp = this.getAttribute( 'defaultgeomprop' );
+			if ( defaultGeomProp && /^UV(\d+)$/.test( defaultGeomProp ) ) {
+
+				index = parseInt( defaultGeomProp.match( /^UV(\d+)$/ )[ 1 ], 10 );
+
+			}
+
+			node = uv( index );
+
+		}
+
+		// Multi-output support for separate/separate3
+		if (
+			( this.element === 'separate3' || this.element === 'separate2' || this.element === 'separate4' ) &&
+			out && typeof out === 'string' && out.startsWith( 'out' )
+		) {
+
+			const inNode = this.getNodeByName( 'in' );
+			return mx_separate( inNode, out );
+
+		}
+
 		//
 
 		const type = this.type;
@@ -519,6 +587,18 @@ class MaterialXNode {
 
 				const nodeElement = MtlXLibrary[ element ];
 
+				if ( ! nodeElement ) {
+
+					throw new Error( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
+
+				}
+
+				if ( ! nodeElement.nodeFunc ) {
+
+					throw new Error( `THREE.MaterialXLoader: Unexpected node 2 ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
+
+				}
+
 				if ( out !== null ) {
 
 					node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ), out );
@@ -551,6 +631,11 @@ class MaterialXNode {
 
 			node = nodeToTypeClass( node );
 
+		} else {
+
+			console.warn( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
+			node = float( 0 );
+
 		}
 
 		node.name = this.name;
@@ -673,6 +758,12 @@ class MaterialXNode {
 
 		//
 
+		let opacityNode = null;
+
+		if ( inputs.opacity ) opacityNode = inputs.opacity;
+
+		//
+
 		let roughnessNode = null;
 
 		if ( inputs.specular_roughness ) roughnessNode = inputs.specular_roughness;
@@ -685,6 +776,64 @@ class MaterialXNode {
 
 		//
 
+		let specularIntensityNode = null;
+
+		if ( inputs.specular ) specularIntensityNode = inputs.specular;
+
+		//
+
+		let specularColorNode = null;
+
+		if ( inputs.specular_color ) specularColorNode = inputs.specular_color;
+
+		//
+
+		let iorNode = null;
+
+		if ( inputs.ior ) iorNode = inputs.ior;
+
+		//
+
+		let anisotropyNode = null;
+		let anisotropyRotationNode = null;
+
+		if ( inputs.specular_anisotropy ) anisotropyNode = inputs.specular_anisotropy;
+		if ( inputs.specular_rotation ) anisotropyRotationNode = inputs.specular_rotation;
+
+		//
+
+		let transmissionNode = null;
+		let transmissionColorNode = null;
+
+		if ( inputs.transmission ) transmissionNode = inputs.transmission;
+		if ( inputs.transmission_color ) transmissionColorNode = inputs.transmission_color;
+
+		//
+
+		let thinFilmThicknessNode = null;
+		let thinFilmIorNode = null;
+
+		if ( inputs.thin_film_thickness ) thinFilmThicknessNode = inputs.thin_film_thickness;
+
+		if ( inputs.thin_film_ior ) {
+
+			// Clamp IOR to valid range for Three.js (1.0 to 2.333)
+			thinFilmIorNode = clamp( inputs.thin_film_ior, float( 1.0 ), float( 2.333 ) );
+
+		}
+
+		//
+
+		let sheenNode = null;
+		let sheenColorNode = null;
+		let sheenRoughnessNode = null;
+
+		if ( inputs.sheen ) sheenNode = inputs.sheen;
+		if ( inputs.sheen_color ) sheenColorNode = inputs.sheen_color;
+		if ( inputs.sheen_roughness ) sheenRoughnessNode = inputs.sheen_roughness;
+
+		//
+
 		let clearcoatNode = null;
 		let clearcoatRoughnessNode = null;
 
@@ -717,13 +866,51 @@ class MaterialXNode {
 		//
 
 		material.colorNode = colorNode || color( 0.8, 0.8, 0.8 );
+		material.opacityNode = opacityNode || float( 1.0 );
 		material.roughnessNode = roughnessNode || float( 0.2 );
 		material.metalnessNode = metalnessNode || float( 0 );
+		material.specularIntensityNode = specularIntensityNode || float( 0.5 );
+		material.specularColorNode = specularColorNode || color( 1.0, 1.0, 1.0 );
+		material.iorNode = iorNode || float( 1.5 );
+		material.anisotropyNode = anisotropyNode || float( 0 );
+		material.anisotropyRotationNode = anisotropyRotationNode || float( 0 );
+		material.transmissionNode = transmissionNode || float( 0 );
+		material.transmissionColorNode = transmissionColorNode || color( 1.0, 1.0, 1.0 );
+		material.thinFilmThicknessNode = thinFilmThicknessNode || float( 0 );
+		material.thinFilmIorNode = thinFilmIorNode || float( 1.5 );
+		material.sheenNode = sheenNode || float( 0 );
+		material.sheenColorNode = sheenColorNode || color( 1.0, 1.0, 1.0 );
+		material.sheenRoughnessNode = sheenRoughnessNode || float( 0.5 );
 		material.clearcoatNode = clearcoatNode || float( 0 );
 		material.clearcoatRoughnessNode = clearcoatRoughnessNode || float( 0 );
 		if ( normalNode ) material.normalNode = normalNode;
 		if ( emissiveNode ) material.emissiveNode = emissiveNode;
 
+		// Auto-enable iridescence when thin film parameters are present
+		if ( thinFilmThicknessNode && thinFilmThicknessNode.value !== undefined && thinFilmThicknessNode.value > 0 ) {
+
+			material.iridescence = 1.0;
+
+		}
+
+		// Auto-enable transparency when opacity or transmission is non-default
+		const hasNonDefaultOpacity = opacityNode && opacityNode.value !== undefined && opacityNode.value < 1.0;
+		const hasTransmission = transmissionNode && transmissionNode.value !== undefined && transmissionNode.value > 0;
+
+		if ( hasNonDefaultOpacity ) {
+
+			material.transparent = true;
+
+		}
+
+		// Set material properties for transmission
+		if ( hasTransmission ) {
+
+			material.transparent = true;
+			material.side = DoubleSide;
+
+		}
+
 	}
 
 	/*setGltfPBR( material ) {

+ 38 - 0
examples/materialx/color3_vec3_cm_test.mtlx

@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<materialx version="1.39" colorspace="lin_rec709">
+  <surfacematerial name="mat_color3_vec3_cm_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" output="out" nodegraph="normalmap_cm" />
+  </standard_surface>
+  <!-- create nodegraph to test cm conversion on image node before feeding to heighttonormal  -->
+  <nodegraph name="height_to_normal_cm">
+    <image name="b_image" type="color3">
+      <input name="file" type="filename" value="resources/Images/grid.png" colorspace="srgb_texture" />
+    </image>
+    <extract name="extract1" type="float">
+      <input name="in" type="color3" nodename="b_image" />
+      <input name="index" type="integer" value="1" />
+    </extract>
+    <heighttonormal name="impl_heighttonormalmap" type="vector3">
+      <input name="in" type="float" nodename="extract1" />
+      <input name="scale" type="float" value="1.0" />
+    </heighttonormal>
+    <output name="out" type="vector3" nodename="impl_heighttonormalmap" />
+  </nodegraph>
+  <!-- create nodegraph to test cm conversion on image node before feeding to normalmap  -->
+  <nodegraph name="normalmap_cm">
+    <image name="b_image" type="color3">
+      <input name="file" type="filename" value="resources/Images/grid.png" colorspace="srgb_texture" />
+    </image>
+    <convert name="c3tov3" type="vector3">
+      <input name="in" type="color3" nodename="b_image" />
+    </convert>
+    <normalmap name="impl_normalmap" type="vector3">
+      <input name="in" type="vector3" nodename="c3tov3" />
+      <input name="scale" type="float" value="1.5" />
+    </normalmap>
+    <output name="out" type="vector3" nodename="impl_normalmap" />
+  </nodegraph>
+</materialx>

+ 15 - 0
examples/materialx/combined_test.mtlx

@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_combined_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.6, 0.8, 0.4" />
+    <input name="opacity" type="float" value="0.7" />
+    <input name="specular" type="float" value="0.9" />
+    <input name="specular_color" type="color3" value="0.8, 1.0, 0.8" />
+    <input name="ior" type="float" value="1.8" />
+    <input name="specular_roughness" type="float" value="0.1" />
+    <input name="metalness" type="float" value="0.0" />
+  </standard_surface>
+</materialx> 

+ 35 - 0
examples/materialx/conditional_if_float.mtlx

@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_ifgreater_blue" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" output="out" nodegraph="ifgreater_blue" />
+  </standard_surface>
+  <nodegraph name="ifgreater_blue">
+    <image name="input_image" type="float">
+      <input name="file" type="filename" value="resources/Images/grid.png" />
+      <input name="default" type="float" value="0.0" />
+    </image>
+    <!-- Output a gradient of blue based on the image value for debugging -->
+    <combine3 name="blue_gradient" type="color3">
+      <input name="in1" type="float" value="0.0" />
+      <input name="in2" type="float" value="0.0" />
+      <input name="in3" type="float" nodename="input_image" />
+    </combine3>
+    <!-- Use ifgreater to output bright blue above threshold, dark blue below -->
+    <ifgreater name="ifgreater1" type="color3">
+      <input name="value1" type="float" nodename="input_image" />
+      <input name="value2" type="float" value="0.5" />
+      <input name="in1" type="color3" value="0.2, 0.2, 1.0" /> <!-- bright blue -->
+      <input name="in2" type="color3" value="0.0, 0.0, 0.2" /> <!-- dark blue -->
+    </ifgreater>
+    <!-- Mix between the blue_gradient and the thresholded blue for debug visualization -->
+    <mix name="debug_mix" type="color3">
+      <input name="bg" type="color3" nodename="blue_gradient" />
+      <input name="fg" type="color3" nodename="ifgreater1" />
+      <input name="mix" type="float" value="0.5" />
+    </mix>
+    <output name="out" type="color3" nodename="debug_mix" />
+  </nodegraph>
+</materialx>

+ 23 - 0
examples/materialx/heightnormal.mtlx

@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <nodegraph name="height_to_normal_example">
+    <image name="height_image" type="float">
+      <input name="file" type="filename" value="resources/Images/grid.png" />
+      <input name="default" type="float" value="0.5" />
+    </image>
+    <heighttonormal name="height_to_normal" type="vector3">
+      <input name="in" type="float" nodename="height_image" />
+      <input name="scale" type="float" value="1.0" />
+    </heighttonormal>
+    <convert name="normal_to_color" type="color3">
+      <input name="in" type="vector3" nodename="height_to_normal" />
+    </convert>
+    <output name="out" type="color3" nodename="normal_to_color" />
+  </nodegraph>
+  <surfacematerial name="mat_heightnormal" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" output="out" nodegraph="height_to_normal_example" />
+  </standard_surface>
+</materialx> 

+ 21 - 0
examples/materialx/heighttonormal_normal_input.mtlx

@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_heighttonormal_normal" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.8, 0.8, 0.8" />
+    <input name="normal" type="vector3" output="out" nodegraph="height_to_normal_normal" />
+  </standard_surface>
+  <nodegraph name="height_to_normal_normal">
+    <image name="height_image" type="float">
+      <input name="file" type="filename" value="resources/Images/grid.png" />
+      <input name="default" type="float" value="0.5" />
+    </image>
+    <heighttonormal name="height_to_normal" type="vector3">
+      <input name="in" type="float" nodename="height_image" />
+      <input name="scale" type="float" value="2.0" />
+    </heighttonormal>
+    <output name="out" type="vector3" nodename="height_to_normal" />
+  </nodegraph>
+</materialx> 

+ 26 - 0
examples/materialx/image_transform.mtlx

@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_image_transform" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" output="out" nodegraph="test_place2d_SRT" />
+  </standard_surface>
+  <nodegraph name="test_place2d_SRT">
+    <texcoord name="texcoord1" type="vector2" />
+    <place2d name="a_place2d" type="vector2">
+      <input name="texcoord" type="vector2" nodename="texcoord1" />
+      <input name="offset" type="vector2" value="0.0, 0.0" />
+      <input name="rotate" type="float" value="30.0" unittype="angle" unit="degree" />
+      <input name="scale" type="vector2" value="2.0, 1.0" />
+      <input name="pivot" type="vector2" value="0.5, 0.5" />
+      <input name="operationorder" type="integer" value="0" />
+    </place2d>
+    <image name="image_number_1" type="color3">
+      <input name="file" type="filename" value="resources/Images/grid.png" />
+      <input name="default" type="color3" value="1.0, 0.0, 0.0" />
+      <input name="texcoord" type="vector2" nodename="a_place2d" />
+    </image>
+    <output name="out" type="color3" nodename="image_number_1" />
+  </nodegraph>
+</materialx>

+ 12 - 0
examples/materialx/ior_test.mtlx

@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_ior_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.9, 0.9, 0.9" />
+    <input name="ior" type="float" value="2.4" />
+    <input name="specular_roughness" type="float" value="0.0" />
+    <input name="metalness" type="float" value="0.0" />
+  </standard_surface>
+</materialx> 

+ 14 - 0
examples/materialx/opacity_only_test.mtlx

@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_opacity_only_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.2, 0.8, 0.2" />
+    <input name="opacity" type="float" value="0.5" />
+    <input name="specular_roughness" type="float" value="0.2" />
+    <input name="metalness" type="float" value="0.0" />
+    <input name="ior" type="float" value="1.5" />
+    <input name="specular" type="float" value="1.0" />
+  </standard_surface>
+</materialx> 

+ 12 - 0
examples/materialx/opacity_test.mtlx

@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_opacity_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.8, 0.2, 0.2" />
+    <input name="opacity" type="float" value="0.5" />
+    <input name="specular_roughness" type="float" value="0.1" />
+    <input name="metalness" type="float" value="0.0" />
+  </standard_surface>
+</materialx> 

BIN
examples/materialx/resources/Images/grid.png


+ 23 - 0
examples/materialx/rotate2d_test.mtlx

@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_rotate2d_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" output="out" nodegraph="rotate2d_test" />
+  </standard_surface>
+  <nodegraph name="rotate2d_test">
+    <texcoord name="texcoord1" type="vector2" />
+    <rotate2d name="rotate2d_1" type="vector2">
+      <input name="in" type="vector2" nodename="texcoord1" />
+      <input name="amount" type="float" value="45.0" unittype="angle" unit="degree" />
+      <input name="pivot" type="vector2" value="0.5, 0.5" />
+    </rotate2d>
+    <image name="rotated_image" type="color3">
+      <input name="file" type="filename" value="resources/Images/grid.png" />
+      <input name="default" type="color3" value="0.5, 0.5, 0.5" />
+      <input name="texcoord" type="vector2" nodename="rotate2d_1" />
+    </image>
+    <output name="out" type="color3" nodename="rotated_image" />
+  </nodegraph>
+</materialx> 

+ 44 - 0
examples/materialx/rotate3d_test.mtlx

@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_rotate2d_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" output="out" nodegraph="rotate2d_test" />
+  </standard_surface>
+  <nodegraph name="rotate2d_test">
+    <texcoord name="texcoord1" type="vector2" />
+    <separate2 name="separate_texcoord" type="vector2">
+      <input name="in" type="vector2" nodename="texcoord1" />
+    </separate2>
+    <combine3 name="texcoord_3d" type="vector3">
+      <input name="in1" type="float" nodename="separate_texcoord" output="x" />
+      <input name="in2" type="float" nodename="separate_texcoord" output="y" />
+      <input name="in3" type="float" value="0.0" />
+    </combine3>
+    <time name="time1" type="float" />
+    <multiply name="multiply1" type="float">
+      <input name="in1" type="float" nodename="time1" />
+      <input name="in2" type="float" value="10.0" />
+    </multiply>
+    <rotate3d name="rotate3d_1" type="vector3">
+      <input name="in" type="vector3" nodename="texcoord_3d" />
+      <input name="amount" type="float" nodename="multiply1" />
+      <input name="axis" type="vector3" value="0.0, 0.0, 1.0" />
+    </rotate3d>
+    <separate3 name="separate_rotated" type="vector3">
+      <input name="in" type="vector3" nodename="rotate3d_1" />
+    </separate3>
+    <combine3 name="rotated_texcoord" type="vector3">
+      <input name="in1" type="float" nodename="separate_rotated" output="x" />
+      <input name="in2" type="float" nodename="separate_rotated" output="y" />
+      <input name="in3" type="float" nodename="separate_rotated" output="z" />
+    </combine3>
+    <image name="rotated_image" type="color3">
+      <input name="file" type="filename" value="resources/Images/grid.png" />
+      <input name="default" type="color3" value="0.5, 0.5, 0.5" />
+      <input name="texcoord" type="vector2" nodename="rotated_texcoord" />
+    </image>
+    <output name="out" type="color3" nodename="rotated_image" />
+  </nodegraph>
+</materialx> 

+ 17 - 0
examples/materialx/roughness_test.mtlx

@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_roughness_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.8, 0.8, 0.8" />
+    <input name="roughness" type="float" output="out" nodegraph="roughness_map" />
+  </standard_surface>
+  <nodegraph name="roughness_map">
+    <image name="roughness_image" type="float">
+      <input name="file" type="filename" value="resources/Images/grid.png" />
+      <input name="default" type="float" value="0.5" />
+    </image>
+    <output name="out" type="float" nodename="roughness_image" />
+  </nodegraph>
+</materialx> 

+ 14 - 0
examples/materialx/sheen_test.mtlx

@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_sheen_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.2, 0.4, 0.8" />
+    <input name="sheen" type="float" value="0.8" />
+    <input name="sheen_color" type="color3" value="1.0, 0.9, 0.7" />
+    <input name="sheen_roughness" type="float" value="0.3" />
+    <input name="specular_roughness" type="float" value="0.8" />
+    <input name="metalness" type="float" value="0.0" />
+  </standard_surface>
+</materialx> 

+ 13 - 0
examples/materialx/specular_test.mtlx

@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_specular_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.2, 0.2, 0.8" />
+    <input name="specular" type="float" value="0.8" />
+    <input name="specular_color" type="color3" value="1.0, 0.8, 0.6" />
+    <input name="specular_roughness" type="float" value="0.05" />
+    <input name="metalness" type="float" value="0.0" />
+  </standard_surface>
+</materialx> 

+ 19 - 0
examples/materialx/texture_opacity_test.mtlx

@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_texture_opacity_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.2, 0.6, 0.8" />
+    <input name="opacity" type="float" output="out" nodegraph="opacity_texture_graph" />
+    <input name="specular_roughness" type="float" value="0.2" />
+    <input name="metalness" type="float" value="0.0" />
+  </standard_surface>
+  <nodegraph name="opacity_texture_graph">
+    <image name="opacity_texture" type="float">
+      <input name="file" type="filename" value="resources/Images/grid.png" />
+      <input name="default" type="float" value="1.0" />
+    </image>
+    <output name="out" type="float" nodename="opacity_texture" />
+  </nodegraph>
+</materialx> 

+ 15 - 0
examples/materialx/thin_film_ior_clamp_test.mtlx

@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_thin_film_ior_clamp_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.1, 0.1, 0.1" />
+    <input name="thin_film_thickness" type="float" value="200.0" />
+    <input name="thin_film_ior" type="float" value="3.5" />
+    <input name="specular_roughness" type="float" value="0.1" />
+    <input name="metalness" type="float" value="0.0" />
+    <input name="ior" type="float" value="1.5" />
+    <input name="specular" type="float" value="1.0" />
+  </standard_surface>
+</materialx> 

+ 15 - 0
examples/materialx/thin_film_rainbow_test.mtlx

@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_thin_film_rainbow_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.05, 0.05, 0.05" />
+    <input name="thin_film_thickness" type="float" value="100.0" />
+    <input name="thin_film_ior" type="float" value="2.0" />
+    <input name="specular_roughness" type="float" value="0.0" />
+    <input name="metalness" type="float" value="0.0" />
+    <input name="ior" type="float" value="1.5" />
+    <input name="specular" type="float" value="1.0" />
+  </standard_surface>
+</materialx> 

+ 14 - 0
examples/materialx/transmission_only_test.mtlx

@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_transmission_only_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="1.0, 1.0, 1.0" />
+    <input name="transmission" type="float" value="1.0" />
+    <input name="transmission_color" type="color3" value="1.0, 1.0, 1.0" />
+    <input name="ior" type="float" value="1.5" />
+    <input name="specular_roughness" type="float" value="0.05" />
+    <input name="metalness" type="float" value="0.0" />
+  </standard_surface>
+</materialx> 

+ 14 - 0
examples/materialx/transmission_rough.mtlx

@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_transmission_rough" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.9, 0.95, 1.0" />
+    <input name="transmission" type="float" value="0.95" />
+    <input name="transmission_color" type="color3" value="0.95, 0.98, 1.0" />
+    <input name="ior" type="float" value="1.5" />
+    <input name="specular_roughness" type="float" value="0.3" />
+    <input name="metalness" type="float" value="0.0" />
+  </standard_surface>
+</materialx> 

+ 14 - 0
examples/materialx/transmission_test.mtlx

@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<materialx version="1.39">
+  <surfacematerial name="mat_transmission_test" type="material" nodedef="ND_surfacematerial">
+    <input name="surfaceshader" type="surfaceshader" nodename="surface_shader1" />
+  </surfacematerial>
+  <standard_surface name="surface_shader1" type="surfaceshader" nodedef="ND_standard_surface_surfaceshader">
+    <input name="base_color" type="color3" value="0.9, 0.95, 1.0" />
+    <input name="transmission" type="float" value="0.9" />
+    <input name="transmission_color" type="color3" value="0.95, 0.98, 1.0" />
+    <input name="ior" type="float" value="1.5" />
+    <input name="specular_roughness" type="float" value="0.1" />
+    <input name="metalness" type="float" value="0.0" />
+  </standard_surface>
+</materialx> 

BIN
examples/screenshots/webgpu_loader_materialx.jpg


+ 35 - 6
examples/webgpu_loader_materialx.html

@@ -40,7 +40,7 @@
 			import { MaterialXLoader } from './jsm/loaders/MaterialXLoader.js';
 
 			const SAMPLE_PATH = 'https://raw.githubusercontent.com/materialx/MaterialX/main/resources/Materials/Examples/StandardSurface/';
-
+			const LOCAL_SAMPLE_PATH = 'materialx/';
 			const samples = [
 				'standard_surface_brass_tiled.mtlx',
 				'standard_surface_brick_procedural.mtlx',
@@ -65,6 +65,29 @@
 				'standard_surface_wood_tiled.mtlx'
 			];
 
+			const localSamples = [
+				'heightnormal.mtlx',
+				'conditional_if_float.mtlx',
+				'image_transform.mtlx',
+				'color3_vec3_cm_test.mtlx',
+				'rotate2d_test.mtlx',
+				'rotate3d_test.mtlx',
+				'heighttonormal_normal_input.mtlx',
+				'roughness_test.mtlx',
+				'opacity_test.mtlx',
+				'opacity_only_test.mtlx',
+				'specular_test.mtlx',
+				'ior_test.mtlx',
+				'combined_test.mtlx',
+				'texture_opacity_test.mtlx',
+				'transmission_test.mtlx',
+				'transmission_only_test.mtlx',
+				'transmission_rough.mtlx',
+				'thin_film_rainbow_test.mtlx',
+				'thin_film_ior_clamp_test.mtlx',
+				'sheen_test.mtlx',
+			];
+
 			let camera, scene, renderer, prefab;
 			const models = [];
 
@@ -80,7 +103,7 @@
 
 				scene = new THREE.Scene();
 
-				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer = new THREE.WebGPURenderer( { antialias: true, alpha: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.toneMapping = THREE.LinearToneMapping;
@@ -109,7 +132,13 @@
 
 						for ( const sample of samples ) {
 
-							addSample( sample );
+							await addSample( sample, SAMPLE_PATH );
+
+						}
+
+						for ( const sample of localSamples ) {
+
+							await addSample( sample, LOCAL_SAMPLE_PATH );
 
 						}
 
@@ -141,7 +170,7 @@
 
 			}
 
-			async function addSample( sample ) {
+			async function addSample( sample, path ) {
 
 				const model = prefab.clone();
 
@@ -154,10 +183,10 @@
 				//
 
 				const material = await new MaterialXLoader()
-					.setPath( SAMPLE_PATH )
+					.setPath( path )
 					.loadAsync( sample )
 					.then( ( { materials } ) => Object.values( materials ).pop() );
-
+			
 				const calibrationMesh = model.getObjectByName( 'Calibration_Mesh' );
 				calibrationMesh.material = material;
 

+ 65 - 21
src/Three.TSL.js

@@ -2,8 +2,10 @@ import { TSL } from 'three/webgpu';
 
 export const BRDF_GGX = TSL.BRDF_GGX;
 export const BRDF_Lambert = TSL.BRDF_Lambert;
+export const BasicPointShadowFilter = TSL.BasicPointShadowFilter;
 export const BasicShadowFilter = TSL.BasicShadowFilter;
 export const Break = TSL.Break;
+export const Const = TSL.Const;
 export const Continue = TSL.Continue;
 export const DFGApprox = TSL.DFGApprox;
 export const D_GGX = TSL.D_GGX;
@@ -13,27 +15,32 @@ export const F_Schlick = TSL.F_Schlick;
 export const Fn = TSL.Fn;
 export const INFINITY = TSL.INFINITY;
 export const If = TSL.If;
-export const Switch = TSL.Switch;
 export const Loop = TSL.Loop;
+export const NodeAccess = TSL.NodeAccess;
 export const NodeShaderStage = TSL.NodeShaderStage;
 export const NodeType = TSL.NodeType;
 export const NodeUpdateType = TSL.NodeUpdateType;
-export const NodeAccess = TSL.NodeAccess;
 export const PCFShadowFilter = TSL.PCFShadowFilter;
 export const PCFSoftShadowFilter = TSL.PCFSoftShadowFilter;
 export const PI = TSL.PI;
 export const PI2 = TSL.PI2;
+export const PointShadowFilter = TSL.PointShadowFilter;
 export const Return = TSL.Return;
 export const Schlick_to_F0 = TSL.Schlick_to_F0;
 export const ScriptableNodeResources = TSL.ScriptableNodeResources;
 export const ShaderNode = TSL.ShaderNode;
+export const Stack = TSL.Stack;
+export const Switch = TSL.Switch;
 export const TBNViewMatrix = TSL.TBNViewMatrix;
 export const VSMShadowFilter = TSL.VSMShadowFilter;
 export const V_GGX_SmithCorrelated = TSL.V_GGX_SmithCorrelated;
+export const Var = TSL.Var;
+export const VarIntent = TSL.VarIntent;
 export const abs = TSL.abs;
 export const acesFilmicToneMapping = TSL.acesFilmicToneMapping;
 export const acos = TSL.acos;
 export const add = TSL.add;
+export const addMethodChaining = TSL.addMethodChaining;
 export const addNodeElement = TSL.addNodeElement;
 export const agxToneMapping = TSL.agxToneMapping;
 export const all = TSL.all;
@@ -53,13 +60,13 @@ export const atan2 = TSL.atan2;
 export const atomicAdd = TSL.atomicAdd;
 export const atomicAnd = TSL.atomicAnd;
 export const atomicFunc = TSL.atomicFunc;
+export const atomicLoad = TSL.atomicLoad;
 export const atomicMax = TSL.atomicMax;
 export const atomicMin = TSL.atomicMin;
 export const atomicOr = TSL.atomicOr;
 export const atomicStore = TSL.atomicStore;
 export const atomicSub = TSL.atomicSub;
 export const atomicXor = TSL.atomicXor;
-export const atomicLoad = TSL.atomicLoad;
 export const attenuationColor = TSL.attenuationColor;
 export const attenuationDistance = TSL.attenuationDistance;
 export const attribute = TSL.attribute;
@@ -112,6 +119,7 @@ export const checker = TSL.checker;
 export const cineonToneMapping = TSL.cineonToneMapping;
 export const clamp = TSL.clamp;
 export const clearcoat = TSL.clearcoat;
+export const clearcoatNormalView = TSL.clearcoatNormalView;
 export const clearcoatRoughness = TSL.clearcoatRoughness;
 export const code = TSL.code;
 export const color = TSL.color;
@@ -120,8 +128,6 @@ export const colorToDirection = TSL.colorToDirection;
 export const compute = TSL.compute;
 export const computeKernel = TSL.computeKernel;
 export const computeSkinning = TSL.computeSkinning;
-export const cond = TSL.cond;
-export const Const = TSL.Const;
 export const context = TSL.context;
 export const convert = TSL.convert;
 export const convertColorSpace = TSL.convertColorSpace;
@@ -129,6 +135,8 @@ export const convertToTexture = TSL.convertToTexture;
 export const cos = TSL.cos;
 export const cross = TSL.cross;
 export const cubeTexture = TSL.cubeTexture;
+export const cubeTextureBase = TSL.cubeTextureBase;
+export const cubeToUV = TSL.cubeToUV;
 export const dFdx = TSL.dFdx;
 export const dFdy = TSL.dFdy;
 export const dashSize = TSL.dashSize;
@@ -144,10 +152,12 @@ export const densityFog = TSL.densityFog;
 export const densityFogFactor = TSL.densityFogFactor;
 export const depth = TSL.depth;
 export const depthPass = TSL.depthPass;
+export const determinant = TSL.determinant;
 export const difference = TSL.difference;
 export const diffuseColor = TSL.diffuseColor;
 export const directPointLight = TSL.directPointLight;
 export const directionToColor = TSL.directionToColor;
+export const directionToFaceDirection = TSL.directionToFaceDirection;
 export const dispersion = TSL.dispersion;
 export const distance = TSL.distance;
 export const div = TSL.div;
@@ -186,10 +196,11 @@ export const getParallaxCorrectNormal = TSL.getParallaxCorrectNormal;
 export const getRoughness = TSL.getRoughness;
 export const getScreenPosition = TSL.getScreenPosition;
 export const getShIrradianceAt = TSL.getShIrradianceAt;
-export const getTextureIndex = TSL.getTextureIndex;
-export const getViewPosition = TSL.getViewPosition;
 export const getShadowMaterial = TSL.getShadowMaterial;
 export const getShadowRenderObjectFunction = TSL.getShadowRenderObjectFunction;
+export const getTextureIndex = TSL.getTextureIndex;
+export const getViewPosition = TSL.getViewPosition;
+export const globalId = TSL.globalId;
 export const glsl = TSL.glsl;
 export const glslFn = TSL.glslFn;
 export const grayscale = TSL.grayscale;
@@ -208,6 +219,7 @@ export const instancedBufferAttribute = TSL.instancedBufferAttribute;
 export const instancedDynamicBufferAttribute = TSL.instancedDynamicBufferAttribute;
 export const instancedMesh = TSL.instancedMesh;
 export const int = TSL.int;
+export const inverse = TSL.inverse;
 export const inverseSqrt = TSL.inverseSqrt;
 export const inversesqrt = TSL.inversesqrt;
 export const invocationLocalIndex = TSL.invocationLocalIndex;
@@ -226,6 +238,7 @@ export const lengthSq = TSL.lengthSq;
 export const lessThan = TSL.lessThan;
 export const lessThanEqual = TSL.lessThanEqual;
 export const lightPosition = TSL.lightPosition;
+export const lightProjectionUV = TSL.lightProjectionUV;
 export const lightShadowMatrix = TSL.lightShadowMatrix;
 export const lightTargetDirection = TSL.lightTargetDirection;
 export const lightTargetPosition = TSL.lightTargetPosition;
@@ -235,13 +248,10 @@ export const lights = TSL.lights;
 export const linearDepth = TSL.linearDepth;
 export const linearToneMapping = TSL.linearToneMapping;
 export const localId = TSL.localId;
-export const globalId = TSL.globalId;
 export const log = TSL.log;
 export const log2 = TSL.log2;
 export const logarithmicDepthToViewZ = TSL.logarithmicDepthToViewZ;
-export const loop = TSL.loop;
 export const luminance = TSL.luminance;
-export const mediumpModelViewMatrix = TSL.mediumpModelViewMatrix;
 export const mat2 = TSL.mat2;
 export const mat3 = TSL.mat3;
 export const mat4 = TSL.mat4;
@@ -258,6 +268,8 @@ export const materialClearcoatRoughness = TSL.materialClearcoatRoughness;
 export const materialColor = TSL.materialColor;
 export const materialDispersion = TSL.materialDispersion;
 export const materialEmissive = TSL.materialEmissive;
+export const materialEnvIntensity = TSL.materialEnvIntensity;
+export const materialEnvRotation = TSL.materialEnvRotation;
 export const materialIOR = TSL.materialIOR;
 export const materialIridescence = TSL.materialIridescence;
 export const materialIridescenceIOR = TSL.materialIridescenceIOR;
@@ -288,6 +300,7 @@ export const materialThickness = TSL.materialThickness;
 export const materialTransmission = TSL.materialTransmission;
 export const max = TSL.max;
 export const maxMipLevel = TSL.maxMipLevel;
+export const mediumpModelViewMatrix = TSL.mediumpModelViewMatrix;
 export const metalness = TSL.metalness;
 export const min = TSL.min;
 export const mix = TSL.mix;
@@ -308,24 +321,45 @@ export const morphReference = TSL.morphReference;
 export const mrt = TSL.mrt;
 export const mul = TSL.mul;
 export const mx_aastep = TSL.mx_aastep;
+export const mx_add = TSL.mx_add;
+export const mx_atan2 = TSL.mx_atan2;
 export const mx_cell_noise_float = TSL.mx_cell_noise_float;
 export const mx_contrast = TSL.mx_contrast;
+export const mx_divide = TSL.mx_divide;
 export const mx_fractal_noise_float = TSL.mx_fractal_noise_float;
 export const mx_fractal_noise_vec2 = TSL.mx_fractal_noise_vec2;
 export const mx_fractal_noise_vec3 = TSL.mx_fractal_noise_vec3;
 export const mx_fractal_noise_vec4 = TSL.mx_fractal_noise_vec4;
+export const mx_frame = TSL.mx_frame;
+export const mx_heighttonormal = TSL.mx_heighttonormal;
 export const mx_hsvtorgb = TSL.mx_hsvtorgb;
+export const mx_ifequal = TSL.mx_ifequal;
+export const mx_ifgreater = TSL.mx_ifgreater;
+export const mx_ifgreatereq = TSL.mx_ifgreatereq;
+export const mx_invert = TSL.mx_invert;
+export const mx_modulo = TSL.mx_modulo;
+export const mx_multiply = TSL.mx_multiply;
 export const mx_noise_float = TSL.mx_noise_float;
 export const mx_noise_vec3 = TSL.mx_noise_vec3;
 export const mx_noise_vec4 = TSL.mx_noise_vec4;
+export const mx_place2d = TSL.mx_place2d;
+export const mx_power = TSL.mx_power;
+export const mx_ramp4 = TSL.mx_ramp4;
 export const mx_ramplr = TSL.mx_ramplr;
 export const mx_ramptb = TSL.mx_ramptb;
 export const mx_rgbtohsv = TSL.mx_rgbtohsv;
+export const mx_rotate2d = TSL.mx_rotate2d;
+export const mx_rotate3d = TSL.mx_rotate3d;
 export const mx_safepower = TSL.mx_safepower;
+export const mx_separate = TSL.mx_separate;
 export const mx_splitlr = TSL.mx_splitlr;
 export const mx_splittb = TSL.mx_splittb;
 export const mx_srgb_texture_to_lin_rec709 = TSL.mx_srgb_texture_to_lin_rec709;
+export const mx_subtract = TSL.mx_subtract;
+export const mx_timer = TSL.mx_timer;
 export const mx_transform_uv = TSL.mx_transform_uv;
+export const mx_unifiednoise2d = TSL.mx_unifiednoise2d;
+export const mx_unifiednoise3d = TSL.mx_unifiednoise3d;
 export const mx_worley_noise_float = TSL.mx_worley_noise_float;
 export const mx_worley_noise_vec2 = TSL.mx_worley_noise_vec2;
 export const mx_worley_noise_vec3 = TSL.mx_worley_noise_vec3;
@@ -334,8 +368,10 @@ export const neutralToneMapping = TSL.neutralToneMapping;
 export const nodeArray = TSL.nodeArray;
 export const nodeImmutable = TSL.nodeImmutable;
 export const nodeObject = TSL.nodeObject;
+export const nodeObjectIntent = TSL.nodeObjectIntent;
 export const nodeObjects = TSL.nodeObjects;
 export const nodeProxy = TSL.nodeProxy;
+export const nodeProxyIntent = TSL.nodeProxyIntent;
 export const normalFlat = TSL.normalFlat;
 export const normalGeometry = TSL.normalGeometry;
 export const normalLocal = TSL.normalLocal;
@@ -375,6 +411,7 @@ export const passTexture = TSL.passTexture;
 export const pcurve = TSL.pcurve;
 export const perspectiveDepthToViewZ = TSL.perspectiveDepthToViewZ;
 export const pmremTexture = TSL.pmremTexture;
+export const pointShadow = TSL.pointShadow;
 export const pointUV = TSL.pointUV;
 export const pointWidth = TSL.pointWidth;
 export const positionGeometry = TSL.positionGeometry;
@@ -397,7 +434,6 @@ export const range = TSL.range;
 export const rangeFog = TSL.rangeFog;
 export const rangeFogFactor = TSL.rangeFogFactor;
 export const reciprocal = TSL.reciprocal;
-export const lightProjectionUV = TSL.lightProjectionUV;
 export const reference = TSL.reference;
 export const referenceBuffer = TSL.referenceBuffer;
 export const reflect = TSL.reflect;
@@ -408,7 +444,6 @@ export const refract = TSL.refract;
 export const refractVector = TSL.refractVector;
 export const refractView = TSL.refractView;
 export const reinhardToneMapping = TSL.reinhardToneMapping;
-export const remainder = TSL.remainder;
 export const remap = TSL.remap;
 export const remapClamp = TSL.remapClamp;
 export const renderGroup = TSL.renderGroup;
@@ -436,10 +471,9 @@ export const select = TSL.select;
 export const setCurrentStack = TSL.setCurrentStack;
 export const shaderStages = TSL.shaderStages;
 export const shadow = TSL.shadow;
-export const pointShadow = TSL.pointShadow;
 export const shadowPositionWorld = TSL.shadowPositionWorld;
-export const sharedUniformGroup = TSL.sharedUniformGroup;
 export const shapeCircle = TSL.shapeCircle;
+export const sharedUniformGroup = TSL.sharedUniformGroup;
 export const sheen = TSL.sheen;
 export const sheenRoughness = TSL.sheenRoughness;
 export const shiftLeft = TSL.shiftLeft;
@@ -459,6 +493,7 @@ export const spritesheetUV = TSL.spritesheetUV;
 export const sqrt = TSL.sqrt;
 export const stack = TSL.stack;
 export const step = TSL.step;
+export const stepElement = TSL.stepElement;
 export const storage = TSL.storage;
 export const storageBarrier = TSL.storageBarrier;
 export const storageObject = TSL.storageObject;
@@ -504,22 +539,18 @@ export const triNoise3D = TSL.triNoise3D;
 export const triplanarTexture = TSL.triplanarTexture;
 export const triplanarTextures = TSL.triplanarTextures;
 export const trunc = TSL.trunc;
-export const tslFn = TSL.tslFn;
 export const uint = TSL.uint;
 export const uniform = TSL.uniform;
-export const uniformCubeTexture = TSL.uniformCubeTexture;
 export const uniformArray = TSL.uniformArray;
+export const uniformCubeTexture = TSL.uniformCubeTexture;
 export const uniformGroup = TSL.uniformGroup;
 export const uniformTexture = TSL.uniformTexture;
-export const uniforms = TSL.uniforms;
 export const unpremultiplyAlpha = TSL.unpremultiplyAlpha;
 export const userData = TSL.userData;
 export const uv = TSL.uv;
 export const uvec2 = TSL.uvec2;
 export const uvec3 = TSL.uvec3;
 export const uvec4 = TSL.uvec4;
-export const Var = TSL.Var;
-export const VarIntent = TSL.VarIntent;
 export const varying = TSL.varying;
 export const varyingProperty = TSL.varyingProperty;
 export const vec2 = TSL.vec2;
@@ -529,12 +560,12 @@ export const vectorComponents = TSL.vectorComponents;
 export const velocity = TSL.velocity;
 export const vertexColor = TSL.vertexColor;
 export const vertexIndex = TSL.vertexIndex;
+export const vertexStage = TSL.vertexStage;
 export const vibrance = TSL.vibrance;
 export const viewZToLogarithmicDepth = TSL.viewZToLogarithmicDepth;
 export const viewZToOrthographicDepth = TSL.viewZToOrthographicDepth;
 export const viewZToPerspectiveDepth = TSL.viewZToPerspectiveDepth;
 export const viewport = TSL.viewport;
-export const viewportBottomLeft = TSL.viewportBottomLeft;
 export const viewportCoordinate = TSL.viewportCoordinate;
 export const viewportDepthTexture = TSL.viewportDepthTexture;
 export const viewportLinearDepth = TSL.viewportLinearDepth;
@@ -544,7 +575,6 @@ export const viewportSafeUV = TSL.viewportSafeUV;
 export const viewportSharedTexture = TSL.viewportSharedTexture;
 export const viewportSize = TSL.viewportSize;
 export const viewportTexture = TSL.viewportTexture;
-export const viewportTopLeft = TSL.viewportTopLeft;
 export const viewportUV = TSL.viewportUV;
 export const wgsl = TSL.wgsl;
 export const wgslFn = TSL.wgslFn;
@@ -553,3 +583,17 @@ export const workgroupBarrier = TSL.workgroupBarrier;
 export const workgroupId = TSL.workgroupId;
 export const workingToColorSpace = TSL.workingToColorSpace;
 export const xor = TSL.xor;
+
+/*
+// Use this code to generate the export statements dynamically
+
+let code = '';
+
+for ( const key of Object.keys( THREE.TSL ) ) {
+
+	code += `export const ${ key } = TSL.${ key };\n`;
+
+}
+
+console.log( code );
+//*/

+ 131 - 2
src/nodes/materialx/MaterialXNodes.js

@@ -2,13 +2,17 @@ import {
 	mx_perlin_noise_float, mx_perlin_noise_vec3,
 	mx_worley_noise_float as worley_noise_float, mx_worley_noise_vec2 as worley_noise_vec2, mx_worley_noise_vec3 as worley_noise_vec3,
 	mx_cell_noise_float as cell_noise_float,
+	mx_unifiednoise2d as unifiednoise2d, mx_unifiednoise3d as unifiednoise3d,
 	mx_fractal_noise_float as fractal_noise_float, mx_fractal_noise_vec2 as fractal_noise_vec2, mx_fractal_noise_vec3 as fractal_noise_vec3, mx_fractal_noise_vec4 as fractal_noise_vec4
 } from './lib/mx_noise.js';
 import { mx_hsvtorgb, mx_rgbtohsv } from './lib/mx_hsv.js';
 import { mx_srgb_texture_to_lin_rec709 } from './lib/mx_transform_color.js';
-import { mix, smoothstep } from '../math/MathNode.js';
+
+import { float, vec2, vec3, vec4, int, add, sub, mul, div, mod, atan, mix, pow, smoothstep } from '../tsl/TSLBase.js';
 import { uv } from '../accessors/UV.js';
-import { float, vec2, vec4, int } from '../tsl/TSLBase.js';
+import { bumpMap } from '../display/BumpMapNode.js';
+import { rotate } from '../utils/RotateNode.js';
+import { frameId, time } from '../utils/Timer.js';
 
 export const mx_aastep = ( threshold, value ) => {
 
@@ -25,6 +29,19 @@ const _ramp = ( a, b, uv, p ) => mix( a, b, uv[ p ].clamp() );
 export const mx_ramplr = ( valuel, valuer, texcoord = uv() ) => _ramp( valuel, valuer, texcoord, 'x' );
 export const mx_ramptb = ( valuet, valueb, texcoord = uv() ) => _ramp( valuet, valueb, texcoord, 'y' );
 
+// Bilinear ramp: interpolate between four corners (tl, tr, bl, br) using texcoord.x and texcoord.y
+export const mx_ramp4 = (
+	valuetl, valuetr, valuebl, valuebr, texcoord = uv()
+) => {
+
+	const u = texcoord.x.clamp();
+	const v = texcoord.y.clamp();
+	const top = mix( valuetl, valuetr, u );
+	const bottom = mix( valuebl, valuebr, u );
+	return mix( top, bottom, v );
+
+};
+
 const _split = ( a, b, center, uv, p ) => mix( a, b, mx_aastep( center, uv[ p ] ) );
 export const mx_splitlr = ( valuel, valuer, center, texcoord = uv() ) => _split( valuel, valuer, center, texcoord, 'x' );
 export const mx_splittb = ( valuet, valueb, center, texcoord = uv() ) => _split( valuet, valueb, center, texcoord, 'y' );
@@ -54,6 +71,9 @@ export const mx_noise_vec4 = ( texcoord = uv(), amplitude = 1, pivot = 0 ) => {
 
 };
 
+export const mx_unifiednoise2d = ( noiseType, texcoord = uv(), freq = vec2( 1, 1 ), offset = vec2( 0, 0 ), jitter = 1, outmin = 0, outmax = 1, clampoutput = false, octaves = 1, lacunarity = 2, diminish = .5 ) => unifiednoise2d( noiseType, texcoord.convert( 'vec2|vec3' ), freq, offset, jitter, outmin, outmax, clampoutput, octaves, lacunarity, diminish );
+export const mx_unifiednoise3d = ( noiseType, texcoord = uv(), freq = vec2( 1, 1 ), offset = vec2( 0, 0 ), jitter = 1, outmin = 0, outmax = 1, clampoutput = false, octaves = 1, lacunarity = 2, diminish = .5 ) => unifiednoise3d( noiseType, texcoord.convert( 'vec2|vec3' ), freq, offset, jitter, outmin, outmax, clampoutput, octaves, lacunarity, diminish );
+
 export const mx_worley_noise_float = ( texcoord = uv(), jitter = 1 ) => worley_noise_float( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) );
 export const mx_worley_noise_vec2 = ( texcoord = uv(), jitter = 1 ) => worley_noise_vec2( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) );
 export const mx_worley_noise_vec3 = ( texcoord = uv(), jitter = 1 ) => worley_noise_vec3( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) );
@@ -66,3 +86,112 @@ export const mx_fractal_noise_vec3 = ( position = uv(), octaves = 3, lacunarity
 export const mx_fractal_noise_vec4 = ( position = uv(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => fractal_noise_vec4( position, int( octaves ), lacunarity, diminish ).mul( amplitude );
 
 export { mx_hsvtorgb, mx_rgbtohsv, mx_srgb_texture_to_lin_rec709 };
+
+// === Moved from MaterialXLoader.js ===
+
+// Math ops
+export const mx_add = ( in1, in2 = float( 0 ) ) => add( in1, in2 );
+export const mx_subtract = ( in1, in2 = float( 0 ) ) => sub( in1, in2 );
+export const mx_multiply = ( in1, in2 = float( 1 ) ) => mul( in1, in2 );
+export const mx_divide = ( in1, in2 = float( 1 ) ) => div( in1, in2 );
+export const mx_modulo = ( in1, in2 = float( 1 ) ) => mod( in1, in2 );
+export const mx_power = ( in1, in2 = float( 1 ) ) => pow( in1, in2 );
+export const mx_atan2 = ( in1 = float( 0 ), in2 = float( 1 ) ) => atan( in1, in2 );
+export const mx_timer = () => time;
+export const mx_frame = () => frameId;
+export const mx_invert = ( in1, amount = float( 1 ) ) => sub( amount, in1 );
+export const mx_ifgreater = ( value1, value2, in1, in2 ) => value1.greaterThan( value2 ).mix( in1, in2 );
+export const mx_ifgreatereq = ( value1, value2, in1, in2 ) => value1.greaterThanEqual( value2 ).mix( in1, in2 );
+export const mx_ifequal = ( value1, value2, in1, in2 ) => value1.equal( value2 ).mix( in1, in2 );
+
+// Enhanced separate node to support multi-output referencing (outx, outy, outz, outw)
+export const mx_separate = ( in1, channelOrOut = null ) => {
+
+	if ( typeof channelOrOut === 'string' ) {
+
+		const map = { x: 0, r: 0, y: 1, g: 1, z: 2, b: 2, w: 3, a: 3 };
+		const c = channelOrOut.replace( /^out/, '' ).toLowerCase();
+		if ( map[ c ] !== undefined ) return in1.element( map[ c ] );
+
+	}
+
+	if ( typeof channelOrOut === 'number' ) {
+
+		return in1.element( channelOrOut );
+
+	}
+
+	if ( typeof channelOrOut === 'string' && channelOrOut.length === 1 ) {
+
+		const map = { x: 0, r: 0, y: 1, g: 1, z: 2, b: 2, w: 3, a: 3 };
+		if ( map[ channelOrOut ] !== undefined ) return in1.element( map[ channelOrOut ] );
+
+	}
+
+	return in1;
+
+};
+
+export const mx_place2d = (
+	texcoord, pivot = vec2( 0.5, 0.5 ), scale = vec2( 1, 1 ), rotate = float( 0 ), offset = vec2( 0, 0 )/*, operationorder = int( 0 )*/
+) => {
+
+	let uv = texcoord;
+	if ( pivot ) uv = uv.sub( pivot );
+	if ( scale ) uv = uv.mul( scale );
+	if ( rotate ) {
+
+		const rad = rotate.mul( Math.PI / 180.0 );
+		const cosR = rad.cos();
+		const sinR = rad.sin();
+		uv = vec2(
+			uv.x.mul( cosR ).sub( uv.y.mul( sinR ) ),
+			uv.x.mul( sinR ).add( uv.y.mul( cosR ) )
+		);
+
+	}
+
+	if ( pivot ) uv = uv.add( pivot );
+	if ( offset ) uv = uv.add( offset );
+	return uv;
+
+};
+
+export const mx_rotate2d = ( input, amount ) => {
+
+	input = vec2( input );
+	amount = float( amount );
+
+	const radians = amount.mul( Math.PI / 180.0 );
+	return rotate( input, radians );
+
+};
+
+export const mx_rotate3d = ( input, amount, axis ) => {
+
+	input = vec3( input );
+	amount = float( amount );
+	axis = vec3( axis );
+
+
+	const radians = amount.mul( Math.PI / 180.0 );
+	const nAxis = axis.normalize();
+	const cosA = radians.cos();
+	const sinA = radians.sin();
+	const oneMinusCosA = float( 1 ).sub( cosA );
+	const rot =
+		input.mul( cosA )
+			.add( nAxis.cross( input ).mul( sinA ) )
+			.add( nAxis.mul( nAxis.dot( input ) ).mul( oneMinusCosA ) );
+	return rot;
+
+};
+
+export const mx_heighttonormal = ( input, scale/*, texcoord*/ ) => {
+
+	input = vec3( input );
+	scale = float( scale );
+
+	return bumpMap( input, scale );
+
+};

+ 165 - 1
src/nodes/materialx/lib/mx_noise.js

@@ -4,7 +4,7 @@
 import { int, uint, float, vec3, bool, uvec3, vec2, vec4, If, Fn } from '../../tsl/TSLBase.js';
 import { select } from '../../math/ConditionalNode.js';
 import { sub, mul } from '../../math/OperatorNode.js';
-import { floor, abs, max, dot, min, sqrt } from '../../math/MathNode.js';
+import { floor, abs, max, dot, min, sqrt, clamp } from '../../math/MathNode.js';
 import { overloadingFn } from '../../utils/FunctionOverloadingNode.js';
 import { Loop } from '../../utils/LoopNode.js';
 
@@ -1325,3 +1325,167 @@ export const mx_worley_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_
 } );
 
 export const mx_worley_noise_vec3 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec3_0, mx_worley_noise_vec3_1 ] );
+
+// Unified Noise 2D
+export const mx_unifiednoise2d = /*@__PURE__*/ Fn( ( [
+	noiseType_immutable, texcoord_immutable, freq_immutable, offset_immutable,
+	jitter_immutable, outmin_immutable, outmax_immutable, clampoutput_immutable,
+	octaves_immutable, lacunarity_immutable, diminish_immutable
+] ) => {
+
+	const noiseType = int( noiseType_immutable ).toVar();
+	const texcoord = vec2( texcoord_immutable ).toVar();
+	const freq = vec2( freq_immutable ).toVar();
+	const offset = vec2( offset_immutable ).toVar();
+	const jitter = float( jitter_immutable ).toVar();
+	const outmin = float( outmin_immutable ).toVar();
+	const outmax = float( outmax_immutable ).toVar();
+	const clampoutput = bool( clampoutput_immutable ).toVar();
+	const octaves = int( octaves_immutable ).toVar();
+	const lacunarity = float( lacunarity_immutable ).toVar();
+	const diminish = float( diminish_immutable ).toVar();
+
+	// Compute input position
+	const p = texcoord.mul( freq ).add( offset );
+
+	const result = float( 0.0 ).toVar();
+
+	// Perlin
+	If( noiseType.equal( int( 0 ) ), () => {
+
+		result.assign( mx_perlin_noise_vec3( p ) );
+
+	} );
+
+	// Cell
+	If( noiseType.equal( int( 1 ) ), () => {
+
+		result.assign( mx_cell_noise_vec3( p ) );
+
+	} );
+
+	// Worley (metric=0 = euclidean)
+	If( noiseType.equal( int( 2 ) ), () => {
+
+		result.assign( mx_worley_noise_vec3( p, jitter, int( 0 ) ) );
+
+	} );
+
+	// Fractal (use vec3(p, 0.0) for 2D input)
+	If( noiseType.equal( int( 3 ) ), () => {
+
+		result.assign( mx_fractal_noise_vec3( vec3( p, 0.0 ), octaves, lacunarity, diminish ) );
+
+	} );
+
+	// Remap output to [outmin, outmax]
+	result.assign( result.mul( outmax.sub( outmin ) ).add( outmin ) );
+
+	// Clamp if requested
+	If( clampoutput, () => {
+
+		result.assign( clamp( result, outmin, outmax ) );
+
+	} );
+
+	return result;
+
+} ).setLayout( {
+	name: 'mx_unifiednoise2d',
+	type: 'float',
+	inputs: [
+		{ name: 'noiseType', type: 'int' },
+		{ name: 'texcoord', type: 'vec2' },
+		{ name: 'freq', type: 'vec2' },
+		{ name: 'offset', type: 'vec2' },
+		{ name: 'jitter', type: 'float' },
+		{ name: 'outmin', type: 'float' },
+		{ name: 'outmax', type: 'float' },
+		{ name: 'clampoutput', type: 'bool' },
+		{ name: 'octaves', type: 'int' },
+		{ name: 'lacunarity', type: 'float' },
+		{ name: 'diminish', type: 'float' }
+	]
+} );
+
+// Unified Noise 3D
+export const mx_unifiednoise3d = /*@__PURE__*/ Fn( ( [
+	noiseType_immutable, position_immutable, freq_immutable, offset_immutable,
+	jitter_immutable, outmin_immutable, outmax_immutable, clampoutput_immutable,
+	octaves_immutable, lacunarity_immutable, diminish_immutable
+] ) => {
+
+	const noiseType = int( noiseType_immutable ).toVar();
+	const position = vec3( position_immutable ).toVar();
+	const freq = vec3( freq_immutable ).toVar();
+	const offset = vec3( offset_immutable ).toVar();
+	const jitter = float( jitter_immutable ).toVar();
+	const outmin = float( outmin_immutable ).toVar();
+	const outmax = float( outmax_immutable ).toVar();
+	const clampoutput = bool( clampoutput_immutable ).toVar();
+	const octaves = int( octaves_immutable ).toVar();
+	const lacunarity = float( lacunarity_immutable ).toVar();
+	const diminish = float( diminish_immutable ).toVar();
+
+	// Compute input position
+	const p = position.mul( freq ).add( offset );
+
+	const result = float( 0.0 ).toVar();
+
+	// Perlin
+	If( noiseType.equal( int( 0 ) ), () => {
+
+		result.assign( mx_perlin_noise_vec3( p ) );
+
+	} );
+
+	// Cell
+	If( noiseType.equal( int( 1 ) ), () => {
+
+		result.assign( mx_cell_noise_vec3( p ) );
+
+	} );
+
+	// Worley (metric=0 = euclidean)
+	If( noiseType.equal( int( 2 ) ), () => {
+
+		result.assign( mx_worley_noise_vec3( p, jitter, int( 0 ) ) );
+
+	} );
+
+	// Fractal
+	If( noiseType.equal( int( 3 ) ), () => {
+
+		result.assign( mx_fractal_noise_vec3( p, octaves, lacunarity, diminish ) );
+
+	} );
+
+	// Remap output to [outmin, outmax]
+	result.assign( result.mul( outmax.sub( outmin ) ).add( outmin ) );
+
+	// Clamp if requested
+	If( clampoutput, () => {
+
+		result.assign( clamp( result, outmin, outmax ) );
+
+	} );
+
+	return result;
+
+} ).setLayout( {
+	name: 'mx_unifiednoise3d',
+	type: 'float',
+	inputs: [
+		{ name: 'noiseType', type: 'int' },
+		{ name: 'position', type: 'vec3' },
+		{ name: 'freq', type: 'vec3' },
+		{ name: 'offset', type: 'vec3' },
+		{ name: 'jitter', type: 'float' },
+		{ name: 'outmin', type: 'float' },
+		{ name: 'outmax', type: 'float' },
+		{ name: 'clampoutput', type: 'bool' },
+		{ name: 'octaves', type: 'int' },
+		{ name: 'lacunarity', type: 'float' },
+		{ name: 'diminish', type: 'float' }
+	]
+} );

+ 27 - 3
src/nodes/math/MathNode.js

@@ -361,6 +361,8 @@ MathNode.RECIPROCAL = 'reciprocal';
 MathNode.TRUNC = 'trunc';
 MathNode.FWIDTH = 'fwidth';
 MathNode.TRANSPOSE = 'transpose';
+MathNode.DETERMINANT = 'determinant';
+MathNode.INVERSE = 'inverse';
 
 // 2 inputs
 
@@ -743,6 +745,26 @@ export const fwidth = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.FWIDTH )
  */
 export const transpose = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.TRANSPOSE ).setParameterLength( 1 );
 
+/**
+ * Returns the determinant of a matrix.
+ *
+ * @tsl
+ * @function
+ * @param {Node<mat2|mat3|mat4>} x - The parameter.
+ * @returns {Node<float>}
+ */
+export const determinant = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.DETERMINANT ).setParameterLength( 1 );
+
+/**
+ * Returns the inverse of a matrix.
+ *
+ * @tsl
+ * @function
+ * @param {Node<mat2|mat3|mat4>} x - The parameter.
+ * @returns {Node<mat2|mat3|mat4>}
+ */
+export const inverse = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.INVERSE ).setParameterLength( 1 );
+
 // 2 inputs
 
 /**
@@ -853,9 +875,9 @@ export const dot = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.DOT ).setPa
  *
  * @tsl
  * @function
- * @param {Node<vec2|vec3|vec4>} x - The first vector.
- * @param {Node<vec2|vec3|vec4>} y - The second vector.
- * @returns {Node<vec2|vec3|vec4>}
+ * @param {Node<vec2|vec3>} x - The first vector.
+ * @param {Node<vec2|vec3>} y - The second vector.
+ * @returns {Node<float|vec3>}
  */
 export const cross = /*@__PURE__*/ nodeProxyIntent( MathNode, MathNode.CROSS ).setParameterLength( 2 );
 
@@ -1134,4 +1156,6 @@ addMethodChaining( 'difference', difference );
 addMethodChaining( 'saturate', saturate );
 addMethodChaining( 'cbrt', cbrt );
 addMethodChaining( 'transpose', transpose );
+addMethodChaining( 'determinant', determinant );
+addMethodChaining( 'inverse', inverse );
 addMethodChaining( 'rand', rand );

粤ICP备19079148号