Bläddra i källkod

NodeMaterialLoader + example + tests

sunag 7 år sedan
förälder
incheckning
cc5ffd8156

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
examples/nodes/caustic.json


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
examples/nodes/displace.json


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
examples/nodes/wave.json


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
examples/nodes/xray.json


+ 285 - 0
examples/webgl_loader_nodes.html

@@ -0,0 +1,285 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - node material</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				color: #fff;
+				font-family:Monospace;
+				font-size:13px;
+				margin: 0px;
+				text-align:center;
+				overflow: hidden;
+			}
+
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				display:block;
+			}
+
+			a { color: white }
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - Node-Based Material</br>
+			Serialized using <a href="webgl_materials_nodes.html">webgl_materials_nodes.html</a>
+		</div>
+
+		<script src="../build/three.js"></script>
+
+		<script src='js/geometries/TeapotBufferGeometry.js'></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
+
+		<!-- NodeLibrary -->
+		<script src="js/nodes/GLNode.js"></script>
+		<script src="js/nodes/RawNode.js"></script>
+		<script src="js/nodes/TempNode.js"></script>
+		<script src="js/nodes/InputNode.js"></script>
+		<script src="js/nodes/ConstNode.js"></script>
+		<script src="js/nodes/VarNode.js"></script>
+		<script src="js/nodes/FunctionNode.js"></script>
+		<script src="js/nodes/FunctionCallNode.js"></script>
+		<script src="js/nodes/AttributeNode.js"></script>
+		<script src="js/nodes/NodeBuilder.js"></script>
+		<script src="js/nodes/NodeLib.js"></script>
+		<script src="js/nodes/NodeMaterial.js"></script>
+
+		<!-- Accessors -->
+		<script src="js/nodes/accessors/PositionNode.js"></script>
+		<script src="js/nodes/accessors/NormalNode.js"></script>
+		<script src="js/nodes/accessors/UVNode.js"></script>
+		<script src="js/nodes/accessors/ScreenUVNode.js"></script>
+		<script src="js/nodes/accessors/ColorsNode.js"></script>
+		<script src="js/nodes/accessors/CameraNode.js"></script>
+		<script src="js/nodes/accessors/ReflectNode.js"></script>
+		<script src="js/nodes/accessors/LightNode.js"></script>
+
+		<!-- Inputs -->
+		<script src="js/nodes/inputs/IntNode.js"></script>
+		<script src="js/nodes/inputs/FloatNode.js"></script>
+		<script src="js/nodes/inputs/ColorNode.js"></script>
+		<script src="js/nodes/inputs/Vector2Node.js"></script>
+		<script src="js/nodes/inputs/Vector3Node.js"></script>
+		<script src="js/nodes/inputs/Vector4Node.js"></script>
+		<script src="js/nodes/inputs/TextureNode.js"></script>
+		<script src="js/nodes/inputs/Matrix4Node.js"></script>
+		<script src="js/nodes/inputs/CubeTextureNode.js"></script>
+
+		<!-- Math -->
+		<script src="js/nodes/math/Math1Node.js"></script>
+		<script src="js/nodes/math/Math2Node.js"></script>
+		<script src="js/nodes/math/Math3Node.js"></script>
+		<script src="js/nodes/math/OperatorNode.js"></script>
+
+		<!-- Utils -->
+		<script src="js/nodes/utils/SwitchNode.js"></script>
+		<script src="js/nodes/utils/JoinNode.js"></script>
+		<script src="js/nodes/utils/TimerNode.js"></script>
+		<script src="js/nodes/utils/RoughnessToBlinnExponentNode.js"></script>
+		<script src="js/nodes/utils/VelocityNode.js"></script>
+		<script src="js/nodes/utils/LuminanceNode.js"></script>
+		<script src="js/nodes/utils/ColorAdjustmentNode.js"></script>
+		<script src="js/nodes/utils/NoiseNode.js"></script>
+		<script src="js/nodes/utils/ResolutionNode.js"></script>
+		<script src="js/nodes/utils/BumpNode.js"></script>
+		<script src="js/nodes/utils/BlurNode.js"></script>
+		<script src="js/nodes/utils/UVTransformNode.js"></script>
+
+		<!-- Phong Material -->
+		<script src="js/nodes/materials/PhongNode.js"></script>
+		<script src="js/nodes/materials/PhongNodeMaterial.js"></script>
+
+		<!-- Standard Material -->
+		<script src="js/nodes/materials/StandardNode.js"></script>
+		<script src="js/nodes/materials/StandardNodeMaterial.js"></script>
+
+		<!-- NodeMaterial Loader -->
+		<script src="js/loaders/NodeMaterialLoader.js"></script>
+
+		<script>
+
+		var container = document.getElementById( 'container' );
+
+		var renderer, scene, camera, clock = new THREE.Clock(), fov = 50;
+		var teapot, mesh, cloud;
+		var controls;
+		var gui;
+
+		var param = { load: 'caustic' };
+
+		window.addEventListener( 'load', init );
+
+		function init() {
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			container.appendChild( renderer.domElement );
+
+			scene = new THREE.Scene();
+
+			camera = new THREE.PerspectiveCamera( fov, window.innerWidth / window.innerHeight, 1, 1000 );
+			camera.position.x = 50;
+			camera.position.z = - 50;
+			camera.position.y = 30;
+			camera.target = new THREE.Vector3();
+
+			cloud = new THREE.TextureLoader().load( 'textures/lava/cloud.png' );
+			cloud.wrapS = cloud.wrapT = THREE.RepeatWrapping;
+
+			controls = new THREE.OrbitControls( camera, renderer.domElement );
+			controls.minDistance = 50;
+			controls.maxDistance = 200;
+
+			scene.add( new THREE.AmbientLight( 0x464646 ) );
+
+			var light = new THREE.DirectionalLight( 0xffddcc, 1 );
+			light.position.set( 1, 0.75, 0.5 );
+			scene.add( light );
+
+			var light = new THREE.DirectionalLight( 0xccccff, 1 );
+			light.position.set( - 1, 0.75, - 0.5 );
+			scene.add( light );
+
+			teapot = new THREE.TeapotBufferGeometry( 15, 18 );
+
+			mesh = new THREE.Mesh( teapot );
+			scene.add( mesh );
+
+			window.addEventListener( 'resize', onWindowResize, false );
+
+			updateMaterial();
+
+			onWindowResize();
+			animate();
+
+		}
+
+		function clearGui() {
+
+			if ( gui ) gui.destroy();
+
+			gui = new dat.GUI();
+
+			var example = gui.add( param, 'load', {
+				'caustic': 'caustic',
+				'displace': 'displace',
+				'wave': 'wave',
+				'xray': 'xray'
+			} ).onFinishChange( function () {
+
+				updateMaterial();
+
+			} );
+
+			gui.open();
+
+		}
+
+		function addGui( name, value, callback, isColor, min, max ) {
+
+			var node;
+
+			param[ name ] = value;
+
+			if ( isColor ) {
+
+				node = gui.addColor( param, name ).onChange( function () {
+
+					callback( param[ name ] );
+
+				} );
+
+			} else if ( typeof value == 'object' ) {
+
+				node = gui.add( param, name, value ).onChange( function () {
+
+					callback( param[ name ] );
+
+				} );
+
+			} else {
+
+				node = gui.add( param, name, min, max ).onChange( function () {
+
+					callback( param[ name ] );
+
+				} );
+
+			}
+
+			return node;
+
+		}
+
+		
+		function updateMaterial() {
+
+			if ( mesh.material ) mesh.material.dispose();
+
+			clearGui();
+
+			var url = "nodes/" + param.load + ".json";
+
+			var library = {
+				"cloud" : cloud
+			};
+
+			var loader = new THREE.NodeMaterialLoader( undefined, library ).load( url, function () {
+
+				var time = loader.getObjectByName("time");
+			
+				if (time) {
+				
+					addGui( 'timeScale', time.scale, function ( val ) {
+
+						time.scale = val;
+
+					}, false, -2, 2 );
+				
+				}
+			
+				// set material
+				mesh.material = loader.material;
+
+			} );
+
+		}
+
+		function onWindowResize() {
+
+			var width = window.innerWidth, height = window.innerHeight;
+
+			camera.aspect = width / height;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize( width, height );
+
+		}
+
+		function animate() {
+
+			var delta = clock.getDelta();
+
+			// update material animation and/or gpu calcs (pre-renderer)
+			if ( mesh.material instanceof THREE.NodeMaterial ) mesh.material.updateFrame( delta );
+
+			renderer.render( scene, camera );
+
+			requestAnimationFrame( animate );
+
+		}
+
+		</script>
+	</body>
+</html>

+ 131 - 49
examples/webgl_materials_nodes.html

@@ -30,7 +30,8 @@
 
 		<div id="container"></div>
 		<div id="info">
-			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - Node-Based Material
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - Node-Based Material</br>
+			<a id="serialize" onclick="toggleSerialize()" href="javascript:void(0);">Serialize and apply</a>
 		</div>
 
 		<script src="../build/three.js"></script>
@@ -102,6 +103,9 @@
 		<script src="js/nodes/materials/StandardNode.js"></script>
 		<script src="js/nodes/materials/StandardNodeMaterial.js"></script>
 
+		<!-- NodeMaterial Loader -->
+		<script src="js/loaders/NodeMaterialLoader.js"></script>
+
 		<script>
 
 		var container = document.getElementById( 'container' );
@@ -112,6 +116,8 @@
 		var move = false;
 		var rtTexture, rtMaterial;
 		var gui, guiElements = [];
+		var library = {};
+		var serialized = false;
 		var textures = {
 			brick: { url: 'textures/brick_diffuse.jpg' },
 			grass: { url: 'textures/terrain/grasslight-big.jpg' },
@@ -132,6 +138,8 @@
 				texture = textures[ name ].texture = new THREE.TextureLoader().load( textures[ name ].url );
 				texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
 
+				library[ texture.uuid ] = texture;
+
 			}
 
 			return texture;
@@ -151,6 +159,8 @@
 			var textureCube = new THREE.CubeTextureLoader().load( urls );
 			textureCube.format = THREE.RGBFormat;
 
+			library[ textureCube.uuid ] = textureCube;
+
 			return textureCube;
 
 		}();
@@ -162,6 +172,7 @@
 			renderer = new THREE.WebGLRenderer( { antialias: true } );
 			renderer.setPixelRatio( window.devicePixelRatio );
 			renderer.setSize( window.innerWidth, window.innerHeight );
+			renderer.uuid = THREE.Math.generateUUID(); // generate to library
 			container.appendChild( renderer.domElement );
 
 			scene = new THREE.Scene();
@@ -172,6 +183,7 @@
 			camera.position.y = 30;
 			camera.target = new THREE.Vector3();
 
+
 			controls = new THREE.OrbitControls( camera, renderer.domElement );
 			controls.minDistance = 50;
 			controls.maxDistance = 200;
@@ -191,6 +203,10 @@
 			mesh = new THREE.Mesh( teapot );
 			scene.add( mesh );
 
+			library[ renderer.uuid ] = renderer;
+			library[ camera.uuid ] = camera;
+			library[ mesh.uuid ] = mesh;
+
 			window.addEventListener( 'resize', onWindowResize, false );
 
 			updateMaterial();
@@ -292,6 +308,8 @@
 
 			if ( rtTexture ) {
 
+				delete library[ rtTexture.uuid ];
+
 				rtTexture.dispose();
 				rtTexture = null;
 
@@ -412,7 +430,7 @@
 
 					addGui( 'roughnessA', roughnessA.number, function ( val ) {
 
-						 roughnessA.number = val;
+						roughnessA.number = val;
 
 					}, false, 0, 1 );
 
@@ -424,7 +442,7 @@
 
 					addGui( 'roughnessB', roughnessB.number, function ( val ) {
 
-						 roughnessB.number = val;
+						roughnessB.number = val;
 
 					}, false, 0, 1 );
 
@@ -444,26 +462,26 @@
 
 				case 'physical':
 
-						// MATERIAL
+					// MATERIAL
 
 					mtl = new THREE.StandardNodeMaterial();
 
-						//mtl.color = // albedo (vec3)
-						//mtl.alpha = // opacity (float)
-						//mtl.roughness = // roughness (float)
-						//mtl.metalness = // metalness (float)
-						//mtl.reflectivity = // reflectivity (float)
-						//mtl.clearCoat = // clearCoat (float)
-						//mtl.clearCoatRoughness = // clearCoatRoughness (float)
-						//mtl.normal = // normalmap (vec3)
-						//mtl.normalScale = // normalmap scale (vec2)
-						//mtl.emissive = // emissive color (vec3)
-						//mtl.ambient = // ambient color (vec3)
-						//mtl.shadow = // shadowmap (vec3)
-						//mtl.light = // custom-light (vec3)
-						//mtl.ao = // ambient occlusion (float)
-						//mtl.environment = // reflection/refraction (vec3)
-						//mtl.transform = // vertex transformation (vec3)
+					//mtl.color = // albedo (vec3)
+					//mtl.alpha = // opacity (float)
+					//mtl.roughness = // roughness (float)
+					//mtl.metalness = // metalness (float)
+					//mtl.reflectivity = // reflectivity (float)
+					//mtl.clearCoat = // clearCoat (float)
+					//mtl.clearCoatRoughness = // clearCoatRoughness (float)
+					//mtl.normal = // normalmap (vec3)
+					//mtl.normalScale = // normalmap scale (vec2)
+					//mtl.emissive = // emissive color (vec3)
+					//mtl.ambient = // ambient color (vec3)
+					//mtl.shadow = // shadowmap (vec3)
+					//mtl.light = // custom-light (vec3)
+					//mtl.ao = // ambient occlusion (float)
+					//mtl.environment = // reflection/refraction (vec3)
+					//mtl.transform = // vertex transformation (vec3)
 
 					var mask = new THREE.SwitchNode( new THREE.TextureNode( getTexture( "decalDiffuse" ) ), 'w' );
 
@@ -480,24 +498,24 @@
 					var clearCoatRoughness = new THREE.FloatNode( 1 );
 
 					var roughness = new THREE.Math3Node(
-							roughnessA,
-							roughnessB,
-							mask,
-							THREE.Math3Node.MIX
-						);
+						roughnessA,
+						roughnessB,
+						mask,
+						THREE.Math3Node.MIX
+					);
 
 					var metalness = new THREE.Math3Node(
-							metalnessA,
-							metalnessB,
-							mask,
-							THREE.Math3Node.MIX
-						);
+						metalnessA,
+						metalnessB,
+						mask,
+						THREE.Math3Node.MIX
+					);
 
 					var normalMask = new THREE.OperatorNode(
-							new THREE.Math1Node( mask, THREE.Math1Node.INVERT ),
-							normalScale,
-							THREE.OperatorNode.MUL
-						);
+						new THREE.Math1Node( mask, THREE.Math1Node.INVERT ),
+						normalScale,
+						THREE.OperatorNode.MUL
+					);
 
 					mtl.color = new THREE.ColorNode( 0xEEEEEE );
 					mtl.roughness = roughness;
@@ -509,7 +527,7 @@
 					mtl.normal = new THREE.TextureNode( getTexture( "grassNormal" ) );
 					mtl.normalScale = normalMask;
 
-						// GUI
+					// GUI
 
 					addGui( 'color', mtl.color.value.getHex(), function ( val ) {
 
@@ -519,25 +537,25 @@
 
 					addGui( 'reflectivity', reflectivity.number, function ( val ) {
 
-							 reflectivity.number = val;
+						reflectivity.number = val;
 
 					}, false, 0, 1 );
 
 					addGui( 'clearCoat', clearCoat.number, function ( val ) {
 
-							 clearCoat.number = val;
+						clearCoat.number = val;
 
 					}, false, 0, 1 );
 
 					addGui( 'clearCoatRoughness', clearCoatRoughness.number, function ( val ) {
 
-							 clearCoatRoughness.number = val;
+						clearCoatRoughness.number = val;
 
 					}, false, 0, 1 );
 
 					addGui( 'roughnessA', roughnessA.number, function ( val ) {
 
-							 roughnessA.number = val;
+						roughnessA.number = val;
 
 					}, false, 0, 1 );
 
@@ -549,7 +567,7 @@
 
 					addGui( 'roughnessB', roughnessB.number, function ( val ) {
 
-							 roughnessB.number = val;
+						roughnessB.number = val;
 
 					}, false, 0, 1 );
 
@@ -580,6 +598,10 @@
 					var colorA = new THREE.ColorNode( 0xFFFFFF );
 					var colorB = new THREE.ColorNode( 0x0054df );
 
+					// used for serialization only
+					time.name = "time";
+					speed.name = "speed";
+
 					var timeScale = new THREE.OperatorNode(
 						time,
 						speed,
@@ -1078,7 +1100,7 @@
 
 					var satrgb = new THREE.FunctionNode( [
 						"vec3 satrgb(vec3 rgb, float adjustment) {",
-					//"	const vec3 W = vec3(0.2125, 0.7154, 0.0721);", // LUMA
+						//"	const vec3 W = vec3(0.2125, 0.7154, 0.0721);", // LUMA
 						"	vec3 intensity = vec3(dot(rgb, LUMA));",
 						"	return mix(intensity, rgb, adjustment);",
 						"}"
@@ -1166,6 +1188,10 @@
 					var colorA = new THREE.ColorNode( 0xFFFFFF );
 					var colorB = new THREE.ColorNode( 0x0054df );
 
+					// used for serialization only
+					time.name = "time";
+					speed.name = "speed";
+
 					var uv = new THREE.UVNode();
 
 					var timeScl = new THREE.OperatorNode(
@@ -1357,7 +1383,7 @@
 					].join( "\n" ) );
 
 					var voronoi = new THREE.FunctionNode( [
-					// Based off of iq's described here: http://www.iquilezles.org/www/articles/voronoili
+						// Based off of iq's described here: http://www.iquilezles.org/www/articles/voronoili
 						"float voronoi(vec2 p, in float time) {",
 						"	vec2 n = floor(p);",
 						"	vec2 f = fract(p);",
@@ -1381,7 +1407,7 @@
 					].join( "\n" ), [ hash2 ] ); // define hash2 as dependencies
 
 					var voronoiLayers = new THREE.FunctionNode( [
-					// based on https://www.shadertoy.com/view/4tXSDf
+						// based on https://www.shadertoy.com/view/4tXSDf
 						"float voronoiLayers(vec2 p, in float time) {",
 						"	float v = 0.0;",
 						"	float a = 0.4;",
@@ -1397,6 +1423,10 @@
 					var time = new THREE.TimerNode();
 					var timeScale = new THREE.FloatNode( 2 );
 
+					// used for serialization only
+					time.name = "time";
+					timeScale.name = "speed";
+
 					var alpha = new THREE.FloatNode( 1 );
 					var scale = new THREE.FloatNode( .1 );
 					var intensity = new THREE.FloatNode( 1.5 );
@@ -1928,7 +1958,7 @@
 					mtl = new THREE.PhongNodeMaterial();
 
 					var keywordsexample = new THREE.FunctionNode( [
-					// use "uv" reserved keyword
+						// use "uv" reserved keyword
 						"vec4 keywordsexample( sampler2D texture ) {",
 						"	return texture2D( texture, myUV ) + vec4( position * myAlpha, 0.0 );",
 						"}"
@@ -1967,10 +1997,10 @@
 
 					var setMyVar = new THREE.FunctionNode( [
 						"float setMyVar( vec3 pos ) {",
-					// set "myVar" in vertex shader in this example,
-					// can be used in fragment shader too or in rest of the current shader
+						// set "myVar" in vertex shader in this example,
+						// can be used in fragment shader too or in rest of the current shader
 						"	myVar = pos;",
-					// it is not accept "void" functions for now!
+						// it is not accept "void" functions for now!
 						"	return 0.;",
 						"}"
 					].join( "\n" ) );
@@ -1996,11 +2026,11 @@
 					var alpha = new THREE.FloatNode( 1 );
 
 					var blurtexture = new THREE.FunctionNode( [
-					// Reference: TriangleBlurShader.js
+						// Reference: TriangleBlurShader.js
 						"vec4 blurtexture(sampler2D texture, vec2 uv, vec2 delta) {",
 						"	vec4 color = vec4( 0.0 );",
 						"	float total = 0.0;",
-					// randomize the lookup values to hide the fixed number of samples
+						// randomize the lookup values to hide the fixed number of samples
 						"	float offset = rand( uv );",
 						"	for ( float t = -BLUR_ITERATIONS; t <= BLUR_ITERATIONS; t ++ ) {",
 						"		float percent = ( t + offset - 0.5 ) / BLUR_ITERATIONS;",
@@ -2136,6 +2166,8 @@
 
 					rtTexture = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat } );
 
+					library[ rtTexture.uuid ] = rtTexture;
+
 					var distanceMtl = new THREE.PhongNodeMaterial();
 					distanceMtl.environment = objectDepth;
 					distanceMtl.side = THREE.BackSide;
@@ -2269,6 +2301,56 @@
 
 		}
 
+		function toggleSerialize() {
+
+			if ( serialized ) reset();
+			else serialize();
+
+			serialized = ! serialized;
+
+		}
+
+		function reset() {
+
+			updateMaterial();
+
+			// gui
+
+			var div = document.getElementById( 'serialize' );
+			div.textContent = "Serialize and apply";
+
+		}
+
+		function serialize() {
+
+			var json = mesh.material.toJSON(),
+				loader = new THREE.NodeMaterialLoader( null, library ),
+				material = loader.parse( json );
+
+			mesh.material = material;
+
+			// replace uuid to url (facilitates the load of textures using url otherside uuid)
+			// example:
+
+			THREE.NodeMaterialLoaderUtils.replaceUUID( json, getTexture( "cloud" ), "cloud" );
+
+			// get source
+
+			var jsonStr = JSON.stringify( json );
+
+			console.log( jsonStr );
+
+			// gui
+
+			var div = document.getElementById( 'serialize' );
+			div.textContent = "Click to reset - JSON Generate: " + ( jsonStr.length / 1024 ).toFixed( 3 ) + "kB";
+
+			if ( gui ) gui.destroy();
+
+			gui = null;
+
+		}
+
 		function animate() {
 
 			var delta = clock.getDelta();

+ 206 - 187
examples/webgl_mirror_nodes.html

@@ -96,223 +96,242 @@
 		<script src="js/nodes/materials/PhongNode.js"></script>
 		<script src="js/nodes/materials/PhongNodeMaterial.js"></script>
 
+		<!-- NodeMaterial Loader -->
+		<script src="js/loaders/NodeMaterialLoader.js"></script>
+
 		<script>
 
-			// scene size
-			var WIDTH = window.innerWidth;
-			var HEIGHT = window.innerHeight;
+		// scene size
+		var WIDTH = window.innerWidth;
+		var HEIGHT = window.innerHeight;
+
+		// camera
+		var VIEW_ANGLE = 45;
+		var ASPECT = WIDTH / HEIGHT;
+		var NEAR = 1;
+		var FAR = 500;
+
+		var decalNormal = new THREE.TextureLoader().load( 'textures/decal/decal-normal.jpg' );
+
+		var decalDiffuse = new THREE.TextureLoader().load( 'textures/decal/decal-diffuse.png' );
+		decalDiffuse.wrapS = decalDiffuse.wrapT = THREE.RepeatWrapping;
+
+		var camera, scene, renderer;
+		var clock = new THREE.Clock();
+
+		var cameraControls;
+
+		var gui = new dat.GUI();
+
+		var sphereGroup, smallSphere;
+		var groundMirrorMaterial;
+
+		function init() {
+
+			// renderer
+			renderer = new THREE.WebGLRenderer();
+			renderer.setPixelRatio( window.devicePixelRatio );
+			renderer.setSize( WIDTH, HEIGHT );
+
+			// scene
+			scene = new THREE.Scene();
 
 			// camera
-			var VIEW_ANGLE = 45;
-			var ASPECT = WIDTH / HEIGHT;
-			var NEAR = 1;
-			var FAR = 500;
+			camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR );
+			camera.position.set( 0, 75, 160 );
+
+			cameraControls = new THREE.OrbitControls( camera, renderer.domElement );
+			cameraControls.target.set( 0, 40, 0 );
+			cameraControls.maxDistance = 400;
+			cameraControls.minDistance = 10;
+			cameraControls.update();
+
+			var container = document.getElementById( 'container' );
+			container.appendChild( renderer.domElement );
+
+		}
+
+		function fillScene() {
+
+			var planeGeo = new THREE.PlaneBufferGeometry( 100.1, 100.1 );
+
+			// reflector/mirror plane
+			var geometry = new THREE.PlaneBufferGeometry( 100, 100 );
+			var groundMirror = new THREE.ReflectorRTT( geometry, { clipBias: 0.003, textureWidth: WIDTH, textureHeight: HEIGHT } );
+
+			var mask = new THREE.SwitchNode( new THREE.TextureNode( decalDiffuse ), 'w' );
+			var maskFlip = new THREE.Math1Node( mask, THREE.Math1Node.INVERT );
+
+			var mirror = new THREE.ReflectorNode( groundMirror );
+
+			var normal = new THREE.TextureNode( decalNormal );
+			var normalXY = new THREE.SwitchNode( normal, 'xy' );
+			var normalXYFlip = new THREE.Math1Node(
+				normalXY,
+				THREE.Math1Node.INVERT
+			);
+
+			var offsetNormal = new THREE.OperatorNode(
+				normalXYFlip,
+				new THREE.FloatNode( .5 ),
+				THREE.OperatorNode.SUB
+			);
+
+			mirror.offset = new THREE.OperatorNode(
+				offsetNormal, // normal
+				new THREE.FloatNode( 6 ), // scale
+				THREE.OperatorNode.MUL
+			);
+
+			var clr = new THREE.Math3Node(
+				mirror,
+				new THREE.ColorNode( 0xFFFFFF ),
+				mask,
+				THREE.Math3Node.MIX
+			);
+
+			var blurMirror = new THREE.BlurNode( mirror );
+			blurMirror.size = new THREE.Vector2( WIDTH, HEIGHT );
+			blurMirror.coord = new THREE.FunctionNode( "projCoord.xyz / projCoord.q", "vec3" );
+			blurMirror.coord.keywords[ "projCoord" ] = new THREE.OperatorNode( mirror.offset, mirror.coord, THREE.OperatorNode.ADD );
+			blurMirror.radius.x = blurMirror.radius.y = 0;
+
+			gui.add( { blur: blurMirror.radius.x }, "blur", 0, 25 ).onChange( function ( v ) {
+
+				blurMirror.radius.x = blurMirror.radius.y = v;
+
+			} );
+
+			groundMirrorMaterial = new THREE.PhongNodeMaterial();
+			groundMirrorMaterial.environment = blurMirror; // or add "mirror" variable to disable blur
+			groundMirrorMaterial.environmentAlpha = mask;
+			groundMirrorMaterial.normal = normal;
+			//groundMirrorMaterial.normalScale = new THREE.FloatNode( 1 );
+			groundMirrorMaterial.build();
+
+			// test serialization
+/*
+			var library = {};
+			library[ groundMirror.uuid ] = groundMirror;
+			library[ decalDiffuse.uuid ] = decalDiffuse;
+			library[ decalNormal.uuid ] = decalNormal;
+			library[ mirror.textureMatrix.uuid ] = mirror.textureMatrix; // use textureMatrix to projection
+
+			var json = groundMirrorMaterial.toJSON();
+
+			groundMirrorMaterial = new THREE.NodeMaterialLoader( null, library ).parse( json );
+*/
+			//--
+
+			var mirrorMesh = new THREE.Mesh( planeGeo, groundMirrorMaterial );
+			mirrorMesh.add( groundMirror );
+			mirrorMesh.rotateX( - Math.PI / 2 );
+			scene.add( mirrorMesh );
 
-			var decalNormal = new THREE.TextureLoader().load( 'textures/decal/decal-normal.jpg' );
+			sphereGroup = new THREE.Object3D();
+			scene.add( sphereGroup );
 
-			var decalDiffuse = new THREE.TextureLoader().load( 'textures/decal/decal-diffuse.png' );
-			decalDiffuse.wrapS = decalDiffuse.wrapT = THREE.RepeatWrapping;
+			var geometry = new THREE.CylinderGeometry( 0.1, 15 * Math.cos( Math.PI / 180 * 30 ), 0.1, 24, 1 );
+			var material = new THREE.MeshPhongMaterial( { color: 0xffffff, emissive: 0x444444 } );
+			var sphereCap = new THREE.Mesh( geometry, material );
+			sphereCap.position.y = - 15 * Math.sin( Math.PI / 180 * 30 ) - 0.05;
+			sphereCap.rotateX( - Math.PI );
 
-			var camera, scene, renderer;
-			var clock = new THREE.Clock();
+			var geometry = new THREE.SphereGeometry( 15, 24, 24, Math.PI / 2, Math.PI * 2, 0, Math.PI / 180 * 120 );
+			var halfSphere = new THREE.Mesh( geometry, material );
+			halfSphere.add( sphereCap );
+			halfSphere.rotateX( - Math.PI / 180 * 135 );
+			halfSphere.rotateZ( - Math.PI / 180 * 20 );
+			halfSphere.position.y = 7.5 + 15 * Math.sin( Math.PI / 180 * 30 );
 
-			var cameraControls;
+			sphereGroup.add( halfSphere );
 
-			var gui = new dat.GUI();
+			var geometry = new THREE.IcosahedronGeometry( 5, 0 );
+			var material = new THREE.MeshPhongMaterial( { color: 0xffffff, emissive: 0x333333, flatShading: true } );
+			smallSphere = new THREE.Mesh( geometry, material );
+			scene.add( smallSphere );
 
-			var sphereGroup, smallSphere;
-			var groundMirrorMaterial;
+			// walls
+			var planeTop = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );
+			planeTop.position.y = 100;
+			planeTop.rotateX( Math.PI / 2 );
+			scene.add( planeTop );
+
+			var planeBack = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );
+			planeBack.position.z = - 50;
+			planeBack.position.y = 50;
+			scene.add( planeBack );
 
-			function init() {
+			var planeFront = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0x7f7fff } ) );
+			planeFront.position.z = 50;
+			planeFront.position.y = 50;
+			planeFront.rotateY( Math.PI );
+			scene.add( planeFront );
+
+			var planeRight = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0x00ff00 } ) );
+			planeRight.position.x = 50;
+			planeRight.position.y = 50;
+			planeRight.rotateY( - Math.PI / 2 );
+			scene.add( planeRight );
 
-				// renderer
-				renderer = new THREE.WebGLRenderer();
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( WIDTH, HEIGHT );
+			var planeLeft = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xff0000 } ) );
+			planeLeft.position.x = - 50;
+			planeLeft.position.y = 50;
+			planeLeft.rotateY( Math.PI / 2 );
+			scene.add( planeLeft );
 
-				// scene
-				scene = new THREE.Scene();
+			// lights
+			var mainLight = new THREE.PointLight( 0xcccccc, 1.5, 250 );
+			mainLight.position.y = 60;
+			scene.add( mainLight );
 
-				// camera
-				camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
-				camera.position.set( 0, 75, 160 );
+			var greenLight = new THREE.PointLight( 0x00ff00, 0.25, 1000 );
+			greenLight.position.set( 550, 50, 0 );
+			scene.add( greenLight );
 
-				cameraControls = new THREE.OrbitControls(camera, renderer.domElement);
-				cameraControls.target.set( 0, 40, 0);
-				cameraControls.maxDistance = 400;
-				cameraControls.minDistance = 10;
-				cameraControls.update();
+			var redLight = new THREE.PointLight( 0xff0000, 0.25, 1000 );
+			redLight.position.set( - 550, 50, 0 );
+			scene.add( redLight );
 
-				var container = document.getElementById( 'container' );
-				container.appendChild( renderer.domElement );
+			var blueLight = new THREE.PointLight( 0x7f7fff, 0.25, 1000 );
+			blueLight.position.set( 0, 50, 550 );
+			scene.add( blueLight );
 
-			}
+		}
 
-			function fillScene() {
-
-				var planeGeo = new THREE.PlaneBufferGeometry( 100.1, 100.1 );
-
-				// reflector/mirror plane
-				var geometry = new THREE.PlaneBufferGeometry( 100, 100 );
-				var groundMirror = new THREE.ReflectorRTT( geometry, { clipBias: 0.003, textureWidth: WIDTH, textureHeight: HEIGHT } );
-
-				var mask = new THREE.SwitchNode( new THREE.TextureNode( decalDiffuse ), 'w' );
-				var maskFlip = new THREE.Math1Node( mask, THREE.Math1Node.INVERT );
-
-				var mirror = new THREE.ReflectorNode( groundMirror );
-
-				var normal = new THREE.TextureNode( decalNormal );
-				var normalXY = new THREE.SwitchNode( normal, 'xy' );
-				var normalXYFlip = new THREE.Math1Node(
-					normalXY,
-					THREE.Math1Node.INVERT
-				);
-
-				var offsetNormal = new THREE.OperatorNode(
-					normalXYFlip,
-					new THREE.FloatNode( .5 ),
-					THREE.OperatorNode.SUB
-				);
-
-				mirror.offset = new THREE.OperatorNode(
-					offsetNormal, // normal
-					new THREE.FloatNode( 6 ),// scale
-					THREE.OperatorNode.MUL
-				);
-
-				var clr = new THREE.Math3Node(
-					mirror,
-					new THREE.ColorNode( 0xFFFFFF ),
-					mask,
-					THREE.Math3Node.MIX
-				);
-
-				var blurMirror = new THREE.BlurNode( mirror );
-				blurMirror.size = new THREE.Vector2( WIDTH, HEIGHT );
-				blurMirror.coord = new THREE.FunctionNode( "projCoord.xyz / projCoord.q", "vec3" );
-				blurMirror.coord.keywords[ "projCoord" ] = new THREE.OperatorNode( mirror.offset, mirror.coord, THREE.OperatorNode.ADD );
-				blurMirror.radius.x = blurMirror.radius.y = 0;
-
-				gui.add( { blur : blurMirror.radius.x }, "blur", 0, 25 ).onChange( function(v) {
-
-					blurMirror.radius.x = blurMirror.radius.y = v;
-
-				} );
-
-				groundMirrorMaterial = new THREE.PhongNodeMaterial();
-				groundMirrorMaterial.environment = blurMirror; // or add "mirror" variable to disable blur
-				groundMirrorMaterial.environmentAlpha = mask;
-				groundMirrorMaterial.normal = normal;
-				//groundMirrorMaterial.normalScale = new THREE.FloatNode( 1 );
-				groundMirrorMaterial.build();
-
-				var mirrorMesh = new THREE.Mesh( planeGeo, groundMirrorMaterial );
-				mirrorMesh.add( groundMirror );
-				mirrorMesh.rotateX( - Math.PI / 2 );
-				scene.add( mirrorMesh );
-
-				sphereGroup = new THREE.Object3D();
-				scene.add( sphereGroup );
-
-				var geometry = new THREE.CylinderGeometry( 0.1, 15 * Math.cos( Math.PI / 180 * 30 ), 0.1, 24, 1 );
-				var material = new THREE.MeshPhongMaterial( { color: 0xffffff, emissive: 0x444444 } );
-				var sphereCap = new THREE.Mesh( geometry, material );
-				sphereCap.position.y = -15 * Math.sin( Math.PI / 180 * 30 ) - 0.05;
-				sphereCap.rotateX(-Math.PI);
-
-				var geometry = new THREE.SphereGeometry( 15, 24, 24, Math.PI / 2, Math.PI * 2, 0, Math.PI / 180 * 120 );
-				var halfSphere = new THREE.Mesh( geometry, material );
-				halfSphere.add( sphereCap );
-				halfSphere.rotateX( - Math.PI / 180 * 135 );
-				halfSphere.rotateZ( - Math.PI / 180 * 20 );
-				halfSphere.position.y = 7.5 + 15 * Math.sin( Math.PI / 180 * 30 );
-
-				sphereGroup.add( halfSphere );
-
-				var geometry = new THREE.IcosahedronGeometry( 5, 0 );
-				var material = new THREE.MeshPhongMaterial( { color: 0xffffff, emissive: 0x333333, flatShading: true } );
-				smallSphere = new THREE.Mesh( geometry, material );
-				scene.add(smallSphere);
-
-				// walls
-				var planeTop = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );
-				planeTop.position.y = 100;
-				planeTop.rotateX( Math.PI / 2 );
-				scene.add( planeTop );
-
-				var planeBack = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );
-				planeBack.position.z = -50;
-				planeBack.position.y = 50;
-				scene.add( planeBack );
-
-				var planeFront = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0x7f7fff } ) );
-				planeFront.position.z = 50;
-				planeFront.position.y = 50;
-				planeFront.rotateY( Math.PI );
-				scene.add( planeFront );
-
-				var planeRight = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0x00ff00 } ) );
-				planeRight.position.x = 50;
-				planeRight.position.y = 50;
-				planeRight.rotateY( - Math.PI / 2 );
-				scene.add( planeRight );
-
-				var planeLeft = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xff0000 } ) );
-				planeLeft.position.x = -50;
-				planeLeft.position.y = 50;
-				planeLeft.rotateY( Math.PI / 2 );
-				scene.add( planeLeft );
-
-				// lights
-				var mainLight = new THREE.PointLight( 0xcccccc, 1.5, 250 );
-				mainLight.position.y = 60;
-				scene.add( mainLight );
-
-				var greenLight = new THREE.PointLight( 0x00ff00, 0.25, 1000 );
-				greenLight.position.set( 550, 50, 0 );
-				scene.add( greenLight );
-
-				var redLight = new THREE.PointLight( 0xff0000, 0.25, 1000 );
-				redLight.position.set( - 550, 50, 0 );
-				scene.add( redLight );
-
-				var blueLight = new THREE.PointLight( 0x7f7fff, 0.25, 1000 );
-				blueLight.position.set( 0, 50, 550 );
-				scene.add( blueLight );
+		function render() {
 
-			}
+			renderer.render( scene, camera );
 
-			function render() {
+		}
 
-				renderer.render(scene, camera);
+		function update() {
 
-			}
+			requestAnimationFrame( update );
 
-			function update() {
+			var delta = clock.getDelta();
+			var timer = Date.now() * 0.01;
 
-				requestAnimationFrame( update );
+			sphereGroup.rotation.y -= 0.002;
 
-				var delta = clock.getDelta();
-				var timer = Date.now() * 0.01;
+			smallSphere.position.set(
+				Math.cos( timer * 0.1 ) * 30,
+				Math.abs( Math.cos( timer * 0.2 ) ) * 20 + 5,
+				Math.sin( timer * 0.1 ) * 30
+			);
+			smallSphere.rotation.y = ( Math.PI / 2 ) - timer * 0.1;
+			smallSphere.rotation.z = timer * 0.8;
 
-				sphereGroup.rotation.y -= 0.002;
+			groundMirrorMaterial.updateFrame( delta );
 
-				smallSphere.position.set(
-					Math.cos( timer * 0.1 ) * 30,
-					Math.abs( Math.cos( timer * 0.2 ) ) * 20 + 5,
-					Math.sin( timer * 0.1 ) * 30
-				);
-				smallSphere.rotation.y = ( Math.PI / 2 ) - timer * 0.1;
-				smallSphere.rotation.z = timer * 0.8;
+			render();
 
-				groundMirrorMaterial.updateFrame( delta );
+		}
 
-				render();
-			}
+		init();
+		fillScene();
+		update();
 
-			init();
-			fillScene();
-			update();
 
 		</script>
 	</body>

+ 44 - 30
examples/webgl_postprocessing_nodes.html

@@ -86,6 +86,9 @@
 		<!-- Post-Processing -->
 		<script src="js/nodes/postprocessing/NodePass.js"></script>
 
+		<!-- NodeMaterial Loader -->
+		<script src="js/loaders/NodeMaterialLoader.js"></script>
+
 		<script>
 
 			var camera, scene, renderer, composer;
@@ -121,7 +124,7 @@
 					'adv / saturation': 'saturation',
 					'adv / refraction': 'refraction',
 					'adv / mosaic': 'mosaic'
-				} ).onFinishChange( function() {
+				} ).onFinishChange( function () {
 
 					updateMaterial();
 
@@ -139,7 +142,7 @@
 
 				if ( isColor ) {
 
-					node = gui.addColor( param, name ).onChange( function() {
+					node = gui.addColor( param, name ).onChange( function () {
 
 						callback( param[ name ] );
 
@@ -147,7 +150,7 @@
 
 				} else if ( typeof value == 'object' ) {
 
-					node = gui.add( param, name, value ).onChange( function() {
+					node = gui.add( param, name, value ).onChange( function () {
 
 						callback( param[ name ] );
 
@@ -155,7 +158,7 @@
 
 				} else {
 
-					node = gui.add( param, name, min, max ).onChange( function() {
+					node = gui.add( param, name, min, max ).onChange( function () {
 
 						callback( param[ name ] );
 
@@ -195,31 +198,31 @@
 
 						// GUI
 
-						addGui( 'hue', hue.number, function( val ) {
+						addGui( 'hue', hue.number, function ( val ) {
 
 							hue.number = val;
 
 						}, false, 0, Math.PI * 2 );
 
-						addGui( 'saturation', sataturation.number, function( val ) {
+						addGui( 'saturation', sataturation.number, function ( val ) {
 
 							sataturation.number = val;
 
 						}, false, 0, 2 );
 
-						addGui( 'vibrance', vibrance.number, function( val ) {
+						addGui( 'vibrance', vibrance.number, function ( val ) {
 
 							vibrance.number = val;
 
 						}, false, - 1, 1 );
 
-						addGui( 'brightness', brightness.number, function( val ) {
+						addGui( 'brightness', brightness.number, function ( val ) {
 
 							brightness.number = val;
 
 						}, false, 0, .5 );
 
-						addGui( 'contrast', contrast.number, function( val ) {
+						addGui( 'contrast', contrast.number, function ( val ) {
 
 							contrast.number = val;
 
@@ -245,13 +248,13 @@
 
 						// GUI
 
-						addGui( 'color', color.value.getHex(), function( val ) {
+						addGui( 'color', color.value.getHex(), function ( val ) {
 
 							color.value.setHex( val );
 
 						}, true );
 
-						addGui( 'fade', percent.number, function( val ) {
+						addGui( 'fade', percent.number, function ( val ) {
 
 							percent.number = val;
 
@@ -279,7 +282,7 @@
 
 						// GUI
 
-						addGui( 'alpha', alpha.number, function( val ) {
+						addGui( 'alpha', alpha.number, function ( val ) {
 
 							alpha.number = val;
 
@@ -302,11 +305,11 @@
 						// GUI
 
 						addGui( 'blend', {
-							'addition' : THREE.OperatorNode.ADD,
-							'subtract' : THREE.OperatorNode.SUB,
-							'multiply' : THREE.OperatorNode.MUL,
-							'division' : THREE.OperatorNode.DIV
-						}, function( val ) {
+							'addition': THREE.OperatorNode.ADD,
+							'subtract': THREE.OperatorNode.SUB,
+							'multiply': THREE.OperatorNode.MUL,
+							'division': THREE.OperatorNode.DIV
+						}, function ( val ) {
 
 							multiply.op = val;
 
@@ -324,11 +327,11 @@
 						var sat = new THREE.FloatNode( 0 );
 
 						var satrgb = new THREE.FunctionNode( [
-						"vec3 satrgb(vec3 rgb, float adjustment) {",
-						//"	const vec3 W = vec3(0.2125, 0.7154, 0.0721);", // LUMA
-						"	vec3 intensity = vec3(dot(rgb, LUMA));",
-						"	return mix(intensity, rgb, adjustment);",
-						"}"
+							"vec3 satrgb(vec3 rgb, float adjustment) {",
+							//"	const vec3 W = vec3(0.2125, 0.7154, 0.0721);", // LUMA
+							"	vec3 intensity = vec3(dot(rgb, LUMA));",
+							"	return mix(intensity, rgb, adjustment);",
+							"}"
 						].join( "\n" ) );
 
 						var saturation = new THREE.FunctionCallNode( satrgb );
@@ -339,7 +342,7 @@
 
 						// GUI
 
-						addGui( 'saturation', sat.number, function( val ) {
+						addGui( 'saturation', sat.number, function ( val ) {
 
 							sat.number = val;
 
@@ -392,13 +395,13 @@
 
 						// GUI
 
-						addGui( 'scale', scale.number, function( val ) {
+						addGui( 'scale', scale.number, function ( val ) {
 
 							scale.number = val;
 
 						}, false, 0, 1 );
 
-						addGui( 'invert', false, function( val ) {
+						addGui( 'invert', false, function ( val ) {
 
 							offsetNormal.a = val ? normalXYFlip : normalXY;
 
@@ -444,19 +447,19 @@
 
 						// GUI
 
-						addGui( 'scale', scale.number, function( val ) {
+						addGui( 'scale', scale.number, function ( val ) {
 
 							scale.number = val;
 
 						}, false, 16, 1024 );
 
-						addGui( 'fade', fade.number, function( val ) {
+						addGui( 'fade', fade.number, function ( val ) {
 
 							fade.number = val;
 
 						}, false, 0, 1 );
 
-						addGui( 'mask', true, function( val ) {
+						addGui( 'mask', true, function ( val ) {
 
 							fadeCoord.c = val ? maskAlpha : fade;
 
@@ -479,13 +482,13 @@
 
 						// GUI
 
-						addGui( 'blurX', blurScreen.radius.x, function( val ) {
+						addGui( 'blurX', blurScreen.radius.x, function ( val ) {
 
 							blurScreen.radius.x = val;
 
 						}, false, 0, 15 );
 
-						addGui( 'blurY', blurScreen.radius.y, function( val ) {
+						addGui( 'blurY', blurScreen.radius.y, function ( val ) {
 
 							blurScreen.radius.y = val;
 
@@ -497,6 +500,17 @@
 
 				nodepass.build();
 
+				// test serialization
+/*
+				var library = {};
+				library[ lensflare2.uuid ] = lensflare2;
+				library[ decalNormal.uuid ] = decalNormal;
+
+				var json = nodepass.toJSON();
+
+				nodepass.value = new THREE.NodeMaterialLoader( null, library ).parse( json ).value;
+				nodepass.build();
+*/
 			}
 
 			function init() {

+ 41 - 4
examples/webgl_sprites_nodes.html

@@ -97,13 +97,18 @@
 		<!-- Sprite Material -->
 		<script src="js/nodes/materials/SpriteNode.js"></script>
 		<script src="js/nodes/materials/SpriteNodeMaterial.js"></script>
-		
+
+		<!-- NodeMaterial Loader -->
+		<script src="js/loaders/NodeMaterialLoader.js"></script>
+
 		<script>
 
 		var container = document.getElementById( 'container' );
 
 		var renderer, scene, camera, clock = new THREE.Clock(), fov = 50;
 		var plane, sprite1, sprite2, sprite3;
+		var walkingManTexture, walkingManTextureURL;
+		var library = {};
 		var controls;
 
 		window.addEventListener( 'load', init );
@@ -138,9 +143,13 @@
 			plane = new THREE.PlaneBufferGeometry( 1, 1 );
 
 			// https://openclipart.org/detail/239883/walking-man-sprite-sheet
-			var walkingManTexture = new THREE.TextureLoader().load( "textures/WalkingManSpriteSheet.png" );
+			walkingManTextureURL = "textures/WalkingManSpriteSheet.png";
+
+			walkingManTexture = new THREE.TextureLoader().load( walkingManTextureURL );
 			walkingManTexture.wrapS = walkingManTexture.wrapT = THREE.RepeatWrapping;
 
+			library[ walkingManTextureURL ] = walkingManTexture;
+
 			// horizontal sprite-sheet animator
 
 			function createHorizontalSpriteSheetNode( hCount, speed ) {
@@ -203,7 +212,7 @@
 			sprite2.material.spherical = false; // look at camera horizontally only, very used to vegetation
 			// horizontal zigzag sprite
 			sprite2.material.transform = new THREE.OperatorNode(
-				new THREE.OperatorNode( 
+				new THREE.OperatorNode(
 					new THREE.Math1Node( new THREE.TimerNode( 0, 3 ), THREE.Math1Node.SIN ), // 3 is speed (time scale)
 					new THREE.Vector2Node( .3, 0 ), // horizontal scale (position)
 					THREE.OperatorNode.MUL
@@ -227,14 +236,22 @@
 			sprite3.position.x = - 30;
 			sprite3.scale.x = spriteWidth;
 			sprite3.scale.y = spriteHeight;
-			sprite3.material.fog = true;
 			sprite3.material.color = new THREE.TextureNode( walkingManTexture );
 			sprite3.material.color.coord = new THREE.FunctionCallNode( sineWaveFunction, {
 				"uv": createHorizontalSpriteSheetNode( 8, 0 ),
 				"phase": new THREE.TimerNode()
 			} );
+			sprite3.material.fog = true;
 			sprite3.material.build();
 
+			//
+			//	Test serialization
+			//
+
+			spriteToJSON( sprite1 );
+			spriteToJSON( sprite2 );
+			spriteToJSON( sprite3 );
+
 			//
 			// Events
 			//
@@ -246,6 +263,26 @@
 
 		}
 
+		function spriteToJSON( sprite ) {
+
+			// serialize
+
+			var json = sprite.material.toJSON();
+
+			// replace uuid to url (facilitates the load of textures using url otherside uuid)
+
+			THREE.NodeMaterialLoaderUtils.replaceUUID( json, walkingManTexture, walkingManTextureURL );
+
+			// unserialize
+
+			var material = new THREE.NodeMaterialLoader( null, library ).parse( json );
+
+			// replace material
+
+			sprite.material = material;
+
+		}
+
 		function onWindowResize() {
 
 			var width = window.innerWidth, height = window.innerHeight;

Vissa filer visades inte eftersom för många filer har ändrats