From 5738e9ad2b00225da467f20f795fd64326c0a6ad Mon Sep 17 00:00:00 2001 From: Juan Jose Luna Espinosa Date: Tue, 31 May 2016 19:37:29 +0200 Subject: [PATCH] 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 --- examples/files.js | 2 + examples/js/GPUComputationRenderer.js | 357 ++++++++++++++++ examples/js/SimulationRenderer.js | 233 ---------- examples/webgl_gpgpu_birds.html | 163 ++++--- examples/webgl_gpgpu_protoplanet.html | 593 ++++++++++++++++++++++++++ examples/webgl_gpgpu_water.html | 567 ++++++++++++++++++++++++ 6 files changed, 1624 insertions(+), 291 deletions(-) create mode 100644 examples/js/GPUComputationRenderer.js delete mode 100644 examples/js/SimulationRenderer.js create mode 100644 examples/webgl_gpgpu_protoplanet.html create mode 100644 examples/webgl_gpgpu_water.html diff --git a/examples/files.js b/examples/files.js index 356e715cf3..5fc48a7b9a 100644 --- a/examples/files.js +++ b/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", diff --git a/examples/js/GPUComputationRenderer.js b/examples/js/GPUComputationRenderer.js new file mode 100644 index 0000000000..6ea99f69a9 --- /dev/null +++ b/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"; + + } + +} \ No newline at end of file diff --git a/examples/js/SimulationRenderer.js b/examples/js/SimulationRenderer.js deleted file mode 100644 index 761e368741..0000000000 --- a/examples/js/SimulationRenderer.js +++ /dev/null @@ -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; - - } - -} diff --git a/examples/webgl_gpgpu_birds.html b/examples/webgl_gpgpu_birds.html index a8a84ad9f3..4cc8708043 100644 --- a/examples/webgl_gpgpu_birds.html +++ b/examples/webgl_gpgpu_birds.html @@ -43,56 +43,20 @@ - + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/webgl_gpgpu_water.html b/examples/webgl_gpgpu_water.html new file mode 100644 index 0000000000..844e595b4a --- /dev/null +++ b/examples/webgl_gpgpu_water.html @@ -0,0 +1,567 @@ + + + + three.js webgl - gpgpu - water + + + + + + +
+ three.js - webgl gpgpu water
+ Select water size
+ Move mouse to disturb water.
+ Press mouse button to orbit around. 'W' key toggles wireframe. +
+ + + + + + + + + + + + + + + + + + + + + + + -- GitLab