Browse Source

GPUComputationRenderer and new GPU demos (#9043)

* Water grid simulation w/ ShaderMaterial cloned from MeshPhongMaterial

* Water shader works with texture heightmap

* Added GPUComputationRenderer, I'm about to test it

* GPUComputationRenderer works with water simulation

* Interaction with mouse

* Added water viscosity, simple geometry for mouse picking

* Put pass thru shaders in GPUComputationRenderer. Some minor refactoring.

* Code revisison, added comments and explanations. Inverted mouse Y coord.

* Added GUI controls for some water parameters.

* Commencing conversion of birds gpu demo to GPUComputationRenderer

* Converted gpu birds demo to GPUComputationRenderer and fixed bugs in it.

* Solved mouse bug.

* Optimized smoothWater(), renamed some GPUComputationRenderer methods.

* Substituted old bird gpu implementation. Preparing for new demo.

* Removing old SimulationRenderer

* GPU protoplanets demo started. Minor tweaks in the other demos

* Starting to implement the particle demo

* $th component of texture now is usable. It was the GPUComputationRenderer
pass-through fragment shader.
Next thing to do is use a texture that contains (vx, vy, vz, mass).

* Particles already move. Next is gravity interaction.

* Gravity interaction done. Next is particle collision and aggregation.

* Particle collision works

* Added gui

* Some minor adjustments

* Added the two new demos to the list of examples
Juan Jose Luna Espinosa 9 năm trước cách đây
mục cha
commit
5738e9ad2b

+ 2 - 0
examples/files.js

@@ -44,6 +44,8 @@ var files = {
 		"webgl_geometry_text_earcut",
 		"webgl_geometry_text_pnltri",
 		"webgl_gpgpu_birds",
+		"webgl_gpgpu_water",
+		"webgl_gpgpu_protoplanet",
 		"webgl_gpu_particle_system",
 		"webgl_hdr",
 		"webgl_helpers",

+ 357 - 0
examples/js/GPUComputationRenderer.js

@@ -0,0 +1,357 @@
+/**
+ * @author yomboprime https://github.com/yomboprime
+ *
+ * GPUComputationRenderer, based on SimulationRenderer by zz85
+ *
+ * The GPUComputationRenderer uses the concept of variables. These variables are RGBA float textures that hold 4 floats
+ * for each compute element (texel)
+ *
+ * Each variable has a fragment shader that defines the computation made to obtain the variable in question.
+ * You can use as many variables you need, and make dependencies so you can use textures of other variables in the shader
+ * (the sampler uniforms are added automatically) Most of the variables will need themselves as dependency.
+ *
+ * The renderer has actually two render targets per variable, to make ping-pong. Textures from the current frame are used
+ * as inputs to render the textures of the next frame.
+ *
+ * The render targets of the variables can be used as input textures for your visualization shaders.
+ *
+ * Variable names should be valid identifiers and should not collide with THREE GLSL used identifiers.
+ * a common approach could be to use 'texture' prefixing the variable name; i.e texturePosition, textureVelocity...
+ *
+ * The size of the computation (sizeX * sizeY) is defined as 'resolution' automatically in the shader. For example:
+ * #DEFINE resolution vec2( 1024.0, 1024.0 )
+ *
+ * -------------
+ *
+ * Basic use:
+ *
+ * // Initialization...
+ *
+ * // Create computation renderer
+ * var gpuCompute = new GPUComputationRenderer( 1024, 1024, renderer );
+ *
+ * // Create initial state float textures
+ * var pos0 = gpuCompute.createTexture();
+ * var vel0 = gpuCompute.createTexture();
+ * // and fill in here the texture data...
+ *
+ * // Add texture variables
+ * var velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, pos0 );
+ * var posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, vel0 );
+ *
+ * // Add variable dependencies
+ * gpuCompute.setVariableDependencies( velVar, [ velVar, posVar ] );
+ * gpuCompute.setVariableDependencies( posVar, [ velVar, posVar ] );
+ *
+ * // Add custom uniforms
+ * velVar.material.uniforms.time = { type: "f", value: 0.0 };
+ *
+ * // Check for completeness
+ * var error = gpuCompute.init();
+ * if ( error !== null ) {
+ *		console.error( error );
+  * }
+ *
+ *
+ * // In each frame...
+ *
+ * // Compute!
+ * gpuCompute.compute();
+ *
+ * // Update texture uniforms in your visualization materials with the gpu renderer output
+ * myMaterial.uniforms.myTexture.value = gpuCompute.getCurrentRenderTarget( posVar ).texture;
+ *
+ * // Do your rendering
+ * renderer.render( myScene, myCamera );
+ *
+ * -------------
+ *
+ * Also, you can use utility functions to create ShaderMaterial and perform computations (rendering between textures)
+ * Note that the shaders can have multiple input textures.
+ *
+ * var myFilter1 = gpuCompute.createShaderMaterial( myFilterFragmentShader1, { theTexture: { type: "t", value: null } } );
+ * var myFilter2 = gpuCompute.createShaderMaterial( myFilterFragmentShader2, { theTexture: { type: "t", value: null } } );
+ *
+ * var inputTexture = gpuCompute.createTexture();
+ *
+ * // Fill in here inputTexture...
+ *
+ * myFilter1.uniforms.theTexture.value = inputTexture;
+ *
+ * var myRenderTarget = gpuCompute.createRenderTarget();
+ * myFilter2.uniforms.theTexture.value = myRenderTarget.texture;
+ *
+ * var outputRenderTarget = gpuCompute.createRenderTarget();
+ *
+ * // Now use the output texture where you want:
+ * myMaterial.uniforms.map.value = outputRenderTarget.texture;
+ *
+ * // And compute each frame, before rendering to screen:
+ * gpuCompute.doRenderTarget( myFilter1, myRenderTarget );
+ * gpuCompute.doRenderTarget( myFilter2, outputRenderTarget );
+ * 
+ *
+ *
+ * @param {int} sizeX Computation problem size is always 2d: sizeX * sizeY elements.
+ * @param {int} sizeY Computation problem size is always 2d: sizeX * sizeY elements.
+ * @param {WebGLRenderer} renderer The renderer
+  */
+
+function GPUComputationRenderer( sizeX, sizeY, renderer ) {
+
+	this.variables = [];
+
+	this.currentTextureIndex = 0;
+
+	var scene = new THREE.Scene();
+
+	var camera = new THREE.Camera();
+	camera.position.z = 1;
+
+	var passThruUniforms = {
+		texture: { type: "t", value: null }
+	};
+
+	var passThruShader = createShaderMaterial( getPassThroughFragmentShader(), passThruUniforms );
+
+	var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), passThruShader );
+	scene.add( mesh );
+
+
+	this.addVariable = function( variableName, computeFragmentShader, initialValueTexture ) {
+
+		var material = this.createShaderMaterial( computeFragmentShader );
+
+		var variable = {
+			name: variableName,
+			initialValueTexture: initialValueTexture,
+			material: material,
+			dependencies: null,
+			renderTargets: [],
+			wrapS: null,
+			wrapT: null
+		};
+
+		this.variables.push( variable );
+
+		return variable;
+		
+	};
+
+	this.setVariableDependencies = function( variable, dependencies ) {
+
+		variable.dependencies = dependencies;
+
+	};
+
+	this.init = function() {
+
+		if ( ! renderer.extensions.get( "OES_texture_float" ) ) {
+
+			return "No OES_texture_float support for float textures.";
+
+		}
+
+		if ( renderer.capabilities.maxVertexTextures === 0 ) {
+
+			return "No support for vertex shader textures.";
+
+		}
+
+		for ( var i = 0; i < this.variables.length; i++ ) {
+
+			var variable = this.variables[ i ];
+
+			// Creates rendertargets and initialize them with input texture
+			variable.renderTargets[ 0 ] = this.createRenderTarget( variable.wrapS, variable.wrapT );
+			variable.renderTargets[ 1 ] = this.createRenderTarget( variable.wrapS, variable.wrapT );
+			this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 0 ] );
+			this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 1 ] );
+
+			// Adds dependencies uniforms to the ShaderMaterial
+			var material = variable.material;
+			var uniforms = material.uniforms;
+			if ( variable.dependencies !== null ) {
+				for ( var d = 0; d < variable.dependencies.length; d++ ) {
+
+					var depVar = variable.dependencies[ d ];
+
+					if ( depVar.name !== variable.name ) {
+
+						// Checks if variable exists
+						var found = false;
+						for ( var j = 0; j < this.variables.length; j++ ) {
+
+							if ( depVar.name === this.variables[ j ].name ) {
+								found = true;
+								break;
+							}
+
+						}
+						if ( ! found ) {
+							return "Variable dependency not found. Variable=" + variable.name + ", dependency=" + depVar.name;
+						}
+
+					}
+
+					uniforms[ depVar.name ] = { type: "t", value: null };
+
+					material.fragmentShader = "\nuniform sampler2D " + depVar.name + ";\n" + variable.material.fragmentShader;
+
+				}
+			}
+		}
+
+		this.currentTextureIndex = 0;
+
+		return null;
+
+	};
+
+	this.compute = function() {
+
+		var currentTextureIndex = this.currentTextureIndex;
+		var nextTextureIndex = this.currentTextureIndex === 0 ? 1 : 0;
+
+		for ( var i = 0; i < this.variables.length; i++ ) {
+
+			var variable = this.variables[ i ];
+
+			// Sets texture dependencies uniforms
+			var uniforms = variable.material.uniforms;
+			for ( var d = 0; d < variable.dependencies.length; d++ ) {
+
+				var depVar = variable.dependencies[ d ];
+
+				uniforms[ depVar.name ].value = depVar.renderTargets[ currentTextureIndex ].texture;
+
+			}
+
+			// Performs the computation for this variable
+			this.doRenderTarget( variable.material, variable.renderTargets[ nextTextureIndex ] );
+
+		}
+
+		this.currentTextureIndex = nextTextureIndex;
+	};
+
+	this.getCurrentRenderTarget = function( variable ) {
+
+		return variable.renderTargets[ this.currentTextureIndex ];
+
+	};
+
+	this.getAlternateRenderTarget = function( variable ) {
+
+		return variable.renderTargets[ this.currentTextureIndex === 0 ? 1 : 0 ];
+
+	};
+
+	function addResolutionDefine( materialShader ) {
+
+		materialShader.defines.resolution = 'vec2( ' + sizeX.toFixed( 1 ) + ', ' + sizeY.toFixed( 1 ) + " )";
+
+	};
+	this.addResolutionDefine = addResolutionDefine;
+
+
+	// The following functions can be used to compute things manually
+
+	function createShaderMaterial( computeFragmentShader, uniforms ) {
+
+		uniforms = uniforms || {};
+
+		var material = new THREE.ShaderMaterial( {
+			uniforms: uniforms,
+			vertexShader: getPassThroughVertexShader(),
+			fragmentShader: computeFragmentShader
+		} );
+
+		addResolutionDefine( material );
+
+		return material;
+	};
+	this.createShaderMaterial = createShaderMaterial;
+
+	this.createRenderTarget = function( wrapS, wrapT, sizeXTexture, sizeYTexture ) {
+		
+		wrapS = wrapS || THREE.ClampToEdgeWrapping;
+		wrapT = wrapT || THREE.ClampToEdgeWrapping;
+
+		sizeXTexture = sizeXTexture || sizeX;
+		sizeYTexture = sizeYTexture || sizeY;
+
+		var renderTarget = new THREE.WebGLRenderTarget( sizeXTexture, sizeYTexture, {
+			wrapS: wrapS,
+			wrapT: wrapT,
+			minFilter: THREE.NearestFilter,
+			magFilter: THREE.NearestFilter,
+			format: THREE.RGBAFormat,
+			type: THREE.FloatType,
+			stencilBuffer: false
+		} );
+
+		return renderTarget;
+
+	};
+
+    this.createTexture = function( sizeXTexture, sizeYTexture ) {
+
+		sizeXTexture = sizeXTexture || sizeX;
+		sizeYTexture = sizeYTexture || sizeY;
+
+		var a = new Float32Array( sizeXTexture * sizeYTexture * 4 );
+		var texture = new THREE.DataTexture( a, sizeX, sizeY, THREE.RGBAFormat, THREE.FloatType );
+		texture.needsUpdate = true;
+
+		return texture;
+
+	};
+
+
+	this.renderTexture = function( input, output ) {
+
+		// Takes a texture, and render out in rendertarget
+		// input = Texture
+		// output = RenderTarget
+
+		passThruUniforms.texture.value = input;
+
+		this.doRenderTarget( passThruShader, output);
+
+	};
+
+	this.doRenderTarget = function( material, output ) {
+
+		mesh.material = material;
+		renderer.render( scene, camera, output );
+
+	};
+
+	// Shaders
+
+	function getPassThroughVertexShader() {
+
+		return	"void main()	{\n" +
+				"\n" +
+				"	gl_Position = vec4( position, 1.0 );\n" +
+				"\n" +
+				"}\n";
+
+	}
+
+	function getPassThroughFragmentShader() {
+
+		return	"uniform sampler2D texture;\n" +
+				"\n" +
+				"void main() {\n" +
+				"\n" +
+				"	vec2 uv = gl_FragCoord.xy / resolution.xy;\n" +
+				"\n" +
+				"	gl_FragColor = texture2D( texture, uv );\n" +
+				"\n" +
+				"}\n";
+
+	}
+
+}

+ 0 - 233
examples/js/SimulationRenderer.js

@@ -1,233 +0,0 @@
-/**
- * @author zz85 https://github.com/zz85 / http://www.lab4games.net/zz85/blog
- *
- * Bird Simulation Render
- *
- * 	A simple scene rendering a quad of the following shaders
- *	1. Pass-thru Shader,
- *	2. Bird Position Update Shader,
- *	3. Bird Velocity Update Shader
- *
- */
-
-function SimulationRenderer( WIDTH, renderer ) {
-
-	WIDTH = WIDTH || 4;
-	var camera = new THREE.Camera();
-	camera.position.z = 1;
-
-	if ( ! renderer.extensions.get( "OES_texture_float" ) ) {
-
-		alert( "No OES_texture_float support for float textures!" );
-		return;
-
-	}
-
-	if ( renderer.capabilities.maxVertexTextures === 0 ) {
-
-		alert( "No support for vertex shader textures!" );
-		return;
-
-	}
-
-	var scene = new THREE.Scene();
-
-	var uniforms = {
-		time: { type: "f", value: 1.0 },
-		resolution: { type: "v2", value: new THREE.Vector2( WIDTH, WIDTH ) },
-		texture: { type: "t", value: null }
-	};
-
-	var passThruShader = new THREE.ShaderMaterial( {
-		uniforms: uniforms,
-		vertexShader: document.getElementById( 'vertexShader' ).textContent,
-		fragmentShader: document.getElementById( 'fragmentShader' ).textContent
-	} );
-
-	var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), passThruShader );
-
-	var positionShader = new THREE.ShaderMaterial( {
-
-		uniforms: {
-			time: { type: "f", value: 1.0 },
-			delta: { type: "f", value: 0.0 },
-			resolution: { type: "v2", value: new THREE.Vector2( WIDTH, WIDTH ) },
-			texturePosition: { type: "t", value: null },
-			textureVelocity: { type: "t", value: null },
-		},
-		vertexShader: document.getElementById( 'vertexShader' ).textContent,
-		fragmentShader: document.getElementById( 'fragmentShaderPosition' ).textContent
-
-	} );
-
-	this.positionShader = positionShader;
-
-	var velocityShader = new THREE.ShaderMaterial( {
-
-		uniforms: {
-			time: { type: "f", value: 1.0 },
-			delta: { type: "f", value: 0.0 },
-			resolution: { type: "v2", value: new THREE.Vector2( WIDTH, WIDTH ) },
-			texturePosition: { type: "t", value: null },
-			textureVelocity: { type: "t", value: null },
-			testing: { type: "f", value: 1.0 },
-			seperationDistance: { type: "f", value: 1.0 },
-			alignmentDistance: { type: "f", value: 1.0 },
-			cohesionDistance: { type: "f", value: 1.0 },
-			freedomFactor: { type: "f", value: 1.0 },
-			predator: { type: "v3", value: new THREE.Vector3() }
-		},
-		defines: {
-			WIDTH: WIDTH.toFixed( 2 )
-		},
-		vertexShader: document.getElementById( 'vertexShader' ).textContent,
-		fragmentShader: document.getElementById( 'fragmentShaderVelocity' ).textContent
-
-	} );
-
-	this.velocityUniforms = velocityShader.uniforms;
-
-	scene.add( mesh );
-
-	var flipflop = true;
-	var rtPosition1, rtPosition2, rtVelocity1, rtVelocity2;
-
-	function init() {
-
-		var dtPosition = generatePositionTexture();
-		var dtVelocity = generateVelocityTexture();
-
-		rtPosition1 = getRenderTarget( THREE.RGBAFormat );
-		rtPosition2 = rtPosition1.clone();
-		rtVelocity1 = getRenderTarget( THREE.RGBAFormat );
-		rtVelocity2 = rtVelocity1.clone();
-
-		simulator.renderTexture( dtPosition, rtPosition1 );
-		simulator.renderTexture( rtPosition1.texture, rtPosition2 );
-
-		simulator.renderTexture( dtVelocity, rtVelocity1 );
-		simulator.renderTexture( rtVelocity1.texture, rtVelocity2 );
-
-		simulator.velocityUniforms.testing.value = 10;
-
-	}
-
-	this.init = init;
-
-	function getRenderTarget( type ) {
-
-		var renderTarget = new THREE.WebGLRenderTarget( WIDTH, WIDTH, {
-			wrapS: THREE.RepeatWrapping,
-			wrapT: THREE.RepeatWrapping,
-			minFilter: THREE.NearestFilter,
-			magFilter: THREE.NearestFilter,
-			format: type,
-			type: THREE.FloatType,
-			stencilBuffer: false
-		} );
-
-		return renderTarget;
-
-	}
-
-	// Takes a texture, and render out as another texture
-	this.renderTexture = function ( input, output ) {
-
-		mesh.material = passThruShader;
-		uniforms.texture.value = input;
-		renderer.render( scene, camera, output );
-
-	};
-
-
-	this.renderPosition = function( position, velocity, output, delta ) {
-
-		mesh.material = positionShader;
-		positionShader.uniforms.texturePosition.value = position;
-		positionShader.uniforms.textureVelocity.value = velocity;
-		positionShader.uniforms.time.value = performance.now();
-		positionShader.uniforms.delta.value = delta;
-		renderer.render( scene, camera, output );
-		this.currentPosition = output.texture;
-
-	};
-
-	this.renderVelocity = function( position, velocity, output, delta ) {
-
-		mesh.material = velocityShader;
-		velocityShader.uniforms.texturePosition.value = position;
-		velocityShader.uniforms.textureVelocity.value = velocity;
-		velocityShader.uniforms.time.value = performance.now();
-		velocityShader.uniforms.delta.value = delta;
-		renderer.render( scene, camera, output );
-		this.currentVelocity = output.texture;
-
-	};
-
-	this.simulate = function( delta ) {
-
-		if ( flipflop ) {
-
-			simulator.renderVelocity( rtPosition1.texture, rtVelocity1.texture, rtVelocity2, delta );
-			simulator.renderPosition( rtPosition1.texture, rtVelocity2.texture, rtPosition2, delta );
-
-		} else {
-
-			simulator.renderVelocity( rtPosition2.texture, rtVelocity2.texture, rtVelocity1, delta );
-			simulator.renderPosition( rtPosition2.texture, rtVelocity1.texture, rtPosition1, delta );
-
-		}
-
-		flipflop = ! flipflop;
-
-	};
-
-	function generatePositionTexture() {
-
-		var a = new Float32Array( PARTICLES * 4 );
-
-		for ( var k = 0, kl = a.length; k < kl; k += 4 ) {
-
-			var x = Math.random() * BOUNDS - BOUNDS_HALF;
-			var y = Math.random() * BOUNDS - BOUNDS_HALF;
-			var z = Math.random() * BOUNDS - BOUNDS_HALF;
-
-			a[ k + 0 ] = x;
-			a[ k + 1 ] = y;
-			a[ k + 2 ] = z;
-			a[ k + 3 ] = 1;
-
-		}
-
-		var texture = new THREE.DataTexture( a, WIDTH, WIDTH, THREE.RGBAFormat, THREE.FloatType );
-		texture.needsUpdate = true;
-
-		return texture;
-
-	}
-
-	function generateVelocityTexture() {
-
-		var a = new Float32Array( PARTICLES * 4 );
-
-		for ( var k = 0, kl = a.length; k < kl; k += 4 ) {
-
-			var x = Math.random() - 0.5;
-			var y = Math.random() - 0.5;
-			var z = Math.random() - 0.5;
-
-			a[ k + 0 ] = x * 10;
-			a[ k + 1 ] = y * 10;
-			a[ k + 2 ] = z * 10;
-			a[ k + 3 ] = 1;
-
-		}
-
-		var texture = new THREE.DataTexture( a, WIDTH, WIDTH, THREE.RGBAFormat, THREE.FloatType ); // was RGB format. changed to RGBA format. see discussion in #8415 / #8450
-		texture.needsUpdate = true;
-
-		return texture;
-
-	}
-
-}

+ 105 - 58
examples/webgl_gpgpu_birds.html

@@ -43,56 +43,20 @@
 		<script src="js/libs/stats.min.js"></script>
 		<script src="js/libs/dat.gui.min.js"></script>
 
-		<script src="js/SimulationRenderer.js"></script>
+		<script src="js/GPUComputationRenderer.js"></script>
 
 		<!--
 		TODO: If you're reading this, you may wish to improve this example by
 			- Create a better shading for the birds?
-			- Refactoring the SimulationRenderer to a more generic TextureRenderer / making the GPGPU workflow easier?
 
 		-->
 
 
-
-		<!-- pass through vertex shader -->
-		<script id="vertexShader" type="x-shader/x-vertex">
-
-			void main()	{
-
-				gl_Position = vec4( position, 1.0 );
-
-			}
-
-		</script>
-
-		<!-- pass through fragment shader -->
-		<script id="fragmentShader" type="x-shader/x-fragment">
-
-			uniform vec2 resolution;
-			uniform float time;
-			uniform sampler2D texture;
-
-			void main()	{
-
-				vec2 uv = gl_FragCoord.xy / resolution.xy;
-
-				vec3 color = texture2D( texture, uv ).xyz;
-
-				gl_FragColor = vec4( color, 1.0 );
-
-			}
-
-		</script>
-		<!-- end pass through shaders -->
-
 		<!-- shader for bird's position -->
 		<script id="fragmentShaderPosition" type="x-shader/x-fragment">
 
-			uniform vec2 resolution;
 			uniform float time;
 			uniform float delta;
-			uniform sampler2D textureVelocity;
-			uniform sampler2D texturePosition;
 
 			void main()	{
 
@@ -116,7 +80,6 @@
 		<!-- shader for bird's velocity -->
 		<script id="fragmentShaderVelocity" type="x-shader/x-fragment">
 
-			uniform vec2 resolution;
 			uniform float time;
 			uniform float testing;
 			uniform float delta; // about 0.016
@@ -126,12 +89,8 @@
 			uniform float freedomFactor;
 			uniform vec3 predator;
 
-
-			uniform sampler2D textureVelocity;
-			uniform sampler2D texturePosition;
-
-			const float width = WIDTH;
-			const float height = WIDTH;
+			const float width = resolution.x;
+			const float height = resolution.y;
 
 			const float PI = 3.141592653589793;
 			const float PI_2 = PI * 2.0;
@@ -143,7 +102,7 @@
 			float separationThresh = 0.45;
 			float alignmentThresh = 0.65;
 
-			const float UPPER_BOUNDS = 400.0;
+			const float UPPER_BOUNDS = BOUNDS;
 			const float LOWER_BOUNDS = -UPPER_BOUNDS;
 
 			const float SPEED_LIMIT = 9.0;
@@ -465,10 +424,9 @@
 			var windowHalfX = window.innerWidth / 2;
 			var windowHalfY = window.innerHeight / 2;
 
-			var PARTICLES = WIDTH * WIDTH;
 			var BOUNDS = 800, BOUNDS_HALF = BOUNDS / 2;
 
-			document.getElementById('birds').innerText = PARTICLES;
+			document.getElementById('birds').innerText = BIRDS;
 
 			function change(n) {
 				location.hash = n;
@@ -486,7 +444,12 @@
 
 			var last = performance.now();
 
-			var simulator;
+			var gpuCompute;
+			var velocityVariable;
+			var positionVariable;
+			var positionUniforms;
+			var velocityUniforms;
+			var birdUniforms;
 
 			init();
 			animate();
@@ -509,8 +472,7 @@
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				container.appendChild( renderer.domElement );
 
-				simulator = new SimulationRenderer(WIDTH, renderer);
-				simulator.init();
+				initComputeRenderer();
 
 				stats = new Stats();
 				container.appendChild( stats.dom );
@@ -537,10 +499,10 @@
 
 				var valuesChanger = function() {
 
-					simulator.velocityUniforms.seperationDistance.value = effectController.seperation;
-					simulator.velocityUniforms.alignmentDistance.value = effectController.alignment;
-					simulator.velocityUniforms.cohesionDistance.value = effectController.cohesion;
-					simulator.velocityUniforms.freedomFactor.value = effectController.freedom;
+					velocityUniforms.seperationDistance.value = effectController.seperation;
+					velocityUniforms.alignmentDistance.value = effectController.alignment;
+					velocityUniforms.cohesionDistance.value = effectController.cohesion;
+					velocityUniforms.freedomFactor.value = effectController.freedom;
 
 				};
 
@@ -556,6 +518,47 @@
 
 			}
 
+			function initComputeRenderer() {
+
+    				gpuCompute = new GPUComputationRenderer( WIDTH, WIDTH, renderer );
+
+				var dtPosition = gpuCompute.createTexture();
+				var dtVelocity = gpuCompute.createTexture();
+				fillPositionTexture( dtPosition );
+				fillVelocityTexture( dtVelocity );
+
+				velocityVariable = gpuCompute.addVariable( "textureVelocity", document.getElementById( 'fragmentShaderVelocity' ).textContent, dtVelocity );
+				positionVariable = gpuCompute.addVariable( "texturePosition", document.getElementById( 'fragmentShaderPosition' ).textContent, dtPosition );
+
+				gpuCompute.setVariableDependencies( velocityVariable, [ positionVariable, velocityVariable ] );
+				gpuCompute.setVariableDependencies( positionVariable, [ positionVariable, velocityVariable ] );
+
+				positionUniforms = positionVariable.material.uniforms;
+				velocityUniforms = velocityVariable.material.uniforms;
+
+				positionUniforms.time = { type: "f", value: 0.0 };
+				positionUniforms.delta = { type: "f", value: 0.0 };
+				velocityUniforms.time = { type: "f", value: 1.0 };
+				velocityUniforms.delta = { type: "f", value: 0.0 };
+				velocityUniforms.testing = { type: "f", value: 1.0 };
+				velocityUniforms.seperationDistance = { type: "f", value: 1.0 };
+				velocityUniforms.alignmentDistance = { type: "f", value: 1.0 };
+				velocityUniforms.cohesionDistance = { type: "f", value: 1.0 };
+				velocityUniforms.freedomFactor = { type: "f", value: 1.0 };
+				velocityUniforms.predator = { type: "v3", value: new THREE.Vector3() };
+				velocityVariable.material.defines.BOUNDS = BOUNDS.toFixed( 2 );
+
+				velocityVariable.wrapS = THREE.RepeatWrapping;
+				velocityVariable.wrapT = THREE.RepeatWrapping;
+				positionVariable.wrapS = THREE.RepeatWrapping;
+				positionVariable.wrapT = THREE.RepeatWrapping;
+
+				var error = gpuCompute.init();
+				if ( error !== null ) {
+				    console.error( error );
+				}
+
+			}
 
 			function initBirds() {
 
@@ -588,6 +591,45 @@
 
 			}
 
+			function fillPositionTexture( texture ) {
+
+				var theArray = texture.image.data;
+
+				for ( var k = 0, kl = theArray.length; k < kl; k += 4 ) {
+
+					var x = Math.random() * BOUNDS - BOUNDS_HALF;
+					var y = Math.random() * BOUNDS - BOUNDS_HALF;
+					var z = Math.random() * BOUNDS - BOUNDS_HALF;
+
+					theArray[ k + 0 ] = x;
+					theArray[ k + 1 ] = y;
+					theArray[ k + 2 ] = z;
+					theArray[ k + 3 ] = 1;
+
+				}
+
+			}
+
+			function fillVelocityTexture( texture ) {
+
+				var theArray = texture.image.data;
+
+				for ( var k = 0, kl = theArray.length; k < kl; k += 4 ) {
+
+					var x = Math.random() - 0.5;
+					var y = Math.random() - 0.5;
+					var z = Math.random() - 0.5;
+
+					theArray[ k + 0 ] = x * 10;
+					theArray[ k + 1 ] = y * 10;
+					theArray[ k + 2 ] = z * 10;
+					theArray[ k + 3 ] = 1;
+
+				}
+
+			}
+
+
 			function onWindowResize() {
 
 				windowHalfX = window.innerWidth / 2;
@@ -652,17 +694,22 @@
 				if (delta > 1) delta = 1; // safety cap on large deltas
 				last = now;
 
+				positionUniforms.time.value = now;
+				positionUniforms.delta.value = delta;
+				velocityUniforms.time.value = now;
+				velocityUniforms.delta.value = delta;
 				birdUniforms.time.value = now;
 				birdUniforms.delta.value = delta;
 
-				simulator.simulate( delta );
-				simulator.velocityUniforms.predator.value.set( mouseX / windowHalfX, -mouseY / windowHalfY, 0 );
+				velocityUniforms.predator.value.set( 0.5 * mouseX / windowHalfX, - 0.5 * mouseY / windowHalfY, 0 );
 
 				mouseX = 10000;
 				mouseY = 10000;
 
-				birdUniforms.texturePosition.value = simulator.currentPosition;
-				birdUniforms.textureVelocity.value = simulator.currentVelocity;
+				gpuCompute.compute();
+
+				birdUniforms.texturePosition.value = gpuCompute.getCurrentRenderTarget( positionVariable ).texture;
+				birdUniforms.textureVelocity.value = gpuCompute.getCurrentRenderTarget( velocityVariable ).texture;
 
 				renderer.render( scene, camera );
 

+ 593 - 0
examples/webgl_gpgpu_protoplanet.html

@@ -0,0 +1,593 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - gpgpu - protoplanet</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 {
+				background-color: #000000;
+				margin: 0px;
+				overflow: hidden;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+				text-align:center;
+				cursor: pointer;
+			}
+
+			a {
+				color:#0078ff;
+			}
+
+			#info {
+				color: #ffffff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+			}
+
+		</style>
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">three.js</a> - <span id="protoplanets"></span> webgl gpgpu debris<br/>
+			Select <span id="options"></span> debris<br/>
+
+		</div>
+
+		<script src="../build/three.js"></script>
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+
+		<script src="js/GPUComputationRenderer.js"></script>
+
+
+		<!-- Fragment shader for protoplanet's position -->
+		<script id="computeShaderPosition" type="x-shader/x-fragment">
+
+			#define delta ( 1.0 / 60.0 )
+
+			void main()	{
+
+				vec2 uv = gl_FragCoord.xy / resolution.xy;
+
+				vec4 tmpPos = texture2D( texturePosition, uv );
+				vec3 pos = tmpPos.xyz;
+
+				vec4 tmpVel = texture2D( textureVelocity, uv );
+				vec3 vel = tmpVel.xyz;
+				float mass = tmpVel.w;
+
+				if ( mass == 0.0 ) {
+				    vel = vec3( 0.0 );
+				}
+
+				// Dynamics
+			        pos += vel * delta;
+
+				gl_FragColor = vec4( pos, 1.0 );
+
+			}
+
+		</script>
+
+		<!-- Fragment shader for protoplanet's velocity -->
+		<script id="computeShaderVelocity" type="x-shader/x-fragment">
+
+			// For PI declaration:
+			#include <common>
+
+			#define delta ( 1.0 / 60.0 )
+
+			uniform float gravityConstant;
+			uniform float density;
+
+			const float width = resolution.x;
+			const float height = resolution.y;
+
+			float radiusFromMass( float mass ) {
+				// Calculate radius of a sphere from mass and density
+				return pow( ( 3.0 / ( 4.0 * PI ) ) * mass / density, 1.0 / 3.0 );
+			}
+
+			void main()	{
+
+				vec2 uv = gl_FragCoord.xy / resolution.xy;
+				float idParticle = uv.y * resolution.x + uv.x;
+
+				vec4 tmpPos = texture2D( texturePosition, uv );
+				vec3 pos = tmpPos.xyz;
+
+				vec4 tmpVel = texture2D( textureVelocity, uv );
+				vec3 vel = tmpVel.xyz;
+				float mass = tmpVel.w;
+
+				if ( mass > 0.0 ) {
+				    
+
+				float radius = radiusFromMass( mass );
+
+				vec3 acceleration = vec3( 0.0 );
+
+				// Gravity interaction
+				for ( float y = 0.0; y < height; y++ ) {
+
+					for ( float x = 0.0; x < width; x++ ) {
+
+						vec2 secondParticleCoords = vec2( x + 0.5, y + 0.5 ) / resolution.xy;
+						vec3 pos2 = texture2D( texturePosition, secondParticleCoords ).xyz;
+						vec4 velTemp2 = texture2D( textureVelocity, secondParticleCoords );
+						vec3 vel2 = velTemp2.xyz;
+						float mass2 = velTemp2.w;
+
+						float idParticle2 = secondParticleCoords.y * resolution.x + secondParticleCoords.x;
+
+						if ( idParticle == idParticle2 ) {
+							continue;
+						}
+
+						if ( mass2 == 0.0 ) {
+							continue;
+						}
+
+						vec3 dPos = pos2 - pos;
+						float distance = length( dPos );
+						float radius2 = radiusFromMass( mass2 );
+
+						if ( distance == 0.0 ) {
+							continue;
+						}
+
+						// Checks collision
+
+						if ( distance < radius + radius2 ) {
+						    
+							if ( idParticle < idParticle2 ) {
+
+								// This particle is aggregated by the other
+								vel = ( vel * mass + vel2 * mass2 ) / ( mass + mass2 );
+								mass += mass2;
+								radius = radiusFromMass( mass );
+
+							}
+							else {
+
+								// This particle dies
+								mass = 0.0;
+								radius = 0.0;
+								vel = vec3( 0.0 );
+								break;
+
+							}
+		    
+						}
+
+						float distanceSq = distance * distance;
+
+						float gravityField = gravityConstant * mass2 / distanceSq;
+
+						gravityField = min( gravityField, 1000.0 );
+
+						acceleration += gravityField * normalize( dPos );
+
+					}
+
+					if ( mass == 0.0 ) {
+					    break;
+					}
+				}
+
+				// Dynamics
+				vel += delta * acceleration;
+
+				}
+
+				gl_FragColor = vec4( vel, mass );
+
+			}
+
+		</script>
+
+		<!-- Particles vertex shader -->
+		<script type="x-shader/x-vertex" id="particleVertexShader">
+
+			// For PI declaration:
+			#include <common>
+
+			uniform sampler2D texturePosition;
+			uniform sampler2D textureVelocity;
+
+			uniform float cameraConstant;
+			uniform float density;
+
+			varying vec4 vColor;
+
+			float radiusFromMass( float mass ) {
+				// Calculate radius of a sphere from mass and density
+				return pow( ( 3.0 / ( 4.0 * PI ) ) * mass / density, 1.0 / 3.0 );
+			}
+
+
+			void main() {
+
+
+				vec4 posTemp = texture2D( texturePosition, uv );
+				vec3 pos = posTemp.xyz;
+
+				vec4 velTemp = texture2D( textureVelocity, uv );
+				vec3 vel = velTemp.xyz;
+				float mass = velTemp.w;
+
+				vColor = vec4( 1.0, mass / 250.0, 0.0, 1.0 );
+
+				vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );
+
+				// Calculate radius of a sphere from mass and density
+				//float radius = pow( ( 3.0 / ( 4.0 * PI ) ) * mass / density, 1.0 / 3.0 );
+				float radius = radiusFromMass( mass );
+
+				// Apparent size in pixels
+				if ( mass == 0.0 ) {
+					gl_PointSize = 0.0;
+				}
+				else {
+					gl_PointSize = radius * cameraConstant / ( - mvPosition.z );
+				}
+
+				gl_Position = projectionMatrix * mvPosition;
+
+			}
+
+		</script>
+
+		<!-- Particles fragment shader -->
+		<script type="x-shader/x-fragment" id="particleFragmentShader">
+
+			varying vec4 vColor;
+
+			void main() {
+
+				float f = length( gl_PointCoord - vec2( 0.5, 0.5 ) );
+				if ( f > 0.5 ) {
+				    discard;
+				}
+				gl_FragColor = vColor;
+
+			}
+
+		</script>
+
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var hash = document.location.hash.substr( 1 );
+			if ( hash ) hash = parseInt( hash, 0 );
+
+			// Texture width for simulation (each texel is a debris particle)
+			var WIDTH = hash || 64;
+
+			var container, stats;
+			var camera, scene, renderer, geometry, controls;
+
+			var PARTICLES = WIDTH * WIDTH;
+
+			document.getElementById( 'protoplanets' ).innerText = PARTICLES;
+
+			function change(n) {
+				location.hash = n;
+				location.reload();
+				return false;
+			}
+
+
+			var options = '';
+			for ( var i = 1; i < 8; i++ ) {
+				var j = Math.pow( 2, i );
+				options += '<a href="#" onclick="return change(' + j + ')">' + ( j * j ) + '</a> ';
+			}
+			document.getElementById( 'options' ).innerHTML = options;
+
+			var last = performance.now();
+
+			var gpuCompute;
+			var velocityVariable;
+			var positionVariable;
+			var positionUniforms;
+			var velocityUniforms;
+			var particleUniforms;
+			var effectController;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 5, 15000 );
+				camera.position.y = 120;
+				camera.position.z = 400;
+
+				scene = new THREE.Scene();
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setClearColor( 0x000000 );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				controls = new THREE.OrbitControls( camera, renderer.domElement );
+
+				effectController = {
+					// Can be changed dynamically
+					gravityConstant: 100.0,
+					density: 0.45,
+
+					// Must restart simulation
+					radius: 300,
+					height: 8,
+					exponent: 0.4,
+					maxMass: 15.0,
+					velocity: 70,
+					velocityExponent: 0.2,
+					randVelocity: 0.001
+
+				};
+
+				initComputeRenderer();
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				initGUI();
+
+				initProtoplanets();
+
+				dynamicValuesChanger();
+
+			}
+
+			function initComputeRenderer() {
+
+    				gpuCompute = new GPUComputationRenderer( WIDTH, WIDTH, renderer );
+
+				var dtPosition = gpuCompute.createTexture();
+				var dtVelocity = gpuCompute.createTexture();
+
+				fillTextures( dtPosition, dtVelocity );
+
+				velocityVariable = gpuCompute.addVariable( "textureVelocity", document.getElementById( 'computeShaderVelocity' ).textContent, dtVelocity );
+				positionVariable = gpuCompute.addVariable( "texturePosition", document.getElementById( 'computeShaderPosition' ).textContent, dtPosition );
+
+				gpuCompute.setVariableDependencies( velocityVariable, [ positionVariable, velocityVariable ] );
+				gpuCompute.setVariableDependencies( positionVariable, [ positionVariable, velocityVariable ] );
+
+				positionUniforms = positionVariable.material.uniforms;
+				velocityUniforms = velocityVariable.material.uniforms;
+
+				velocityUniforms.gravityConstant = { type: "f", value: 0.0 };
+				velocityUniforms.density = { type: "f", value: 0.0 };
+
+				var error = gpuCompute.init();
+				if ( error !== null ) {
+				    console.error( error );
+				}
+
+			}
+
+			function restartSimulation() {
+
+				var dtPosition = gpuCompute.createTexture();
+				var dtVelocity = gpuCompute.createTexture();
+
+				fillTextures( dtPosition, dtVelocity );
+
+				gpuCompute.renderTexture( dtPosition, positionVariable.renderTargets[ 0 ] );
+				gpuCompute.renderTexture( dtPosition, positionVariable.renderTargets[ 1 ] );
+				gpuCompute.renderTexture( dtVelocity, velocityVariable.renderTargets[ 0 ] );
+				gpuCompute.renderTexture( dtVelocity, velocityVariable.renderTargets[ 1 ] );
+
+			}
+
+			function initProtoplanets() {
+
+				geometry = new THREE.BufferGeometry();
+
+				var positions = new Float32Array( PARTICLES * 3 );
+				var p = 0;
+				for ( var i = 0; i < PARTICLES; i++ ) {
+
+					positions[ p++ ] = ( Math.random() * 2 - 1 ) * effectController.radius;
+					positions[ p++ ] = 0;//( Math.random() * 2 - 1 ) * effectController.radius;
+					positions[ p++ ] = ( Math.random() * 2 - 1 ) * effectController.radius;
+
+				}
+
+				var uvs = new Float32Array( PARTICLES * 2 );
+				p = 0;
+				for ( var j = 0; j < WIDTH; j++ ) {
+					for ( var i = 0; i < WIDTH; i++ ) {
+
+						uvs[ p++ ] = i / ( WIDTH - 1 );
+						uvs[ p++ ] = j / ( WIDTH - 1 );
+
+					}
+				}
+
+				geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
+				geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
+
+				particleUniforms = {
+					texturePosition: { type: "t", value: null },
+					textureVelocity: { type: "t", value: null },
+					cameraConstant: { type: "f", value: getCameraConstant( camera ) },
+					density: { type: "f", value: 0.0 }
+				};
+
+				// ShaderMaterial
+				var material = new THREE.ShaderMaterial( {
+					uniforms:       particleUniforms,
+					vertexShader:   document.getElementById( 'particleVertexShader' ).textContent,
+					fragmentShader: document.getElementById( 'particleFragmentShader' ).textContent
+				} );
+				material.extensions.drawBuffers = true;
+
+				var particles = new THREE.Points( geometry, material );
+				particles.matrixAutoUpdate = false;
+				particles.updateMatrix();
+
+				scene.add( particles );
+
+			}
+
+			function fillTextures( texturePosition, textureVelocity ) {
+
+				var posArray = texturePosition.image.data;
+				var velArray = textureVelocity.image.data;
+
+				var radius = effectController.radius;
+				var height = effectController.height;
+				var exponent = effectController.exponent;
+				var maxMass = effectController.maxMass * 1024 / PARTICLES;
+				var maxVel = effectController.velocity;
+				var velExponent = effectController.velocityExponent;
+				var randVel = effectController.randVelocity;
+
+				for ( var k = 0, kl = posArray.length; k < kl; k += 4 ) {
+
+					// Position
+					var x, y, z, rr;
+
+					do {
+						x = ( Math.random() * 2 - 1 );
+						z = ( Math.random() * 2 - 1 );
+						rr = x * x + z * z;
+					} while ( rr > 1 );
+
+					rr = Math.sqrt( rr );
+
+					var rExp = radius * Math.pow( rr, exponent );
+
+					// Velocity
+					var vel = maxVel * Math.pow( rr, velExponent );
+
+					var vx = vel * z + ( Math.random() * 2 - 1 ) * randVel;
+					var vy = ( Math.random() * 2 - 1 ) * randVel * 0.05;
+					var vz = - vel * x + ( Math.random() * 2 - 1 ) * randVel;
+
+					x *= rExp;
+					z *= rExp;
+					y = ( Math.random() * 2 - 1 ) * height;
+
+					var mass = Math.random() * maxMass + 1;
+
+					// Fill in texture values
+					posArray[ k + 0 ] = x;
+					posArray[ k + 1 ] = y;
+					posArray[ k + 2 ] = z;
+					posArray[ k + 3 ] = 1;
+
+					velArray[ k + 0 ] = vx;
+					velArray[ k + 1 ] = vy;
+					velArray[ k + 2 ] = vz;
+					velArray[ k + 3 ] = mass;
+
+				}
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				particleUniforms.cameraConstant.value = getCameraConstant( camera );
+
+    			}
+
+			function dynamicValuesChanger() {
+
+				velocityUniforms.gravityConstant.value = effectController.gravityConstant;
+				velocityUniforms.density.value = effectController.density;
+				particleUniforms.density.value = effectController.density;
+
+			}
+
+			function initGUI() {
+
+  				var gui = new dat.GUI();
+
+				var folder1 = gui.addFolder( 'Dynamic parameters' );
+
+				folder1.add( effectController, "gravityConstant", 0.0, 1000.0, 0.05 ).onChange( dynamicValuesChanger );
+				folder1.add( effectController, "density", 0.0, 10.0, 0.001 ).onChange( dynamicValuesChanger );
+
+				var folder2 = gui.addFolder( 'Static parameters - press restartSimulation' );
+
+				folder2.add( effectController, "radius", 10.0, 1000.0, 1.0 );
+				folder2.add( effectController, "height", 0.0, 50.0, 0.01 );
+				folder2.add( effectController, "exponent", 0.0, 2.0, 0.001 );
+				folder2.add( effectController, "maxMass", 1.0, 50.0, 0.1 );
+				folder2.add( effectController, "velocity", 0.0, 150.0, 0.1 );
+				folder2.add( effectController, "velocityExponent", 0.0, 1.0, 0.01 );
+				folder2.add( effectController, "randVelocity", 0.0, 50.0, 0.1 );
+
+				var buttonRestart = {
+				    restartSimulation: function() {
+					restartSimulation();
+				    }
+				};
+				folder2.add( buttonRestart, 'restartSimulation' );
+
+				folder1.open();
+				folder2.open();
+
+			}
+
+			function getCameraConstant( camera ) {
+
+				return window.innerHeight / ( Math.tan( THREE.Math.DEG2RAD * 0.5 * camera.fov ) / camera.zoom );
+
+			}
+
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				var now = performance.now();
+				var delta = (now - last) / 1000;
+
+				if (delta > 1) delta = 1; // safety cap on large deltas
+				last = now;
+
+				gpuCompute.compute();
+
+				particleUniforms.texturePosition.value = gpuCompute.getCurrentRenderTarget( positionVariable ).texture;
+				particleUniforms.textureVelocity.value = gpuCompute.getCurrentRenderTarget( velocityVariable ).texture;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 567 - 0
examples/webgl_gpgpu_water.html

@@ -0,0 +1,567 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - gpgpu - water</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 {
+				background-color: #000000;
+				margin: 0px;
+				overflow: hidden;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+				text-align:center;
+			}
+
+			a {
+				color:#0078ff;
+			}
+
+			#info {
+				color: #ffffff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+			}
+
+		</style>
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">three.js</a> - <span id="waterSize"></span> webgl gpgpu water<br/>
+			Select <span id="options"></span> water size<br/>
+			Move mouse to disturb water.<br>
+			Press mouse button to orbit around. 'W' key toggles wireframe.
+		</div>
+
+		<script src="../build/three.js"></script>
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/SimplexNoise.js"></script>
+
+		<script src="js/GPUComputationRenderer.js"></script>
+
+
+		<!-- This is the 'compute shader' for the water heightmap: -->
+		<script id="heightmapFragmentShader" type="x-shader/x-fragment">
+
+			#include <common>
+
+			uniform vec2 mousePos;
+			uniform float mouseSize;
+			uniform float viscosityConstant;
+
+			#define deltaTime ( 1.0 / 60.0 )
+			#define GRAVITY_CONSTANT ( resolution.x * deltaTime * 3.0 )
+
+			void main()	{
+
+				vec2 cellSize = 1.0 / resolution.xy;
+
+				vec2 uv = gl_FragCoord.xy * cellSize;
+
+				// heightmapValue.x == height
+				// heightmapValue.y == velocity
+				// heightmapValue.z, heightmapValue.w not used
+				vec4 heightmapValue = texture2D( heightmap, uv );
+
+				// Get neighbours
+				vec4 north = texture2D( heightmap, uv + vec2( 0.0, cellSize.y ) );
+				vec4 south = texture2D( heightmap, uv + vec2( 0.0, - cellSize.y ) );
+				vec4 east = texture2D( heightmap, uv + vec2( cellSize.x, 0.0 ) );
+				vec4 west = texture2D( heightmap, uv + vec2( - cellSize.x, 0.0 ) );
+
+				float sump = north.x + south.x + east.x + west.x - 4.0 * heightmapValue.x;
+
+				float accel = sump * GRAVITY_CONSTANT;
+
+				// Dynamics
+				heightmapValue.y += accel;
+				heightmapValue.x += heightmapValue.y * deltaTime;
+
+				// Viscosity
+				heightmapValue.x += sump * viscosityConstant;
+
+				// Mouse influence
+				float mousePhase = clamp( length( ( uv - vec2( 0.5 ) ) * BOUNDS - vec2( mousePos.x, - mousePos.y ) ) * PI / mouseSize, 0.0, PI );
+				heightmapValue.x += cos( mousePhase ) + 1.0;
+
+				gl_FragColor = heightmapValue;
+
+			}
+
+		</script>
+
+		<!-- This is just a smoothing 'compute shader' for using manually: -->
+		<script id="smoothFragmentShader" type="x-shader/x-fragment">
+
+			uniform sampler2D texture;
+
+			void main()	{
+
+				vec2 cellSize = 1.0 / resolution.xy;
+
+				vec2 uv = gl_FragCoord.xy * cellSize;
+
+				// Computes the mean of texel and 4 neighbours
+				vec4 textureValue = texture2D( texture, uv );
+				textureValue += texture2D( texture, uv + vec2( 0.0, cellSize.y ) );
+				textureValue += texture2D( texture, uv + vec2( 0.0, - cellSize.y ) );
+				textureValue += texture2D( texture, uv + vec2( cellSize.x, 0.0 ) );
+				textureValue += texture2D( texture, uv + vec2( - cellSize.x, 0.0 ) );
+
+				textureValue /= 5.0;
+
+				gl_FragColor = textureValue;
+
+			}
+
+		</script>
+
+		<!-- This is the water visualization shader, copied from the MeshPhongMaterial and modified: -->
+		<script id="waterVertexShader" type="x-shader/x-vertex">
+
+			uniform sampler2D heightmap;
+
+			#define PHONG
+
+			varying vec3 vViewPosition;
+
+			#ifndef FLAT_SHADED
+
+				varying vec3 vNormal;
+
+			#endif
+
+			#include <common>
+			#include <uv_pars_vertex>
+			#include <uv2_pars_vertex>
+			#include <displacementmap_pars_vertex>
+			#include <envmap_pars_vertex>
+			#include <color_pars_vertex>
+			#include <morphtarget_pars_vertex>
+			#include <skinning_pars_vertex>
+			#include <shadowmap_pars_vertex>
+			#include <logdepthbuf_pars_vertex>
+			#include <clipping_planes_pars_vertex>
+
+			void main() {
+
+				vec2 cellSize = vec2( 1.0 / WIDTH, 1.0 / WIDTH );
+
+				#include <uv_vertex>
+				#include <uv2_vertex>
+				#include <color_vertex>
+
+				// # include <beginnormal_vertex>
+				// Compute normal from heightmap
+				vec3 objectNormal = vec3(
+					( texture2D( heightmap, uv + vec2( - cellSize.x, 0 ) ).x - texture2D( heightmap, uv + vec2( cellSize.x, 0 ) ).x ) * WIDTH / BOUNDS,
+					( texture2D( heightmap, uv + vec2( 0, - cellSize.y ) ).x - texture2D( heightmap, uv + vec2( 0, cellSize.y ) ).x ) * WIDTH / BOUNDS,
+					1.0 );
+				//<beginnormal_vertex>
+
+				#include <morphnormal_vertex>
+				#include <skinbase_vertex>
+				#include <skinnormal_vertex>
+				#include <defaultnormal_vertex>
+
+			#ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
+
+				vNormal = normalize( transformedNormal );
+
+			#endif
+
+				//# include <begin_vertex>
+				float heightValue = texture2D( heightmap, uv ).x;
+				vec3 transformed = vec3( position.x, position.y, heightValue );
+				//<begin_vertex>
+
+				#include <displacementmap_vertex>
+				#include <morphtarget_vertex>
+				#include <skinning_vertex>
+				#include <project_vertex>
+				#include <logdepthbuf_vertex>
+				#include <clipping_planes_vertex>
+
+				vViewPosition = - mvPosition.xyz;
+
+				#include <worldpos_vertex>
+				#include <envmap_vertex>
+				#include <shadowmap_vertex>
+
+			}
+
+		</script>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var hash = document.location.hash.substr( 1 );
+			if ( hash ) hash = parseInt( hash, 0 );
+
+			// Texture width for simulation
+			var WIDTH = hash || 128;
+			var NUM_TEXELS = WIDTH * WIDTH;
+
+			// Water size in system units
+			var BOUNDS = 512;
+			var BOUNDS_HALF = BOUNDS * 0.5;
+
+			var container, stats;
+			var camera, scene, renderer, controls;
+			var mouseMoved = false;
+			var mouseCoords = new THREE.Vector2();
+			var raycaster = new THREE.Raycaster();
+
+			var waterMesh;
+			var meshRay;
+			var gpuCompute;
+			var heightmapVariable;
+			var waterUniforms;
+			var smoothShader;
+
+			var simplex = new SimplexNoise();
+
+			var windowHalfX = window.innerWidth / 2;
+			var windowHalfY = window.innerHeight / 2;
+
+			document.getElementById( 'waterSize' ).innerText = WIDTH + ' x ' + WIDTH;
+
+			function change(n) {
+				location.hash = n;
+				location.reload();
+				return false;
+			}
+
+
+			var options = '';
+			for ( var i = 4; i < 10; i++ ) {
+				var j = Math.pow( 2, i );
+				options += '<a href="#" onclick="return change(' + j + ')">' + j + 'x' + j + '</a> ';
+			}
+			document.getElementById('options').innerHTML = options;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 3000 );
+				camera.position.set( 0, 200, 350 );
+
+				scene = new THREE.Scene();
+
+				var sun = new THREE.DirectionalLight( 0xFFFFFF, 1.0 );
+				sun.position.set( 300, 400, 175 );
+				scene.add( sun );
+
+				var sun2 = new THREE.DirectionalLight( 0x40A040, 0.6 );
+				sun2.position.set( -100, 350, -200 );
+				scene.add( sun2 );
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setClearColor( 0x000000 );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				controls = new THREE.OrbitControls( camera, renderer.domElement );
+
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
+				document.addEventListener( 'touchstart', onDocumentTouchStart, false );
+				document.addEventListener( 'touchmove', onDocumentTouchMove, false );
+
+				document.addEventListener( 'keydown', function( event ) {
+
+					// W Pressed: Toggle wireframe
+					if ( event.keyCode === 87 ) {
+
+						waterMesh.material.wireframe = ! waterMesh.material.wireframe;
+						waterMesh.material.needsUpdate = true;
+
+					}
+
+				} , false );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+
+				var gui = new dat.GUI();
+
+				var effectController = {
+					mouseSize: 20.0,
+					viscosity: 0.03
+				};
+
+				var valuesChanger = function() {
+
+					heightmapVariable.material.uniforms.mouseSize.value = effectController.mouseSize;
+					heightmapVariable.material.uniforms.viscosityConstant.value = effectController.viscosity;
+
+				};
+
+				gui.add( effectController, "mouseSize", 1.0, 100.0, 1.0 ).onChange( valuesChanger );
+				gui.add( effectController, "viscosity", 0.0, 0.1, 0.001 ).onChange( valuesChanger );
+				var buttonSmooth = {
+				    smoothWater: function() {
+					smoothWater();
+				    }
+				};
+				gui.add( buttonSmooth, 'smoothWater' );
+
+
+				initWater();
+
+				valuesChanger();
+
+			}
+
+
+			function initWater() {
+
+				var materialColor = 0x0040C0;
+
+				var geometry = new THREE.PlaneBufferGeometry( BOUNDS, BOUNDS, WIDTH - 1, WIDTH -1 );
+
+				// material: make a ShaderMaterial clone of MeshPhongMaterial, with customized vertex shader
+				var material = new THREE.ShaderMaterial( {
+					uniforms: THREE.UniformsUtils.merge( [
+						THREE.ShaderLib[ 'phong' ].uniforms,
+					    	{
+							heightmap: { type: "t", value: null },
+						}
+					] ),
+					vertexShader: document.getElementById( 'waterVertexShader' ).textContent,
+					fragmentShader: THREE.ShaderChunk[ 'meshphong_frag' ]
+
+				} );
+
+				material.lights = true;
+
+				// Material attributes from MeshPhongMaterial
+				material.color = new THREE.Color( materialColor );
+				material.specular = new THREE.Color( 0x111111 );
+				material.shininess = 50;
+
+				// Sets the uniforms with the material values
+				material.uniforms.diffuse.value = material.color;
+				material.uniforms.specular.value = material.specular;
+				material.uniforms.shininess.value = Math.max( material.shininess, 1e-4 );
+				material.uniforms.opacity.value = material.opacity;
+
+				// Defines
+				material.defines.WIDTH = WIDTH.toFixed( 1 );
+				material.defines.BOUNDS = BOUNDS.toFixed( 1 );
+
+				waterUniforms = material.uniforms;
+
+				waterMesh = new THREE.Mesh( geometry, material );
+				waterMesh.rotation.x = - Math.PI / 2;
+				waterMesh.matrixAutoUpdate = false;
+				waterMesh.updateMatrix();
+
+				scene.add( waterMesh );
+
+				// Mesh just for mouse raycasting
+				var geometryRay = new THREE.PlaneBufferGeometry( BOUNDS, BOUNDS, 1, 1 );
+				meshRay = new THREE.Mesh( geometryRay, new THREE.MeshBasicMaterial( { color: 0xFFFFFF, visible: false } ) );
+				meshRay.rotation.x = - Math.PI / 2;
+				meshRay.matrixAutoUpdate = false;
+				meshRay.updateMatrix();
+				scene.add( meshRay );
+
+
+				// Creates the gpu computation class and sets it up
+
+				gpuCompute = new GPUComputationRenderer( WIDTH, WIDTH, renderer );
+
+				var heightmap0 = gpuCompute.createTexture();
+
+				fillTexture( heightmap0 );
+
+				heightmapVariable = gpuCompute.addVariable( "heightmap", document.getElementById( 'heightmapFragmentShader' ).textContent, heightmap0 );
+
+				gpuCompute.setVariableDependencies( heightmapVariable, [ heightmapVariable ] );
+
+				heightmapVariable.material.uniforms.mousePos = { type: "v2", value: new THREE.Vector2( 10000, 10000 ) };
+				heightmapVariable.material.uniforms.mouseSize = { type: "f", value: 20.0 };
+				heightmapVariable.material.uniforms.viscosityConstant = { type: "f", value: 0.03 };
+				heightmapVariable.material.defines.BOUNDS = BOUNDS.toFixed( 1 );
+
+				var error = gpuCompute.init();
+				if ( error !== null ) {
+				    console.error( error );
+				}
+
+				// Create compute shader to smooth the water surface and velocity
+				smoothShader = gpuCompute.createShaderMaterial( document.getElementById( 'smoothFragmentShader' ).textContent, { texture: { type: "t", value: null } } );
+
+			}
+
+			function fillTexture( texture ) {
+
+				var waterMaxHeight = 10;
+
+				function noise( x, y, z ) {
+					var multR = waterMaxHeight;
+					var mult = 0.025;
+					var r = 0;
+					for ( var i = 0; i < 15; i++ ) {
+						r += multR * simplex.noise3d( x * mult, y * mult, z * mult );
+						multR *= 0.53 + 0.025 * i;
+						mult *= 1.25;
+					}
+					return r;
+				}
+
+				var pixels = texture.image.data;
+
+				var p = 0;
+				for ( var j = 0; j < WIDTH; j++ ) {
+					for ( var i = 0; i < WIDTH; i++ ) {
+
+						var x = i * 128 / WIDTH;
+						var y = j * 128 / WIDTH;
+
+					        pixels[ p + 0 ] = noise( x, y, 0 );
+						pixels[ p + 1 ] = 0;
+						pixels[ p + 2 ] = 0;
+						pixels[ p + 3 ] = 1;
+
+						p += 4;
+					}
+				}
+
+			}
+
+			function smoothWater() {
+
+				var currentRenderTarget = gpuCompute.getCurrentRenderTarget( heightmapVariable );
+				var alternateRenderTarget = gpuCompute.getAlternateRenderTarget( heightmapVariable );
+
+				for ( var i = 0; i < 10; i++ ) {
+
+					smoothShader.uniforms.texture.value = currentRenderTarget.texture;
+					gpuCompute.doRenderTarget( smoothShader, alternateRenderTarget );
+
+					smoothShader.uniforms.texture.value = alternateRenderTarget.texture;
+					gpuCompute.doRenderTarget( smoothShader, currentRenderTarget );
+
+				}
+				
+			}
+
+
+			function onWindowResize() {
+
+				windowHalfX = window.innerWidth / 2;
+				windowHalfY = window.innerHeight / 2;
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function setMouseCoords( x, y ) {
+
+				mouseCoords.set( ( x / renderer.domElement.width ) * 2 - 1, - ( y / renderer.domElement.height ) * 2 + 1 );
+				mouseMoved = true;
+
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				setMouseCoords( event.clientX, event.clientY );
+
+			}
+
+			function onDocumentTouchStart( event ) {
+
+				if ( event.touches.length === 1 ) {
+
+					event.preventDefault();
+
+					setMouseCoords( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+
+				}
+
+			}
+
+			function onDocumentTouchMove( event ) {
+
+				if ( event.touches.length === 1 ) {
+
+					event.preventDefault();
+
+					setMouseCoords( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+
+				}
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				// Set uniforms: mouse interaction
+				var uniforms = heightmapVariable.material.uniforms;
+				if ( mouseMoved ) {
+
+					this.raycaster.setFromCamera( mouseCoords, camera );
+
+					var intersects = this.raycaster.intersectObject( meshRay );
+
+					if ( intersects.length > 0 ) {
+					    var point = intersects[ 0 ].point;
+					    uniforms.mousePos.value.set( point.x, point.z );
+
+					}
+					else {
+					    uniforms.mousePos.value.set( 10000, 10000 );
+					}
+
+					mouseMoved = false;
+				}
+				else {
+					uniforms.mousePos.value.set( 10000, 10000 );
+				}
+
+				// Do the gpu computation
+				gpuCompute.compute();
+
+				// Get compute output in custom uniform
+				waterUniforms.heightmap.value = gpuCompute.getCurrentRenderTarget( heightmapVariable ).texture;
+
+				// Render
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>