提交 5738e9ad 编写于 作者: J Juan Jose Luna Espinosa 提交者: Mr.doob

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
上级 dc36ff06
......@@ -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",
......
/**
* @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";
}
}
\ No newline at end of file
/**
* @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;
}
}
......@@ -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 );
......
<!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>
<!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>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册