浏览代码

Deferred shading: replaced special case handling of emitters with emissive color in G-buffer and full-screen emissive pass. Added handling of directional lights.

alteredq 12 年之前
父节点
当前提交
6b17fd9d6b

+ 131 - 67
examples/js/DeferredHelper.js

@@ -14,8 +14,6 @@ THREE.DeferredHelper = function ( parameters ) {
 
 	//
 
-	var geometryEmitter = new THREE.SphereGeometry( 0.7, 7, 7 );
-
 	var black = new THREE.Color( 0x000000 );
 
 	var colorShader = THREE.ShaderDeferred[ "color" ];
@@ -25,21 +23,23 @@ THREE.DeferredHelper = function ( parameters ) {
 
 	//
 
-	var unlitShader = THREE.ShaderDeferred[ "unlit" ];
-	var lightShader = THREE.ShaderDeferred[ "light" ];
-	var compositeShader = THREE.ShaderDeferred[ "composite" ];
+	var emissiveLightShader = THREE.ShaderDeferred[ "emissiveLight" ];
+	var pointLightShader = THREE.ShaderDeferred[ "pointLight" ];
+	var directionalLightShader = THREE.ShaderDeferred[ "directionalLight" ];
 
-	var unlitMaterials = [];
-	var lightMaterials = [];
+	var compositeShader = THREE.ShaderDeferred[ "composite" ];
 
 	//
 
-	var compColor, compNormal, compDepth, compEmitter, compLight, compFinal;
-	var passColor, passNormal, passDepth, passEmitter, passLight, compositePass;
+	var compColor, compNormal, compDepth, compLight, compFinal;
+	var passColor, passNormal, passDepth, passLightFullscreen, passLightProxy, compositePass;
 
 	var effectFXAA;
 
-	var emitterScene, lightScene;
+	//
+
+	var lightSceneFullscreen, lightSceneProxy;
+	var lightMaterials = [];
 
 	//
 
@@ -99,10 +99,22 @@ THREE.DeferredHelper = function ( parameters ) {
 
 		} );
 
-		var diffuse = originalMaterial.color;
+		if ( originalMaterial instanceof THREE.MeshBasicMaterial ) {
+
+			var diffuse = black;
+			var emissive = originalMaterial.color;
+
+		} else {
+
+			var diffuse = originalMaterial.color;
+			var emissive = originalMaterial.emissive !== undefined ? originalMaterial.emissive : black;
+
+		}
+
 		var specular = originalMaterial.specular !== undefined ? originalMaterial.specular : black;
 		var shininess = originalMaterial.shininess !== undefined ? originalMaterial.shininess : 1;
 
+		uniforms.emissive.value.copy( emissive );
 		uniforms.diffuse.value.copy( diffuse );
 		uniforms.specular.value.copy( specular );
 		uniforms.shininess.value = shininess;
@@ -208,9 +220,9 @@ THREE.DeferredHelper = function ( parameters ) {
 
 		var materialLight = new THREE.ShaderMaterial( {
 
-			uniforms:       THREE.UniformsUtils.clone( lightShader.uniforms ),
-			vertexShader:   lightShader.vertexShader,
-			fragmentShader: lightShader.fragmentShader,
+			uniforms:       THREE.UniformsUtils.clone( pointLightShader.uniforms ),
+			vertexShader:   pointLightShader.vertexShader,
+			fragmentShader: pointLightShader.fragmentShader,
 			defines:		{ "ADDITIVE_SPECULAR": additiveSpecular },
 
 			blending:		THREE.AdditiveBlending,
@@ -245,33 +257,81 @@ THREE.DeferredHelper = function ( parameters ) {
 
 	};
 
-	var createEmitter = function ( light ) {
+	var createDeferredDirectionalLight = function ( light ) {
 
-		// setup emitter material
+		// setup light material
 
-		var matEmitter = new THREE.ShaderMaterial( {
+		var materialLight = new THREE.ShaderMaterial( {
 
-			uniforms:       THREE.UniformsUtils.clone( unlitShader.uniforms ),
-			vertexShader:   unlitShader.vertexShader,
-			fragmentShader: unlitShader.fragmentShader
+			uniforms:       THREE.UniformsUtils.clone( directionalLightShader.uniforms ),
+			vertexShader:   directionalLightShader.vertexShader,
+			fragmentShader: directionalLightShader.fragmentShader,
+			defines:		{ "ADDITIVE_SPECULAR": additiveSpecular },
+
+			blending:		THREE.AdditiveBlending,
+			depthWrite:		false,
+			transparent:	true
 
 		} );
 
-		matEmitter.uniforms[ "viewWidth" ].value = width;
-		matEmitter.uniforms[ "viewHeight" ].value = height;
-		matEmitter.uniforms[ "samplerDepth" ].value = compDepth.renderTarget2;
-		matEmitter.uniforms[ "lightColor" ].value = light.color;
+		materialLight.uniforms[ "lightDir" ].value = light.position;
+		materialLight.uniforms[ "lightIntensity" ].value = light.intensity;
+		materialLight.uniforms[ "lightColor" ].value = light.color;
+
+		materialLight.uniforms[ "viewWidth" ].value = width;
+		materialLight.uniforms[ "viewHeight" ].value = height;
 
-		// create emitter mesh
+		materialLight.uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
+		materialLight.uniforms[ 'samplerDepth' ].value = compDepth.renderTarget2;
+		materialLight.uniforms[ 'samplerNormals' ].value = compNormal.renderTarget2;
 
-		var meshEmitter = new THREE.Mesh( geometryEmitter, matEmitter );
-		meshEmitter.position = light.position;
+		// create light proxy mesh
+
+		var geometryLight = new THREE.PlaneGeometry( 2, 2 );
+		var meshLight = new THREE.Mesh( geometryLight, materialLight );
 
 		// keep reference for size reset
 
-		unlitMaterials.push( matEmitter );
+		lightMaterials.push( materialLight );
 
-		return meshEmitter;
+		return meshLight;
+
+	};
+
+	var createDeferredEmissiveLight = function () {
+
+		// setup light material
+
+		var materialLight = new THREE.ShaderMaterial( {
+
+			uniforms:       THREE.UniformsUtils.clone( emissiveLightShader.uniforms ),
+			vertexShader:   emissiveLightShader.vertexShader,
+			fragmentShader: emissiveLightShader.fragmentShader,
+
+			blending:		THREE.AdditiveBlending,
+			depthTest:		false,
+			depthWrite:		false,
+			transparent:	true
+
+		} );
+
+
+		materialLight.uniforms[ "viewWidth" ].value = width;
+		materialLight.uniforms[ "viewHeight" ].value = height;
+
+		materialLight.uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
+		materialLight.uniforms[ 'samplerDepth' ].value = compDepth.renderTarget2;
+
+		// create light proxy mesh
+
+		var geometryLight = new THREE.PlaneGeometry( 2, 2 );
+		var meshLight = new THREE.Mesh( geometryLight, materialLight );
+
+		// keep reference for size reset
+
+		lightMaterials.push( materialLight );
+
+		return meshLight;
 
 	};
 
@@ -283,11 +343,13 @@ THREE.DeferredHelper = function ( parameters ) {
 
 		if ( object instanceof THREE.PointLight ) {
 
-			var meshEmitter = createEmitter( object );
 			var meshLight = createDeferredPointLight( object );
+			lightSceneProxy.add( meshLight );
+
+		} else if ( object instanceof THREE.DirectionalLight ) {
 
-			lightScene.add( meshLight );
-			emitterScene.add( meshEmitter );
+			var meshLight = createDeferredDirectionalLight( object );
+			lightSceneFullscreen.add( meshLight );
 
 		}
 
@@ -309,7 +371,7 @@ THREE.DeferredHelper = function ( parameters ) {
 
 	};
 
-	var setMaterialNormal= function ( object ) {
+	var setMaterialNormal = function ( object ) {
 
 		if ( object.material ) object.material = object.properties.normalMaterial;
 
@@ -322,19 +384,9 @@ THREE.DeferredHelper = function ( parameters ) {
 		compColor.setSize( width, height );
 		compNormal.setSize( width, height );
 		compDepth.setSize( width, height );
-		compEmitter.setSize( width, height );
 		compLight.setSize( width, height );
 		compFinal.setSize( width, height );
 
-		for ( var i = 0, il = unlitMaterials.length; i < il; i ++ ) {
-
-			var uniforms = unlitMaterials[ i ].uniforms;
-
-			uniforms[ "viewWidth" ].value = width;
-			uniforms[ "viewHeight" ].value = height;
-
-		}
-
 		for ( var i = 0, il = lightMaterials.length; i < il; i ++ ) {
 
 			var uniforms = lightMaterials[ i ].uniforms;
@@ -343,13 +395,17 @@ THREE.DeferredHelper = function ( parameters ) {
 			uniforms[ "viewHeight" ].value = height;
 
 			uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
-			uniforms[ 'samplerNormals' ].value = compNormal.renderTarget2;
 			uniforms[ 'samplerDepth' ].value = compDepth.renderTarget2;
 
+			if ( uniforms[ 'samplerNormals' ] ) {
+
+				uniforms[ 'samplerNormals' ].value = compNormal.renderTarget2;
+
+			}
+
 		}
 
 		compositePass.uniforms[ 'samplerLight' ].value = compLight.renderTarget2;
-		compositePass.uniforms[ 'samplerEmitter' ].value = compEmitter.renderTarget2;
 
 		effectFXAA.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height );
 
@@ -361,23 +417,30 @@ THREE.DeferredHelper = function ( parameters ) {
 
 		// setup deferred properties
 
-		if ( ! scene.properties.emitterScene ) scene.properties.emitterScene = new THREE.Scene();
-		if ( ! scene.properties.lightScene ) scene.properties.lightScene = new THREE.Scene();
+		if ( ! scene.properties.lightSceneProxy ) {
+
+			scene.properties.lightSceneProxy = new THREE.Scene();
+			scene.properties.lightSceneFullscreen = new THREE.Scene();
 
-		emitterScene = scene.properties.emitterScene;
-		lightScene = scene.properties.lightScene;
+			var meshLight = createDeferredEmissiveLight();
+			scene.properties.lightSceneFullscreen.add( meshLight );
+
+		}
+
+		lightSceneProxy = scene.properties.lightSceneProxy;
+		lightSceneFullscreen = scene.properties.lightSceneFullscreen;
 
 		passColor.camera = camera;
 		passNormal.camera = camera;
 		passDepth.camera = camera;
-		passEmitter.camera = camera;
-		passLight.camera = camera;
+		passLightProxy.camera = camera;
+		passLightFullscreen.camera = THREE.EffectComposer.camera;
 
 		passColor.scene = scene;
 		passNormal.scene = scene;
 		passDepth.scene = scene;
-		passEmitter.scene = emitterScene;
-		passLight.scene = lightScene;
+		passLightFullscreen.scene = lightSceneFullscreen;
+		passLightProxy.scene = lightSceneProxy;
 
 		scene.traverse( initDeferredProperties );
 
@@ -396,23 +459,27 @@ THREE.DeferredHelper = function ( parameters ) {
 		scene.traverse( setMaterialNormal );
 		compNormal.render();
 
-		// emitter pass
-
-		compEmitter.render();
-
 		// light pass
 
 		camera.projectionMatrixInverse.getInverse( camera.projectionMatrix );
 
-		for ( var i = 0, il = lightScene.children.length; i < il; i ++ ) {
+		for ( var i = 0, il = lightSceneProxy.children.length; i < il; i ++ ) {
 
-			var uniforms = lightScene.children[ i ].material.uniforms;
+			var uniforms = lightSceneProxy.children[ i ].material.uniforms;
 
 			uniforms[ "matProjInverse" ].value = camera.projectionMatrixInverse;
 			uniforms[ "matView" ].value = camera.matrixWorldInverse;
 
 		}
 
+		for ( var i = 0, il = lightSceneFullscreen.children.length; i < il; i ++ ) {
+
+			var uniforms = lightSceneFullscreen.children[ i ].material.uniforms;
+
+			if ( uniforms[ "matView" ] ) uniforms[ "matView" ].value = camera.matrixWorldInverse;
+
+		}
+
 		compLight.render();
 
 		// composite pass
@@ -429,14 +496,12 @@ THREE.DeferredHelper = function ( parameters ) {
 		var rtNormal  = new THREE.WebGLRenderTarget( width, height, rtParamsFloatLinear );
 		var rtDepth   = new THREE.WebGLRenderTarget( width, height, rtParamsFloatLinear );
 		var rtLight   = new THREE.WebGLRenderTarget( width, height, rtParamsFloatLinear );
-		var rtEmitter = new THREE.WebGLRenderTarget( width, height, rtParamsUByte );
 		var rtFinal   = new THREE.WebGLRenderTarget( width, height, rtParamsUByte );
 
 		rtColor.generateMipmaps = false;
 		rtNormal.generateMipmaps = false;
 		rtDepth.generateMipmaps = false;
 		rtLight.generateMipmaps = false;
-		rtEmitter.generateMipmaps = false;
 		rtFinal.generateMipmaps = false;
 
 		// composers
@@ -453,13 +518,13 @@ THREE.DeferredHelper = function ( parameters ) {
 		compDepth = new THREE.EffectComposer( renderer, rtDepth );
 		compDepth.addPass( passDepth );
 
-		passEmitter = new THREE.RenderPass();
-		compEmitter = new THREE.EffectComposer( renderer, rtEmitter );
-		compEmitter.addPass( passEmitter );
+		passLightProxy = new THREE.RenderPass();
+		passLightFullscreen = new THREE.RenderPass();
+		passLightFullscreen.clear = false;
 
-		passLight = new THREE.RenderPass();
 		compLight = new THREE.EffectComposer( renderer, rtLight );
-		compLight.addPass( passLight );
+		compLight.addPass( passLightProxy );
+		compLight.addPass( passLightFullscreen );
 
 		// composite
 
@@ -467,7 +532,6 @@ THREE.DeferredHelper = function ( parameters ) {
 		compositePass.needsSwap = true;
 
 		compositePass.uniforms[ 'samplerLight' ].value = compLight.renderTarget2;
-		compositePass.uniforms[ 'samplerEmitter' ].value = compEmitter.renderTarget2;
 
 		// FXAA
 

+ 210 - 19
examples/js/ShaderDeferred.js

@@ -17,6 +17,7 @@ THREE.ShaderDeferred = {
 			THREE.UniformsLib[ "shadowmap" ],
 
 			{
+				"emissive" : { type: "c", value: new THREE.Color( 0x000000 ) },
 				"specular" : { type: "c", value: new THREE.Color( 0x111111 ) },
 				"shininess": { type: "f", value: 30 }
 			}
@@ -27,6 +28,7 @@ THREE.ShaderDeferred = {
 
 			"uniform vec3 diffuse;",
 			"uniform vec3 specular;",
+			"uniform vec3 emissive;",
 			"uniform float shininess;",
 
 			THREE.ShaderChunk[ "color_pars_fragment" ],
@@ -76,9 +78,9 @@ THREE.ShaderDeferred = {
 
 				"gl_FragColor.z = shininess;",
 
-				// free
+				// emissive color
 
-				"gl_FragColor.w = 0.0;",
+				"gl_FragColor.w = vec3_to_float( 0.999 * emissive );",
 
 			"}"
 
@@ -313,8 +315,7 @@ THREE.ShaderDeferred = {
 
 		uniforms: {
 
-			samplerLight: 	{ type: "t", value: null },
-			samplerEmitter: { type: "t", value: null }
+			samplerLight: 	{ type: "t", value: null }
 
 		},
 
@@ -322,23 +323,11 @@ THREE.ShaderDeferred = {
 
 			"varying vec2 texCoord;",
 			"uniform sampler2D samplerLight;",
-			"uniform sampler2D samplerEmitter;",
-			"uniform vec3 lightPos;",
 
 			"void main() {",
 
 				"vec3 color = texture2D( samplerLight, texCoord ).xyz;",
-				"vec3 emitter = texture2D( samplerEmitter, texCoord ).xyz;",
-
-				"if ( emitter != vec3( 0.0 ) ) {",
-
-					"gl_FragColor = vec4( emitter, 1.0 );",
-
-				"} else {",
-
-					"gl_FragColor = vec4( sqrt( color ), 1.0 );",
-
-				"}",
+				"gl_FragColor = vec4( sqrt( color ), 1.0 );",
 
 			"}"
 
@@ -360,7 +349,7 @@ THREE.ShaderDeferred = {
 
 	},
 
-	"light" : {
+	"pointLight" : {
 
 		uniforms: {
 
@@ -525,7 +514,209 @@ THREE.ShaderDeferred = {
 
 		].join("\n")
 
-	}
+	},
+
+	"directionalLight" : {
+
+		uniforms: {
+
+			samplerNormals: { type: "t", value: null },
+			samplerDepth: 	{ type: "t", value: null },
+			samplerColor: 	{ type: "t", value: null },
+			matView: 		{ type: "m4", value: new THREE.Matrix4() },
+			matProjInverse: { type: "m4", value: new THREE.Matrix4() },
+			viewWidth: 		{ type: "f", value: 800 },
+			viewHeight: 	{ type: "f", value: 600 },
+			lightDir: 		{ type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
+			lightColor: 	{ type: "c", value: new THREE.Color( 0x000000 ) },
+			lightIntensity: { type: "f", value: 1.0 }
+
+		},
+
+		fragmentShader : [
+
+			"varying vec3 lightView;",
+			"varying vec4 clipPos;",
+
+			"uniform sampler2D samplerColor;",
+			"uniform sampler2D samplerDepth;",
+			"uniform sampler2D samplerNormals;",
+
+			"uniform float lightRadius;",
+			"uniform float lightIntensity;",
+			"uniform float viewHeight;",
+			"uniform float viewWidth;",
+
+			"uniform vec3 lightColor;",
+
+			"uniform mat4 matProjInverse;",
+
+			"vec3 float_to_vec3( float data ) {",
+
+				"vec3 uncompressed;",
+				"uncompressed.x = fract( data );",
+				"float zInt = floor( data / 255.0 );",
+				"uncompressed.z = fract( zInt / 255.0 );",
+				"uncompressed.y = fract( floor( data - ( zInt * 255.0 ) ) / 255.0 );",
+				"return uncompressed;",
+
+			"}",
+
+			"void main() {",
+
+				"vec2 texCoord = gl_FragCoord.xy / vec2( viewWidth, viewHeight );",
+
+				"float z = texture2D( samplerDepth, texCoord ).x;",
+
+				"if ( z == 0.0 ) discard;",
+
+				"float x = texCoord.x * 2.0 - 1.0;",
+				"float y = texCoord.y * 2.0 - 1.0;",
+
+				"vec4 projectedPos = vec4( x, y, z, 1.0 );",
+
+				"vec4 viewPos = matProjInverse * projectedPos;",
+				"viewPos.xyz /= viewPos.w;",
+				"viewPos.w = 1.0;",
+
+				"vec3 lightDir = normalize( lightView );",
+
+				// normal
+
+				"vec3 normal = texture2D( samplerNormals, texCoord ).xyz * 2.0 - 1.0;",
+
+				// color
+
+				"vec4 colorMap = texture2D( samplerColor, texCoord );",
+				"vec3 albedo = float_to_vec3( abs( colorMap.x ) );",
+				"vec3 specularColor = float_to_vec3( abs( colorMap.y ) );",
+				"float shininess = colorMap.z;",
+
+				// wrap around lighting
+
+				"float diffuseFull = max( dot( normal, lightDir ), 0.0 );",
+				"float diffuseHalf = max( 0.5 + 0.5 * dot( normal, lightDir ), 0.0 );",
+
+				"const vec3 wrapRGB = vec3( 0.2, 0.2, 0.2 );",
+				"vec3 diffuse = mix( vec3 ( diffuseFull ), vec3( diffuseHalf ), wrapRGB );",
+
+				// simple lighting
+
+				//"float diffuseFull = max( dot( normal, lightDir ), 0.0 );",
+				//"vec3 diffuse = vec3 ( diffuseFull );",
+
+				// specular
+
+				"vec3 halfVector = normalize( lightDir + normalize( viewPos.xyz ) );",
+				"float dotNormalHalf = max( dot( normal, halfVector ), 0.0 );",
+
+				// simple specular
 
+				//"vec3 specular = specularIntensity * max( pow( dotNormalHalf, shininess ), 0.0 ) * diffuse;",
+
+				// physically based specular
+
+				"float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+				"vec3 schlick = specularColor + vec3( 1.0 - specularColor ) * pow( 1.0 - dot( lightDir, halfVector ), 5.0 );",
+				"vec3 specular = schlick * max( pow( dotNormalHalf, shininess ), 0.0 ) * diffuse * specularNormalization;",
+
+				// combine
+
+				"vec3 light = lightIntensity * lightColor;",
+
+				"#ifdef ADDITIVE_SPECULAR",
+
+					"gl_FragColor = vec4( albedo * light * diffuse, 1.0 ) + vec4( light * specular, 1.0 );",
+
+				"#else",
+
+					"gl_FragColor = vec4( albedo * light * ( diffuse + specular ), 1.0 );",
+
+				"#endif",
+
+			"}"
+
+		].join("\n"),
+
+		vertexShader : [
+
+			"varying vec3 lightView;",
+			"varying vec4 clipPos;",
+			"uniform vec3 lightDir;",
+			"uniform mat4 matView;",
+
+			"void main() { ",
+
+				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+				"gl_Position = projectionMatrix * mvPosition;",
+				"lightView = vec3( matView * vec4( lightDir, 0.0 ) );",
+				"clipPos = gl_Position;",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	"emissiveLight" : {
+
+		uniforms: {
+
+			samplerDepth: 	{ type: "t", value: null },
+			samplerColor: 	{ type: "t", value: null },
+			viewWidth: 		{ type: "f", value: 800 },
+			viewHeight: 	{ type: "f", value: 600 },
+
+		},
+
+		fragmentShader : [
+
+			"uniform sampler2D samplerDepth;",
+			"uniform sampler2D samplerColor;",
+
+			"uniform float viewHeight;",
+			"uniform float viewWidth;",
+
+			"vec3 float_to_vec3( float data ) {",
+
+				"vec3 uncompressed;",
+				"uncompressed.x = fract( data );",
+				"float zInt = floor( data / 255.0 );",
+				"uncompressed.z = fract( zInt / 255.0 );",
+				"uncompressed.y = fract( floor( data - ( zInt * 255.0 ) ) / 255.0 );",
+				"return uncompressed;",
+
+			"}",
+
+			"void main() {",
+
+				"vec2 texCoord = gl_FragCoord.xy / vec2( viewWidth, viewHeight );",
+
+				"float z = texture2D( samplerDepth, texCoord ).x;",
+
+				"if ( z == 0.0 ) discard;",
+
+				"vec4 colorMap = texture2D( samplerColor, texCoord );",
+				"vec3 emissiveColor = float_to_vec3( abs( colorMap.w ) );",
+
+				"gl_FragColor = vec4( emissiveColor, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		vertexShader : [
+
+			"void main() { ",
+
+				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+				"gl_Position = projectionMatrix * mvPosition;",
+
+			"}"
+
+		].join("\n")
+
+	}
 
 };

+ 18 - 0
examples/webgl_lights_deferred_morphs.html

@@ -119,6 +119,8 @@
 				renderer.setSize( WIDTH, HEIGHT );
 				renderer.setClearColorHex( 0x000000, 1 );
 
+				renderer.autoClear = false;
+
 				renderer.domElement.style.position = "absolute";
 				renderer.domElement.style.top = MARGIN + "px";
 				renderer.domElement.style.left = "0px";
@@ -208,6 +210,22 @@
 
 				}
 
+				var geometry = new THREE.SphereGeometry( 0.7, 7, 7 );
+
+				for ( var i = 0; i < numLights; i ++ ) {
+
+					var light = lights[ i ];
+
+					var material = new THREE.MeshBasicMaterial();
+					material.color = light.color;
+
+					var emitter = new THREE.Mesh( geometry, material );
+					emitter.position = light.position;
+
+					scene.add( emitter );
+
+				}
+
 			}
 
 			function initObjects() {

+ 18 - 0
examples/webgl_lights_deferred_pointlights.html

@@ -119,6 +119,8 @@
 				renderer.setSize( WIDTH, HEIGHT );
 				renderer.setClearColorHex( 0x000000, 1 );
 
+				renderer.autoClear = false;
+
 				renderer.domElement.style.position = "absolute";
 				renderer.domElement.style.top = MARGIN + "px";
 				renderer.domElement.style.left = "0px";
@@ -208,6 +210,22 @@
 
 				}
 
+				var geometry = new THREE.SphereGeometry( 0.7, 7, 7 );
+
+				for ( var i = 0; i < numLights; i ++ ) {
+
+					var light = lights[ i ];
+
+					var material = new THREE.MeshBasicMaterial();
+					material.color = light.color;
+
+					var emitter = new THREE.Mesh( geometry, material );
+					emitter.position = light.position;
+
+					scene.add( emitter );
+
+				}
+
 			}
 
 			// -----------------------------