2
0
Эх сурвалжийг харах

Raytracing Renderer using Web Workers Sandbox

Joshua Koo 9 жил өмнө
parent
commit
cb98a43888

+ 8 - 2
examples/js/renderers/RaytracingRenderer.js

@@ -38,6 +38,7 @@ THREE.RaytracingRenderer = function ( parameters ) {
 	var objects;
 	var lights = [];
 	var cache = {};
+	var timeRendering = 0;
 
 	var animationFrameId = null;
 
@@ -461,7 +462,8 @@ THREE.RaytracingRenderer = function ( parameters ) {
 				blockY += blockSize;
 
 				if ( blockY >= canvasHeight ) {
-
+					console.log('Total Renderering time', timeRendering / 1000, 's');
+					console.log('Absolute time', (Date.now() - reallyThen) / 1000, 's');
 					scope.dispatchEvent( { type: "complete" } );
 					return;
 
@@ -472,8 +474,11 @@ THREE.RaytracingRenderer = function ( parameters ) {
 			context.fillRect( blockX, blockY, blockSize, blockSize );
 
 			animationFrameId = requestAnimationFrame( function () {
-
+				console.time('render')
+				var then = Date.now();
 				renderBlock( blockX, blockY );
+				timeRendering += Date.now() - then;
+				console.timeEnd('render')
 
 			} );
 
@@ -482,6 +487,7 @@ THREE.RaytracingRenderer = function ( parameters ) {
 	}() );
 
 	this.render = function ( scene, camera ) {
+		reallyThen = Date.now()
 
 		if ( this.autoClear === true ) this.clear();
 

+ 578 - 0
examples/js/renderers/RaytracingWorker.js

@@ -0,0 +1,578 @@
+var workers, worker;
+var BLOCK = 128;
+var startX, startY, division, completed = 0;
+
+self.onmessage = function(e) {
+	var data = e.data;
+	if (!data) return;
+
+	if (data.init) {
+		console.log('init')
+		var
+			width = data.init[0],
+			height = data.init[1];
+
+		initScene(width, height);
+		worker = data.worker;
+		workers = data.workers;
+		BLOCK = data.blockSize;
+
+		if (data.maxRecursionDepth) maxRecursionDepth = data.maxRecursionDepth;
+
+		var xblocks = Math.ceil(width / BLOCK);
+		var yblocks = Math.ceil(height / BLOCK);
+
+		division = Math.ceil(xblocks * yblocks / workers);
+
+		var start = division * worker;
+		startX = (start % xblocks) * BLOCK;
+		startY = (start / xblocks | 0) * BLOCK;
+
+		completed = 0;
+	}
+
+	if (data.render) {
+		renderer.render(scene, camera)
+	}
+}
+
+importScripts('../../../build/three.min.js');
+importScripts('../../../examples/raytrace_scene.js');
+
+
+/**
+ * DOM-less version of Raytracing Renderer
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author zz95 / http://github.com/zz85
+ */
+
+THREE.RaytracingRenderer =
+
+THREE.RaytracingRendererWorker = function ( parameters ) {
+
+	console.log( 'THREE.RaytracingRenderer', THREE.REVISION );
+
+	parameters = parameters || {};
+
+	var scope = this;
+
+	var maxRecursionDepth = 3;
+
+	var canvasWidth, canvasHeight;
+	var canvasWidthHalf, canvasHeightHalf;
+	var origin = new THREE.Vector3();
+	var direction = new THREE.Vector3();
+
+	var cameraPosition = new THREE.Vector3();
+
+	var raycaster = new THREE.Raycaster( origin, direction );
+	var raycasterLight = new THREE.Raycaster();
+
+	var perspective;
+	var modelViewMatrix = new THREE.Matrix4();
+	var cameraNormalMatrix = new THREE.Matrix3();
+
+	var objects;
+	var lights = [];
+	var cache = {};
+	var timeRendering = 0;
+
+	var animationFrameId = null;
+
+	this.setSize = function ( width, height ) {
+		canvasWidth = width;
+		canvasHeight = height;
+
+		canvasWidthHalf = Math.floor( canvasWidth / 2 );
+		canvasHeightHalf = Math.floor( canvasHeight / 2 );
+	};
+
+	//
+
+	var spawnRay = ( function () {
+
+		var diffuseColor = new THREE.Color();
+		var specularColor = new THREE.Color();
+		var lightColor = new THREE.Color();
+		var schlick = new THREE.Color();
+
+		var lightContribution = new THREE.Color();
+
+		var eyeVector = new THREE.Vector3();
+		var lightVector = new THREE.Vector3();
+		var normalVector = new THREE.Vector3();
+		var halfVector = new THREE.Vector3();
+
+		var localPoint = new THREE.Vector3();
+		var reflectionVector = new THREE.Vector3();
+
+		var tmpVec = new THREE.Vector3();
+
+		var tmpColor = [];
+
+		for ( var i = 0; i < maxRecursionDepth; i ++ ) {
+
+			tmpColor[ i ] = new THREE.Color();
+
+		}
+
+		return function spawnRay( rayOrigin, rayDirection, outputColor, recursionDepth ) {
+
+			var ray = raycaster.ray;
+
+			ray.origin = rayOrigin;
+			ray.direction = rayDirection;
+
+			//
+
+			var rayLight = raycasterLight.ray;
+
+			//
+
+			outputColor.setRGB( 0, 0, 0 );
+
+			//
+
+			var intersections = raycaster.intersectObjects( objects, true );
+
+			// ray didn't find anything
+			// (here should come setting of background color?)
+
+			if ( intersections.length === 0 ) {
+
+				return;
+
+			}
+
+			// ray hit
+
+			var intersection = intersections[ 0 ];
+
+			var point = intersection.point;
+			var object = intersection.object;
+			var material = object.material;
+			var face = intersection.face;
+
+			var vertices = object.geometry.vertices;
+
+			//
+
+			var _object = cache[ object.id ];
+
+			localPoint.copy( point ).applyMatrix4( _object.inverseMatrix );
+			eyeVector.subVectors( raycaster.ray.origin, point ).normalize();
+
+			// resolve pixel diffuse color
+
+			if ( material instanceof THREE.MeshLambertMaterial ||
+				 material instanceof THREE.MeshPhongMaterial ||
+				 material instanceof THREE.MeshBasicMaterial ) {
+
+				diffuseColor.copyGammaToLinear( material.color );
+
+			} else {
+
+				diffuseColor.setRGB( 1, 1, 1 );
+
+			}
+
+			if ( material.vertexColors === THREE.FaceColors ) {
+
+				diffuseColor.multiply( face.color );
+
+			}
+
+			// compute light shading
+
+			rayLight.origin.copy( point );
+
+			if ( material instanceof THREE.MeshBasicMaterial ) {
+
+				for ( var i = 0, l = lights.length; i < l; i ++ ) {
+
+					var light = lights[ i ];
+
+					lightVector.setFromMatrixPosition( light.matrixWorld );
+					lightVector.sub( point );
+
+					rayLight.direction.copy( lightVector ).normalize();
+
+					var intersections = raycasterLight.intersectObjects( objects, true );
+
+					// point in shadow
+
+					if ( intersections.length > 0 ) continue;
+
+					// point visible
+
+					outputColor.add( diffuseColor );
+
+				}
+
+			} else if ( material instanceof THREE.MeshLambertMaterial ||
+						material instanceof THREE.MeshPhongMaterial ) {
+
+				var normalComputed = false;
+
+				for ( var i = 0, l = lights.length; i < l; i ++ ) {
+
+					var light = lights[ i ];
+
+					lightColor.copyGammaToLinear( light.color );
+
+					lightVector.setFromMatrixPosition( light.matrixWorld );
+					lightVector.sub( point );
+
+					rayLight.direction.copy( lightVector ).normalize();
+
+					var intersections = raycasterLight.intersectObjects( objects, true );
+
+					// point in shadow
+
+					if ( intersections.length > 0 ) continue;
+
+					// point lit
+
+					if ( normalComputed === false ) {
+
+						// the same normal can be reused for all lights
+						// (should be possible to cache even more)
+
+						computePixelNormal( normalVector, localPoint, material.shading, face, vertices );
+						normalVector.applyMatrix3( _object.normalMatrix ).normalize();
+
+						normalComputed = true;
+
+					}
+
+					// compute attenuation
+
+					var attenuation = 1.0;
+
+					if ( light.physicalAttenuation === true ) {
+
+						attenuation = lightVector.length();
+						attenuation = 1.0 / ( attenuation * attenuation );
+
+					}
+
+					lightVector.normalize();
+
+					// compute diffuse
+
+					var dot = Math.max( normalVector.dot( lightVector ), 0 );
+					var diffuseIntensity = dot * light.intensity;
+
+					lightContribution.copy( diffuseColor );
+					lightContribution.multiply( lightColor );
+					lightContribution.multiplyScalar( diffuseIntensity * attenuation );
+
+					outputColor.add( lightContribution );
+
+					// compute specular
+
+					if ( material instanceof THREE.MeshPhongMaterial ) {
+
+						halfVector.addVectors( lightVector, eyeVector ).normalize();
+
+						var dotNormalHalf = Math.max( normalVector.dot( halfVector ), 0.0 );
+						var specularIntensity = Math.max( Math.pow( dotNormalHalf, material.shininess ), 0.0 ) * diffuseIntensity;
+
+						var specularNormalization = ( material.shininess + 2.0 ) / 8.0;
+
+						specularColor.copyGammaToLinear( material.specular );
+
+						var alpha = Math.pow( Math.max( 1.0 - lightVector.dot( halfVector ), 0.0 ), 5.0 );
+
+						schlick.r = specularColor.r + ( 1.0 - specularColor.r ) * alpha;
+						schlick.g = specularColor.g + ( 1.0 - specularColor.g ) * alpha;
+						schlick.b = specularColor.b + ( 1.0 - specularColor.b ) * alpha;
+
+						lightContribution.copy( schlick );
+
+						lightContribution.multiply( lightColor );
+						lightContribution.multiplyScalar( specularNormalization * specularIntensity * attenuation );
+						outputColor.add( lightContribution );
+
+					}
+
+				}
+
+			}
+
+			// reflection / refraction
+
+			var reflectivity = material.reflectivity;
+
+			if ( ( material.mirror || material.glass ) && reflectivity > 0 && recursionDepth < maxRecursionDepth ) {
+
+				if ( material.mirror ) {
+
+					reflectionVector.copy( rayDirection );
+					reflectionVector.reflect( normalVector );
+
+				} else if ( material.glass ) {
+
+					var eta = material.refractionRatio;
+
+					var dotNI = rayDirection.dot( normalVector );
+					var k = 1.0 - eta * eta * ( 1.0 - dotNI * dotNI );
+
+					if ( k < 0.0 ) {
+
+						reflectionVector.set( 0, 0, 0 );
+
+					} else {
+
+						reflectionVector.copy( rayDirection );
+						reflectionVector.multiplyScalar( eta );
+
+						var alpha = eta * dotNI + Math.sqrt( k );
+						tmpVec.copy( normalVector );
+						tmpVec.multiplyScalar( alpha );
+						reflectionVector.sub( tmpVec );
+
+					}
+
+				}
+
+				var theta = Math.max( eyeVector.dot( normalVector ), 0.0 );
+				var rf0 = reflectivity;
+				var fresnel = rf0 + ( 1.0 - rf0 ) * Math.pow( ( 1.0 - theta ), 5.0 );
+
+				var weight = fresnel;
+
+				var zColor = tmpColor[ recursionDepth ];
+
+				spawnRay( point, reflectionVector, zColor, recursionDepth + 1 );
+
+				if ( material.specular !== undefined ) {
+
+					zColor.multiply( material.specular );
+
+				}
+
+				zColor.multiplyScalar( weight );
+				outputColor.multiplyScalar( 1 - weight );
+				outputColor.add( zColor );
+
+			}
+
+		};
+
+	}() );
+
+	var computePixelNormal = ( function () {
+
+		var tmpVec1 = new THREE.Vector3();
+		var tmpVec2 = new THREE.Vector3();
+		var tmpVec3 = new THREE.Vector3();
+
+		return function computePixelNormal( outputVector, point, shading, face, vertices ) {
+
+			var faceNormal = face.normal;
+			var vertexNormals = face.vertexNormals;
+
+			if ( shading === THREE.FlatShading ) {
+
+				outputVector.copy( faceNormal );
+
+			} else if ( shading === THREE.SmoothShading ) {
+
+				// compute barycentric coordinates
+
+				var vA = vertices[ face.a ];
+				var vB = vertices[ face.b ];
+				var vC = vertices[ face.c ];
+
+				tmpVec3.crossVectors( tmpVec1.subVectors( vB, vA ), tmpVec2.subVectors( vC, vA ) );
+				var areaABC = faceNormal.dot( tmpVec3 );
+
+				tmpVec3.crossVectors( tmpVec1.subVectors( vB, point ), tmpVec2.subVectors( vC, point ) );
+				var areaPBC = faceNormal.dot( tmpVec3 );
+				var a = areaPBC / areaABC;
+
+				tmpVec3.crossVectors( tmpVec1.subVectors( vC, point ), tmpVec2.subVectors( vA, point ) );
+				var areaPCA = faceNormal.dot( tmpVec3 );
+				var b = areaPCA / areaABC;
+
+				var c = 1.0 - a - b;
+
+				// compute interpolated vertex normal
+
+				tmpVec1.copy( vertexNormals[ 0 ] );
+				tmpVec1.multiplyScalar( a );
+
+				tmpVec2.copy( vertexNormals[ 1 ] );
+				tmpVec2.multiplyScalar( b );
+
+				tmpVec3.copy( vertexNormals[ 2 ] );
+				tmpVec3.multiplyScalar( c );
+
+				outputVector.addVectors( tmpVec1, tmpVec2 );
+				outputVector.add( tmpVec3 );
+
+			}
+
+		};
+
+	}() );
+
+	var renderBlock = ( function () {
+
+		var blockSize = BLOCK;
+
+		var data = new Uint8ClampedArray(blockSize * blockSize * 4);
+
+		var pixelColor = new THREE.Color();
+
+		return function renderBlock( blockX, blockY ) {
+
+			var index = 0;
+
+			for ( var y = 0; y < blockSize; y ++ ) {
+
+				for ( var x = 0; x < blockSize; x ++, index += 4 ) {
+
+					// spawn primary ray at pixel position
+
+					origin.copy( cameraPosition );
+
+					direction.set( x + blockX - canvasWidthHalf, - ( y + blockY - canvasHeightHalf ), - perspective );
+					direction.applyMatrix3( cameraNormalMatrix ).normalize();
+
+					spawnRay( origin, direction, pixelColor, 0 );
+
+					// convert from linear to gamma
+
+					data[ index ]     = Math.sqrt( pixelColor.r ) * 255;
+					data[ index + 1 ] = Math.sqrt( pixelColor.g ) * 255;
+					data[ index + 2 ] = Math.sqrt( pixelColor.b ) * 255;
+					data[ index + 3 ] = 255;
+
+				}
+
+			}
+
+			// self.postMessage({
+			// 	blockX: blockX,
+			// 	blockY: blockY,
+			// 	blockSize: blockSize,
+			// 	data: data
+			// })
+
+			// Use transferable objects! :)
+			self.postMessage({
+				data: data.buffer,
+				blockX: blockX,
+				blockY: blockY,
+				blockSize: blockSize,
+			}, [data.buffer]);
+
+			data = new Uint8ClampedArray(blockSize * blockSize * 4);
+
+			// OK Done!
+
+			blockX += blockSize;
+
+			completed++;
+
+			if ( blockX >= canvasWidth ) {
+
+				blockX = 0;
+				blockY += blockSize;
+
+			}
+
+			console.log('Worker', worker, 'completed', completed, '/', division)
+
+			if ( blockY >= canvasHeight || completed === division ) {
+				console.log('Total Renderering time', timeRendering / 1000, 's');
+				console.log('Absolute time', (Date.now() - reallyThen) / 1000, 's');
+				scope.dispatchEvent( { type: "complete" } );
+				self.postMessage({
+					type: 'complete',
+					time: Date.now() - reallyThen
+				});
+				return;
+			}
+
+
+			function next () {
+				console.time('render')
+				var then = Date.now();
+				renderBlock( blockX, blockY );
+				timeRendering += Date.now() - then;
+				console.timeEnd('render')
+			}
+
+			// animationFrameId = requestAnimationFrame( next );
+			next();
+
+		};
+
+	}() );
+
+	this.render = function ( scene, camera ) {
+		reallyThen = Date.now()
+
+		cancelAnimationFrame( animationFrameId );
+
+		// update scene graph
+
+		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+		// update camera matrices
+
+		if ( camera.parent === null ) camera.updateMatrixWorld();
+
+		camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+		cameraPosition.setFromMatrixPosition( camera.matrixWorld );
+
+		//
+
+		cameraNormalMatrix.getNormalMatrix( camera.matrixWorld );
+		origin.copy( cameraPosition );
+
+		perspective = 0.5 / Math.tan( THREE.Math.degToRad( camera.fov * 0.5 ) ) * canvasHeight;
+
+		objects = scene.children;
+
+		// collect lights and set up object matrices
+
+		lights.length = 0;
+
+		scene.traverse( function ( object ) {
+
+			if ( object instanceof THREE.Light ) {
+
+				lights.push( object );
+
+			}
+
+			if ( cache[ object.id ] === undefined ) {
+
+				cache[ object.id ] = {
+					normalMatrix: new THREE.Matrix3(),
+					inverseMatrix: new THREE.Matrix4()
+				};
+
+			}
+
+			modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+
+			var _object = cache[ object.id ];
+
+			_object.normalMatrix.getNormalMatrix( modelViewMatrix );
+			_object.inverseMatrix.getInverse( object.matrixWorld );
+
+		} );
+
+		renderBlock( startX, startY );
+
+	};
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.RaytracingRendererWorker.prototype );

+ 119 - 0
examples/js/renderers/RaytracingWorkerRenderer.js

@@ -0,0 +1,119 @@
+/**
+ * The way to use RaytracingWorkerRenderer is similar to RaytracingRenderer
+ * except that it is simply a coordinator for workers. The workers compute
+ * the pixel values and this renderer simply paints it to the Canvas. As such,
+ * it is simply a renderer.
+ *
+ * TODO
+ * - serialize scene and hand it to workers
+ * - renderer thread to hand block jobs to workers
+ * - pass worker path as option
+ *
+ * @author zz85 / http://github.com/zz85
+ */
+
+THREE.RaytracingWorkerRenderer = function ( parameters ) {
+
+	console.log( 'THREE.RaytracingWorkerRenderer', THREE.REVISION );
+
+	parameters = parameters || {};
+
+	var scope = this;
+	var pool = [];
+
+	var canvas = document.createElement( 'canvas' );
+	var context = canvas.getContext( '2d', {
+		alpha: parameters.alpha === true
+	} );
+
+	var maxRecursionDepth = 3;
+
+	var canvasWidth, canvasHeight;
+	var canvasWidthHalf, canvasHeightHalf;
+
+	var clearColor = new THREE.Color( 0x000000 );
+
+	this.domElement = canvas;
+
+	this.autoClear = true;
+
+	var workers = parameters.workers || navigator.hardwareConcurrency || 4;
+
+	console.log('%cSpinning off ' + workers + ' Workers ', 'font-size: 20px; background: black; color: white; font-family: monospace;');
+
+	for (var i = 0; i < workers; i++) {
+		var worker = new Worker('js/renderers/RaytracingWorker.js');
+		worker.onmessage = function(e) {
+			var data = e.data;
+
+			if (!data) return;
+
+			if (data.blockSize) {
+				var d = data.data;
+				var imagedata = new ImageData(new Uint8ClampedArray(d), data.blockSize, data.blockSize);
+				renderer.ctx.putImageData( imagedata, data.blockX, data.blockY );
+			} else if (data.type == 'complete') {
+				// TODO can terminate worker here or schedule more other jobs...
+			}
+
+		}
+		pool.push(worker);
+	}
+
+
+	this.setClearColor = function ( color, alpha ) {
+
+		clearColor.set( color );
+
+	};
+
+	this.setPixelRatio = function () {};
+
+	this.setSize = function ( width, height ) {
+
+		canvas.width = width;
+		canvas.height = height;
+
+		canvasWidth = canvas.width;
+		canvasHeight = canvas.height;
+
+		canvasWidthHalf = Math.floor( canvasWidth / 2 );
+		canvasHeightHalf = Math.floor( canvasHeight / 2 );
+
+		context.fillStyle = 'white';
+
+		pool.forEach(function(p, i) {
+			p.postMessage({
+				init: [ width, height ],
+				worker: i,
+				workers: pool.length,
+				blockSize: 64
+			})
+		})
+
+	};
+
+	this.setSize( canvas.width, canvas.height );
+
+	this.clear = function () {
+
+	};
+
+	//
+
+	this.render = function ( scene, camera ) {
+		reallyThen = Date.now()
+
+		pool.forEach(function(p) {
+			p.postMessage({
+				render: true
+			})
+		});
+
+	};
+
+	this.ctx = context;
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.RaytracingWorkerRenderer.prototype );

+ 202 - 0
examples/raytrace_scene.js

@@ -0,0 +1,202 @@
+function initScene(width, height) {
+	console.log('initScene', width, height);
+
+	camera = new THREE.PerspectiveCamera( 60, width / height, 1, 1000 );
+	camera.position.z = 600;
+
+	scene = new THREE.Scene();
+
+	// materials
+
+	var phongMaterial = new THREE.MeshPhongMaterial( {
+		color: 0xffffff,
+		specular: 0x222222,
+		shininess: 150,
+		vertexColors: THREE.NoColors,
+		shading: THREE.SmoothShading
+	} );
+
+	var phongMaterialBox = new THREE.MeshPhongMaterial( {
+		color: 0xffffff,
+		specular: 0x111111,
+		shininess: 100,
+		vertexColors: THREE.NoColors,
+		shading: THREE.FlatShading
+	} );
+
+	var phongMaterialBoxBottom = new THREE.MeshPhongMaterial( {
+		color: 0x666666,
+		specular: 0x111111,
+		shininess: 100,
+		vertexColors: THREE.NoColors,
+		shading: THREE.FlatShading
+	} );
+
+	var phongMaterialBoxLeft = new THREE.MeshPhongMaterial( {
+		color: 0x990000,
+		specular: 0x111111,
+		shininess: 100,
+		vertexColors: THREE.NoColors,
+		shading: THREE.FlatShading
+	} );
+
+	var phongMaterialBoxRight = new THREE.MeshPhongMaterial( {
+		color: 0x0066ff,
+		specular: 0x111111,
+		shininess: 100,
+		vertexColors: THREE.NoColors,
+		shading: THREE.FlatShading
+	} );
+
+	var mirrorMaterialFlat = new THREE.MeshPhongMaterial( {
+		color: 0x000000,
+		specular: 0xff8888,
+		shininess: 10000,
+		vertexColors: THREE.NoColors,
+		shading: THREE.FlatShading
+	} );
+	mirrorMaterialFlat.mirror = true;
+	mirrorMaterialFlat.reflectivity = 1;
+
+	var mirrorMaterialFlatDark = new THREE.MeshPhongMaterial( {
+		color: 0x000000,
+		specular: 0xaaaaaa,
+		shininess: 10000,
+		vertexColors: THREE.NoColors,
+		shading: THREE.FlatShading
+	} );
+	mirrorMaterialFlatDark.mirror = true;
+	mirrorMaterialFlatDark.reflectivity = 1;
+
+	var mirrorMaterialSmooth = new THREE.MeshPhongMaterial( {
+		color: 0xffaa00,
+		specular: 0x222222,
+		shininess: 10000,
+		vertexColors: THREE.NoColors,
+		shading: THREE.SmoothShading
+	} );
+	mirrorMaterialSmooth.mirror = true;
+	mirrorMaterialSmooth.reflectivity = 0.3;
+
+	var glassMaterialFlat = new THREE.MeshPhongMaterial( {
+		color: 0x000000,
+		specular: 0x00ff00,
+		shininess: 10000,
+		vertexColors: THREE.NoColors,
+		shading: THREE.FlatShading
+	} );
+	glassMaterialFlat.glass = true;
+	glassMaterialFlat.reflectivity = 0.5;
+
+	var glassMaterialSmooth = new THREE.MeshPhongMaterial( {
+		color: 0x000000,
+		specular: 0xffaa55,
+		shininess: 10000,
+		vertexColors: THREE.NoColors,
+		shading: THREE.SmoothShading
+	} );
+	glassMaterialSmooth.glass = true;
+	glassMaterialSmooth.reflectivity = 0.25;
+	glassMaterialSmooth.refractionRatio = 0.6;
+
+	// geometries
+
+	var torusGeometry = new THREE.TorusKnotGeometry( 150 );
+	var sphereGeometry = new THREE.SphereGeometry( 100, 16, 8 );
+	var planeGeometry = new THREE.BoxGeometry( 600, 5, 600 );
+	var boxGeometry = new THREE.BoxGeometry( 100, 100, 100 );
+
+	// TorusKnot
+
+	//torus = new THREE.Mesh( torusGeometry, phongMaterial );
+	//scene.add( torus );
+
+	// Sphere
+
+	sphere = new THREE.Mesh( sphereGeometry, phongMaterial );
+	sphere.scale.multiplyScalar( 0.5 );
+	sphere.position.set( -50, -250+5, -50 );
+	scene.add( sphere );
+
+	sphere2 = new THREE.Mesh( sphereGeometry, mirrorMaterialSmooth );
+	sphere2.scale.multiplyScalar( 0.5 );
+	sphere2.position.set( 175, -250+5, -150 );
+	scene.add( sphere2 );
+
+	// Box
+
+	box = new THREE.Mesh( boxGeometry, mirrorMaterialFlat );
+	box.position.set( -175, -250+2.5, -150 );
+	box.rotation.y = 0.5;
+	scene.add( box );
+
+	// Glass
+
+	glass = new THREE.Mesh( sphereGeometry, glassMaterialSmooth );
+	glass.scale.multiplyScalar( 0.5 );
+	glass.position.set( 75, -250+5, -75 );
+	glass.rotation.y = 0.5;
+	scene.add( glass );
+
+	// bottom
+
+	plane = new THREE.Mesh( planeGeometry, phongMaterialBoxBottom );
+	plane.position.set( 0, -300+2.5, -300 );
+	scene.add( plane );
+
+	// top
+
+	plane = new THREE.Mesh( planeGeometry, phongMaterialBox );
+	plane.position.set( 0, 300-2.5, -300 );
+	scene.add( plane );
+
+	// back
+
+	plane = new THREE.Mesh( planeGeometry, phongMaterialBox );
+	plane.rotation.x = 1.57;
+	plane.position.set( 0, 0, -300 );
+	scene.add( plane );
+
+	plane = new THREE.Mesh( planeGeometry, mirrorMaterialFlatDark );
+	plane.rotation.x = 1.57;
+	plane.position.set( 0, 0, -300+10 );
+	plane.scale.multiplyScalar( 0.85 );
+	scene.add( plane );
+
+	// left
+
+	plane = new THREE.Mesh( planeGeometry, phongMaterialBoxLeft );
+	plane.rotation.z = 1.57;
+	plane.position.set( -300, 0, -300 );
+	scene.add( plane );
+
+	// right
+
+	plane = new THREE.Mesh( planeGeometry, phongMaterialBoxRight );
+	plane.rotation.z = 1.57;
+	plane.position.set( 300, 0, -300 );
+	scene.add( plane );
+
+	// light
+
+	var intensity = 70000;
+
+	var light = new THREE.PointLight( 0xffaa55, intensity );
+	light.position.set( -200, 100, 100 );
+	light.physicalAttenuation = true;
+	scene.add( light );
+
+	var light = new THREE.PointLight( 0x55aaff, intensity );
+	light.position.set( 200, 100, 100 );
+	light.physicalAttenuation = true;
+	scene.add( light );
+
+	var light = new THREE.PointLight( 0xffffff, intensity * 1.5 );
+	light.position.set( 0, 0, 300 );
+	light.physicalAttenuation = true;
+	scene.add( light );
+
+	renderer = new THREE.RaytracingRenderer();
+	renderer.setSize( width, height );
+
+}