Browse Source

Examples: Add GPU Progressive Lightmapping Example (#21435)

* A promising start...

But the blending is backwards!

* Beginning to Believe...

* Pre-Switching Over...

* Attempt to port the progressive shadowmapping to two objects...

* Only update shadows periodically...

* Why isn't this accumulating?!

* Excise the Effect Composer

Turned out to be a lead crutch!

* Get everything working, trim unused scripts

* Blur the edges of the Lightmap

* Publish Page

* Add progressive surface to examples?

* Try HalfFloats for iOS

* Begin hacking in iOS Support...

* Revert "Begin hacking in iOS Support..."

This reverts commit f98dc3975f0123845d45682a9334c7863722566a.

* Turn the Jitter back up

* Add Transform Gizmo and AO

* Switch AO to a Uniform Hemisphere Distribution

* Brighten, Enable Dithering, Switch back to HalfFloats

* Switch back to full floats since iOS doesn't work anyway

* Separate Files, Document Surface Mapping

* Codify Alternate Scene Universe

scene.attach() (not scene.add()) was the secret!

This is still slower than it needs to be, extraneous renders...

* Remove Extraneous Import

* Switch to Explicit Lightmapping Mode

* Get Lightmap Packing Working

* Add padding to remove bleeding

* Optimize Rendering; Add Debug Visualization

* Add back aesthetic bug

* Limit Debug Warnings

* Fix Debug Display

* Fix Debug Display 2

* Doobify and Prepare for PR

* Build GH Pages

* Accumulate Frames to Pass Test

* Update the Screenshot via node test

* Switch mesh to a .glb, class to ES6

* Remove another unnecessary semicolon

* Fix WebGL1 Support and Warnings
Johnathon Selstad 4 years ago
parent
commit
71332b0e0b

+ 2 - 1
examples/files.json

@@ -300,7 +300,8 @@
 		"webgl_shadowmap_pcss",
 		"webgl_simple_gi",
 		"webgl_tiled_forward",
-		"webgl_worker_offscreencanvas"
+		"webgl_worker_offscreencanvas",
+		"webgl_shadowmap_progressive"
 	],
 	"webgl2": [
 		"webgl2_buffergeometry_attributes_integer",

+ 125 - 0
examples/jsm/libs/potpack.module.js

@@ -0,0 +1,125 @@
+/**
+ * potpack - by [@mourner](https://github.com/mourner)
+ * 
+ * A tiny JavaScript function for packing 2D rectangles into a near-square container, 
+ * which is useful for generating CSS sprites and WebGL textures. Similar to 
+ * [shelf-pack](https://github.com/mapbox/shelf-pack), but static (you can't add items 
+ * once a layout is generated), and aims for maximal space utilization.
+ *
+ * A variation of algorithms used in [rectpack2D](https://github.com/TeamHypersomnia/rectpack2D)
+ * and [bin-pack](https://github.com/bryanburgers/bin-pack), which are in turn based 
+ * on [this article by Blackpawn](http://blackpawn.com/texts/lightmaps/default.html).
+ * 
+ * @license
+ * ISC License
+ * 
+ * Copyright (c) 2018, Mapbox
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any purpose
+ * with or without fee is hereby granted, provided that the above copyright notice
+ * and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+
+function potpack(boxes) {
+
+	// calculate total box area and maximum box width
+	let area = 0;
+	let maxWidth = 0;
+	
+	for (const box of boxes) {
+		area += box.w * box.h;
+		maxWidth = Math.max(maxWidth, box.w);
+	}
+	
+	// sort the boxes for insertion by height, descending
+	boxes.sort((a, b) => b.h - a.h);
+	
+	// aim for a squarish resulting container,
+	// slightly adjusted for sub-100% space utilization
+	const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);
+	
+	// start with a single empty space, unbounded at the bottom
+	const spaces = [{x: 0, y: 0, w: startWidth, h: Infinity}];
+	
+	let width = 0;
+	let height = 0;
+	
+	for (const box of boxes) {
+		// look through spaces backwards so that we check smaller spaces first
+		for (let i = spaces.length - 1; i >= 0; i--) {
+			const space = spaces[i];
+			
+			// look for empty spaces that can accommodate the current box
+			if (box.w > space.w || box.h > space.h) continue;
+			
+			// found the space; add the box to its top-left corner
+			// |-------|-------|
+			// |  box  |       |
+			// |_______|       |
+			// |         space |
+			// |_______________|
+			box.x = space.x;
+			box.y = space.y;
+			
+			height = Math.max(height, box.y + box.h);
+			width = Math.max(width, box.x + box.w);
+			
+			if (box.w === space.w && box.h === space.h) {
+				// space matches the box exactly; remove it
+				const last = spaces.pop();
+				if (i < spaces.length) spaces[i] = last;
+			
+			} else if (box.h === space.h) {
+				// space matches the box height; update it accordingly
+				// |-------|---------------|
+				// |  box  | updated space |
+				// |_______|_______________|
+				space.x += box.w;
+				space.w -= box.w;
+			
+			} else if (box.w === space.w) {
+				// space matches the box width; update it accordingly
+				// |---------------|
+				// |      box      |
+				// |_______________|
+				// | updated space |
+				// |_______________|
+				space.y += box.h;
+				space.h -= box.h;
+			
+			} else {
+				// otherwise the box splits the space into two spaces
+				// |-------|-----------|
+				// |  box  | new space |
+				// |_______|___________|
+				// | updated space     |
+				// |___________________|
+				spaces.push({
+					x: space.x + box.w,
+					y: space.y,
+					w: space.w - box.w,
+					h: box.h
+				});
+				space.y += box.h;
+				space.h -= box.h;
+			}
+			break;
+		}
+	}
+	
+	return {
+		w: width, // container width
+		h: height, // container height
+		fill: (area / (width * height)) || 0 // space utilization
+	};
+}
+
+export { potpack };

+ 321 - 0
examples/jsm/misc/ProgressiveLightMap.js

@@ -0,0 +1,321 @@
+import * as THREE from '../../../build/three.module.js';
+import { potpack } from '../libs/potpack.module.js';
+
+/**
+ * Progressive Light Map Accumulator, by [zalo](https://github.com/zalo/)
+ *
+ * To use, simply construct a `ProgressiveLightMap` object,
+ * `plmap.addObjectsToLightMap(object)` an array of semi-static
+ * objects and lights to the class once, and then call
+ * `plmap.update(camera)` every frame to begin accumulating
+ * lighting samples.
+ *
+ * This should begin accumulating lightmaps which apply to
+ * your objects, so you can start jittering lighting to achieve
+ * the texture-space effect you're looking for.
+ *
+ * @param {WebGLRenderer} renderer A WebGL Rendering Context
+ * @param {number} res The side-long dimension of you total lightmap
+ */
+class ProgressiveLightMap {
+
+	constructor( renderer, res = 1024 ) {
+
+		this.renderer = renderer;
+		this.res = res;
+		this.lightMapContainers = [];
+		this.compiled = false;
+		this.scene = new THREE.Scene();
+		this.scene.background = null;
+		this.tinyTarget = new THREE.WebGLRenderTarget( 1, 1 );
+		this.buffer1Active = false;
+		this.firstUpdate = true;
+		this.warned = false;
+
+		// Create the Progressive LightMap Texture
+		let format = /(Android|iPad|iPhone|iPod)/g.test( navigator.userAgent ) ? THREE.HalfFloatType : THREE.FloatType;
+		this.progressiveLightMap1 = new THREE.WebGLRenderTarget( this.res, this.res, { type: format } );
+		this.progressiveLightMap2 = new THREE.WebGLRenderTarget( this.res, this.res, { type: format } );
+
+		// Inject some spicy new logic into a standard phong material
+		this.uvMat = new THREE.MeshPhongMaterial();
+		this.uvMat.uniforms = {};
+		this.uvMat.onBeforeCompile = ( shader ) => {
+
+			// Vertex Shader: Set Vertex Positions to the Unwrapped UV Positions
+			shader.vertexShader =
+				'#define USE_LIGHTMAP\n' +
+				shader.vertexShader.slice( 0, - 1 ) +
+				'	gl_Position = vec4((uv2 - 0.5) * 2.0, 1.0, 1.0); }';
+
+			// Fragment Shader: Set Pixels to average in the Previous frame's Shadows
+			let bodyStart = shader.fragmentShader.indexOf( 'void main() {' );
+			shader.fragmentShader =
+				'varying vec2 vUv2;\n' +
+				shader.fragmentShader.slice( 0, bodyStart ) +
+				'	uniform sampler2D previousShadowMap;\n	uniform float averagingWindow;\n' +
+				shader.fragmentShader.slice( bodyStart - 1, - 1 ) +
+				`\nvec3 texelOld = texture2D(previousShadowMap, vUv2).rgb;
+				gl_FragColor.rgb = mix(texelOld, gl_FragColor.rgb, 1.0/averagingWindow);
+			}`;
+
+			// Set the Previous Frame's Texture Buffer and Averaging Window
+			shader.uniforms.previousShadowMap = { value: this.progressiveLightMap1.texture };
+			shader.uniforms.averagingWindow = { value: 100 };
+
+			this.uvMat.uniforms = shader.uniforms;
+
+			// Set the new Shader to this
+			this.uvMat.userData.shader = shader;
+
+			this.compiled = true;
+
+		};
+
+	}
+
+	/**
+	 * Sets these objects' materials' lightmaps and modifies their uv2's.
+	 * @param {Object3D} objects An array of objects and lights to set up your lightmap.
+	 */
+	addObjectsToLightMap( objects ) {
+
+		// Prepare list of UV bounding boxes for packing later...
+		this.uv_boxes = []; let padding = 3 / this.res;
+
+		for ( let ob = 0; ob < objects.length; ob ++ ) {
+
+			let object = objects[ ob ];
+
+			// If this object is a light, simply add it to the internal scene
+			if ( object.isLight ) {
+
+				this.scene.attach( object ); continue;
+
+			}
+
+			if ( ! object.geometry.hasAttribute( "uv" ) ) {
+
+				console.warn( "All lightmap objects need UVs!" ); continue;
+
+			}
+
+			if ( this.blurringPlane == null ) {
+
+				this._initializeBlurPlane( this.res, this.progressiveLightMap1 );
+
+			}
+
+			// Apply the lightmap to the object
+			object.material.lightMap = this.progressiveLightMap2.texture;
+			object.material.dithering = true;
+			object.castShadow = true;
+			object.receiveShadow = true;
+			object.renderOrder = 1000 + ob;
+
+			// Prepare UV boxes for potpack
+			// TODO: Size these by object surface area
+			this.uv_boxes.push( { w: 1 + ( padding * 2 ),
+								  h: 1 + ( padding * 2 ), index: ob } );
+
+			this.lightMapContainers.push( { basicMat: object.material, object: object } );
+
+			this.compiled = false;
+
+		}
+
+		// Pack the objects' lightmap UVs into the same global space
+		const dimensions = potpack( this.uv_boxes );
+		this.uv_boxes.forEach( ( box ) => {
+
+			let uv2 = objects[ box.index ].geometry.getAttribute( "uv" ).clone();
+			for ( let i = 0; i < uv2.array.length; i += uv2.itemSize ) {
+
+				uv2.array[ i ] = ( uv2.array[ i ] + box.x + padding ) / dimensions.w;
+				uv2.array[ i + 1 ] = ( uv2.array[ i + 1 ] + box.y + padding ) / dimensions.h;
+
+			}
+
+			objects[ box.index ].geometry.setAttribute( "uv2", uv2 );
+			objects[ box.index ].geometry.getAttribute( "uv2" ).needsUpdate = true;
+
+		} );
+
+	}
+
+	/**
+	 * This function renders each mesh one at a time into their respective surface maps
+	 * @param {Camera} camera Standard Rendering Camera
+	 * @param {number} blendWindow When >1, samples will accumulate over time.
+	 * @param {boolean} blurEdges  Whether to fix UV Edges via blurring
+	 */
+	update( camera, blendWindow = 100, blurEdges = true ) {
+
+		if ( this.blurringPlane == null ) {
+
+			return;
+
+		}
+
+		// Store the original Render Target
+		let oldTarget = this.renderer.getRenderTarget();
+
+		// The blurring plane applies blur to the seams of the lightmap
+		this.blurringPlane.visible = blurEdges;
+
+		// Steal the Object3D from the real world to our special dimension
+		for ( let l = 0; l < this.lightMapContainers.length; l ++ ) {
+
+			this.lightMapContainers[ l ].object.oldScene =
+				this.lightMapContainers[ l ].object.parent;
+			this.scene.attach( this.lightMapContainers[ l ].object );
+
+		}
+
+		// Render once normally to initialize everything
+		if ( this.firstUpdate ) {
+
+			this.renderer.setRenderTarget( this.tinyTarget ); // Tiny for Speed
+			this.renderer.render( this.scene, camera );
+			this.firstUpdate = false;
+
+		}
+
+		// Set each object's material to the UV Unwrapped Surface Mapping Version
+		for ( let l = 0; l < this.lightMapContainers.length; l ++ ) {
+
+			this.uvMat.uniforms.averagingWindow = { value: blendWindow };
+			this.lightMapContainers[ l ].object.material = this.uvMat;
+			this.lightMapContainers[ l ].object.oldFrustumCulled =
+				this.lightMapContainers[ l ].object.frustumCulled;
+			this.lightMapContainers[ l ].object.frustumCulled = false;
+
+		}
+
+		// Ping-pong two surface buffers for reading/writing
+		let activeMap = this.buffer1Active ? this.progressiveLightMap1 : this.progressiveLightMap2;
+		let inactiveMap = this.buffer1Active ? this.progressiveLightMap2 : this.progressiveLightMap1;
+
+		// Render the object's surface maps
+		this.renderer.setRenderTarget( activeMap );
+		this.uvMat.uniforms.previousShadowMap = { value: inactiveMap.texture };
+		this.blurringPlane.material.uniforms.previousShadowMap = { value: inactiveMap.texture };
+		this.buffer1Active = ! this.buffer1Active;
+		this.renderer.render( this.scene, camera );
+
+		// Restore the object's Real-time Material and add it back to the original world
+		for ( let l = 0; l < this.lightMapContainers.length; l ++ ) {
+
+			this.lightMapContainers[ l ].object.frustumCulled =
+				this.lightMapContainers[ l ].object.oldFrustumCulled;
+			this.lightMapContainers[ l ].object.material = this.lightMapContainers[ l ].basicMat;
+			this.lightMapContainers[ l ].object.oldScene.attach( this.lightMapContainers[ l ].object );
+
+		}
+
+		// Restore the original Render Target
+		this.renderer.setRenderTarget( oldTarget );
+
+	}
+
+	/** DEBUG
+	 * Draw the lightmap in the main scene.  Call this after adding the objects to it.
+	 * @param {boolean} visible Whether the debug plane should be visible
+	 * @param {Vector3} position Where the debug plane should be drawn
+	*/
+	showDebugLightmap( visible, position = undefined ) {
+
+		if ( this.lightMapContainers.length == 0 ) {
+
+			if ( ! this.warned ) {
+
+				console.warn( "Call this after adding the objects!" ); this.warned = true;
+
+			}
+
+			return;
+
+		}
+
+		if ( this.labelMesh == null ) {
+
+			this.labelMaterial = new THREE.MeshBasicMaterial(
+				{ map: this.progressiveLightMap1.texture, side: THREE.DoubleSide } );
+			this.labelPlane = new THREE.PlaneGeometry( 100, 100 );
+			this.labelMesh = new THREE.Mesh( this.labelPlane, this.labelMaterial );
+			this.labelMesh.position.y = 250;
+			this.lightMapContainers[ 0 ].object.parent.add( this.labelMesh );
+
+		}
+
+		if ( position != undefined ) {
+
+			this.labelMesh.position.copy( position );
+
+		}
+
+		this.labelMesh.visible = visible;
+
+	}
+
+	/**
+	 * INTERNAL Creates the Blurring Plane
+	 * @param {number} res The square resolution of this object's lightMap.
+	 * @param {WebGLRenderTexture} lightMap The lightmap to initialize the plane with.
+	 */
+	_initializeBlurPlane( res, lightMap = null ) {
+
+		let blurMaterial = new THREE.MeshBasicMaterial();
+		blurMaterial.uniforms = { previousShadowMap: { value: null },
+								  pixelOffset: { value: 1.0 / res },
+								  polygonOffset: true, polygonOffsetFactor: - 1, polygonOffsetUnits: 3.0 };
+		blurMaterial.onBeforeCompile = ( shader ) => {
+
+			// Vertex Shader: Set Vertex Positions to the Unwrapped UV Positions
+			shader.vertexShader =
+				'#define USE_UV\n' +
+				shader.vertexShader.slice( 0, - 1 ) +
+				'	gl_Position = vec4((uv - 0.5) * 2.0, 1.0, 1.0); }';
+
+			// Fragment Shader: Set Pixels to 9-tap box blur the current frame's Shadows
+			let bodyStart	= shader.fragmentShader.indexOf( 'void main() {' );
+			shader.fragmentShader =
+				'#define USE_UV\n' +
+				shader.fragmentShader.slice( 0, bodyStart ) +
+				'	uniform sampler2D previousShadowMap;\n	uniform float pixelOffset;\n' +
+				shader.fragmentShader.slice( bodyStart - 1, - 1 ) +
+					`	gl_FragColor.rgb = (
+									texture2D(previousShadowMap, vUv + vec2( pixelOffset,  0.0        )).rgb + 
+									texture2D(previousShadowMap, vUv + vec2( 0.0        ,  pixelOffset)).rgb +
+									texture2D(previousShadowMap, vUv + vec2( 0.0        , -pixelOffset)).rgb +
+									texture2D(previousShadowMap, vUv + vec2(-pixelOffset,  0.0        )).rgb +
+									texture2D(previousShadowMap, vUv + vec2( pixelOffset,  pixelOffset)).rgb + 
+									texture2D(previousShadowMap, vUv + vec2(-pixelOffset,  pixelOffset)).rgb +
+									texture2D(previousShadowMap, vUv + vec2( pixelOffset, -pixelOffset)).rgb +
+									texture2D(previousShadowMap, vUv + vec2(-pixelOffset, -pixelOffset)).rgb)/8.0;
+				}`;
+
+			// Set the LightMap Accumulation Buffer
+			shader.uniforms.previousShadowMap = { value: lightMap.texture };
+			shader.uniforms.pixelOffset = { value: 0.5 / res };
+			blurMaterial.uniforms = shader.uniforms;
+
+			// Set the new Shader to this
+			blurMaterial.userData.shader = shader;
+
+			this.compiled = true;
+
+		};
+
+		this.blurringPlane = new THREE.Mesh( new THREE.PlaneBufferGeometry( 1, 1 ), blurMaterial );
+		this.blurringPlane.name = "Blurring Plane";
+		this.blurringPlane.frustumCulled = false;
+		this.blurringPlane.renderOrder = 0;
+		this.blurringPlane.material.depthWrite = false;
+		this.scene.add( this.blurringPlane );
+
+	}
+
+}
+
+export { ProgressiveLightMap };

BIN
examples/models/gltf/ShadowmappableMesh.glb


BIN
examples/screenshots/webgl_shadowmap_progressive.jpg


+ 1 - 0
examples/tags.json

@@ -74,6 +74,7 @@
 	"webgl_postprocessing_dof2": [ "bokeh" ],
 	"webgl_postprocessing_fxaa": [ "msaa", "multisampled" ],
 	"webgl_postprocessing_godrays": [ "light scattering" ],
+	"webgl_shadowmap_progressive": [ "shadow", "soft", "lightmap", "onBeforeCompile" ],
 	"webgl_postprocessing_ssaa": [ "msaa", "multisampled" ],
 	"webgl_postprocessing_ssaa_unbiased": [ "msaa", "multisampled" ],
 	"webgl_postprocessing_sao": [ "ambient occlusion" ],

+ 238 - 0
examples/webgl_shadowmap_progressive.html

@@ -0,0 +1,238 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - progressive lightmap accumulation</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+		<div id="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Progressive Lightmaps by <a href="https://github.com/zalo" target="_blank" rel="noopener">zalo</a><br/>
+			[Inspired by <a href="http://madebyevan.com/shaders/lightmap/" target="_blank" rel="noopener">evanw's Lightmap Generation</a>]
+		</div>
+
+		<script type="module">
+			import * as THREE from '../build/three.module.js';
+			import { GUI } from './jsm/libs/dat.gui.module.js';
+			import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { TransformControls } from './jsm/controls/TransformControls.js';
+			import { ProgressiveLightMap } from './jsm/misc/ProgressiveLightMap.js';
+
+			// ShadowMap + LightMap Res and Number of Directional Lights
+			let shadowMapRes = 512, lightMapRes = 1024, lightCount = 8;
+			let camera, scene, renderer, dirLights = [], controls, control, control2,
+				object = new THREE.Mesh(), lightOrigin = null, progressiveSurfacemap, lightmapObjects = [];
+			const params = { 'Enable': true, 'Blur Edges': true, 'Blend Window': 200,
+							 'Light Radius': 50, 'Ambient Weight': 0.5, 'Debug Lightmap': false };
+			init();
+			createGUI();
+			animate();
+
+			function init() {
+
+				// renderer
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.shadowMap.enabled = true;
+				document.body.appendChild( renderer.domElement );
+
+				// camera
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.set( 0, 100, 200 );
+				camera.name = "Camera";
+
+				// scene
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x949494 );
+				scene.fog = new THREE.Fog( 0x949494, 1000, 3000 );
+
+				// progressive lightmap
+				progressiveSurfacemap = new ProgressiveLightMap( renderer, lightMapRes );
+
+				// directional lighting "origin"
+				lightOrigin = new THREE.Group();
+				lightOrigin.position.set( 60, 150, 100 );
+				scene.add( lightOrigin );
+
+				// transform gizmo
+				control = new TransformControls( camera, renderer.domElement );
+				control.addEventListener( 'dragging-changed', ( event ) => {
+
+					controls.enabled = ! event.value;
+
+				} );
+				control.attach( lightOrigin );
+				scene.add( control );
+
+				// create 8 directional lights to speed up the convergence
+				for ( let l = 0; l < lightCount; l ++ ) {
+
+					let dirLight = new THREE.DirectionalLight( 0xffffff, 1.0 / lightCount );
+					dirLight.name = 'Dir. Light ' + l;
+					dirLight.position.set( 200, 200, 200 );
+					dirLight.castShadow = true;
+					dirLight.shadow.camera.near = 100;
+					dirLight.shadow.camera.far = 5000;
+					dirLight.shadow.camera.right = 150;
+					dirLight.shadow.camera.left = - 150;
+					dirLight.shadow.camera.top = 150;
+					dirLight.shadow.camera.bottom = - 150;
+					dirLight.shadow.mapSize.width = shadowMapRes;
+					dirLight.shadow.mapSize.height = shadowMapRes;
+					lightmapObjects.push( dirLight );
+					dirLights.push( dirLight );
+
+				}
+
+				// ground
+				let groundMesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 600, 600 ),
+					new THREE.MeshPhongMaterial( { color: 0xffffff, depthWrite: true } ) );
+				groundMesh.position.y = - 0.1;
+				groundMesh.rotation.x = - Math.PI / 2;
+				groundMesh.name = "Ground Mesh";
+				lightmapObjects.push( groundMesh );
+				scene.add( groundMesh );
+
+				// model
+				function loadModel() {
+
+					object.traverse( function ( child ) {
+
+						if ( child.isMesh ) {
+
+							child.name = "Loaded Mesh";
+							child.castShadow = true;
+							child.receiveShadow = true;
+							child.material = new THREE.MeshPhongMaterial();
+
+							// This adds the model to the lightmap
+							lightmapObjects.push( child );
+							progressiveSurfacemap.addObjectsToLightMap( lightmapObjects );
+
+						} else {
+
+							child.layers.disableAll(); // Disable Rendering for this
+
+						}
+
+					} );
+					scene.add( object );
+					object.scale.set( 2, 2, 2 );
+					object.position.set( 0, - 16, 0 );
+					control2 = new TransformControls( camera, renderer.domElement );
+					control2.addEventListener( 'dragging-changed', ( event ) => {
+
+						controls.enabled = ! event.value;
+
+					} );
+					control2.attach( object );
+					scene.add( control2 );
+					let lightTarget = new THREE.Group();
+					lightTarget.position.set( 0, 20, 0 );
+					for ( let l = 0; l < dirLights.length; l ++ ) {
+
+						dirLights[ l ].target = lightTarget;
+
+					}
+
+					object.add( lightTarget );
+
+					if ( typeof TESTING !== 'undefined' ) { for ( let i = 0; i < 300; i ++ ) { render(); }; };
+				}
+
+				const manager = new THREE.LoadingManager( loadModel );
+				const loader = new GLTFLoader( manager );
+				loader.load( 'models/gltf/ShadowmappableMesh.glb', function ( obj ) {
+
+					object = obj.scene.children[0];
+
+				} );
+
+				// controls
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
+				controls.dampingFactor = 0.05;
+				controls.screenSpacePanning = true;
+				controls.minDistance = 100;
+				controls.maxDistance = 500;
+				controls.maxPolarAngle = Math.PI / 1.5;
+				controls.target.set( 0, 100, 0 );
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function createGUI() {
+
+				const gui = new GUI( { name: 'Accumulation Settings' } );
+				gui.add( params, 'Enable' );
+				gui.add( params, 'Blur Edges' );
+				gui.add( params, 'Blend Window', 1, 500 ).step( 1 );
+				gui.add( params, 'Light Radius', 0, 200 ).step( 10 );
+				gui.add( params, 'Ambient Weight', 0, 1 ).step( 0.1 );
+				gui.add( params, 'Debug Lightmap' );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function render() {
+
+				// Update the inertia on the orbit controls
+				controls.update();
+
+				// Accumulate Surface Maps
+				if ( params[ 'Enable' ] ) {
+
+					progressiveSurfacemap.update( camera, params[ 'Blend Window' ], params[ 'Blur Edges' ] );
+					if(!progressiveSurfacemap.firstUpdate) { progressiveSurfacemap.showDebugLightmap( params[ 'Debug Lightmap' ] ); }
+
+				}
+
+				// Manually Update the Directional Lights
+				for ( let l = 0; l < dirLights.length; l ++ ) {
+
+					// Sometimes they will be sampled from the target direction
+					// Sometimes they will be uniformly sampled from the upper hemisphere
+					if ( Math.random() > params[ 'Ambient Weight' ] ) {
+
+						dirLights[ l ].position.set( lightOrigin.position.x + ( Math.random() * params[ 'Light Radius' ] ),
+													lightOrigin.position.y + ( Math.random() * params[ 'Light Radius' ] ),
+													lightOrigin.position.z + ( Math.random() * params[ 'Light Radius' ] ) );
+
+					} else {
+
+						// Uniform Hemispherical Surface Distribution for Ambient Occlusion
+						let lambda = Math.acos( 2 * Math.random() - 1 ) - ( 3.14159 / 2.0 );
+						let phi = 2 * 3.14159 * Math.random();
+						dirLights[ l ].position.set( ( ( Math.cos( lambda ) * Math.cos( phi ) ) * 300 ) + object.position.x,
+											 Math.abs( ( Math.cos( lambda ) * Math.sin( phi ) ) * 300 ) + object.position.y + 20,
+													   ( Math.sin( lambda )                     * 300 ) + object.position.z );
+
+					}
+
+				}
+
+				// Render Scene
+				renderer.render( scene, camera );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+				render();
+
+			}
+		</script>
+	</body>
+</html>