From 515ee07b43c4aee9d0a0c2301f7f49412d51c03c Mon Sep 17 00:00:00 2001 From: Rezmason Date: Wed, 5 Sep 2018 00:47:46 -0700 Subject: [PATCH] Adding GPUComputationRenderer to project. Adding gpgpu_example HTML as a reference. --- TODO.txt | 10 + gpgpu_example.html | 134 +++++++++++++ index.html | 9 +- js/GPUComputationRenderer.js | 368 +++++++++++++++++++++++++++++++++++ 4 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 gpgpu_example.html create mode 100644 js/GPUComputationRenderer.js diff --git a/TODO.txt b/TODO.txt index 3e8d12d..d21fac4 100644 --- a/TODO.txt +++ b/TODO.txt @@ -11,6 +11,9 @@ Much later: Dissolve threejs project into webgl project Maybe webgl2 project? Deluxe + Raindrop sound + https://youtu.be/KoQOKq1C3O4?t=30 + https://youtu.be/h1vLZeVAp5o?t=28 Flashing row effect? https://youtu.be/z_KmNZNT5xw?t=16 Square event? @@ -18,3 +21,10 @@ Much later: https://youtu.be/721sG2D_9-U?t=67 More patterns? Symbol duplication is common + + + + +Also interesting: + The Matrix code for the Zion Control construct is sparser, slower, bluer, and annotated + https://www.youtube.com/watch?v=Jt5z3OEjDzU diff --git a/gpgpu_example.html b/gpgpu_example.html new file mode 100644 index 0000000..4d11568 --- /dev/null +++ b/gpgpu_example.html @@ -0,0 +1,134 @@ + + + + + + + + diff --git a/index.html b/index.html index 558b4c4..7a5c1fe 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,10 @@ - + + + Matrix digital rain + + + + @@ -12,6 +18,7 @@ + diff --git a/js/GPUComputationRenderer.js b/js/GPUComputationRenderer.js new file mode 100644 index 0000000..c9b22e8 --- /dev/null +++ b/js/GPUComputationRenderer.js @@ -0,0 +1,368 @@ +/** + * @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 = { 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: { value: null } } ); + * var myFilter2 = gpuCompute.createShaderMaterial( myFilterFragmentShader2, { theTexture: { 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: { 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, + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter + }; + + 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( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter ); + variable.renderTargets[ 1 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter ); + 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 ] = { value: null }; + + material.fragmentShader = "\nuniform sampler2D " + depVar.name + ";\n" + 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, il = this.variables.length; i < il; i++ ) { + + var variable = this.variables[ i ]; + + // Sets texture dependencies uniforms + if ( variable.dependencies !== null ) { + + var uniforms = variable.material.uniforms; + for ( var d = 0, dl = variable.dependencies.length; d < dl; 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( sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter ) { + + sizeXTexture = sizeXTexture || sizeX; + sizeYTexture = sizeYTexture || sizeY; + + wrapS = wrapS || THREE.ClampToEdgeWrapping; + wrapT = wrapT || THREE.ClampToEdgeWrapping; + + minFilter = minFilter || THREE.NearestFilter; + magFilter = magFilter || THREE.NearestFilter; + + var renderTarget = new THREE.WebGLRenderTarget( sizeXTexture, sizeYTexture, { + wrapS: wrapS, + wrapT: wrapT, + minFilter: minFilter, + magFilter: magFilter, + format: THREE.RGBAFormat, + type: ( /(iPad|iPhone|iPod)/g.test( navigator.userAgent ) ) ? THREE.HalfFloatType : THREE.FloatType, + stencilBuffer: false, + depthBuffer: false + } ); + + return renderTarget; + + }; + + this.createTexture = function() { + + var a = new Float32Array( sizeX * sizeY * 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); + + passThruUniforms.texture.value = null; + + }; + + this.doRenderTarget = function( material, output ) { + + mesh.material = material; + renderer.render( scene, camera, output ); + mesh.material = passThruShader; + + }; + + // 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"; + + } + +}