Last active
          July 25, 2025 22:03 
        
      - 
      
 - 
        
Save eipporko/28f5e37831a720cedaa3e3f5a7885995 to your computer and use it in GitHub Desktop.  
    THREE's GPUComputationRenderer – MRT Support
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | import { | |
| ClampToEdgeWrapping, | |
| DataTexture, | |
| FloatType, | |
| NearestFilter, | |
| RGBAFormat, | |
| ShaderMaterial, | |
| WebGLRenderTarget, | |
| GLSL1, | |
| GLSL3 | |
| } from 'three'; | |
| import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js'; | |
| /** | |
| * GPUComputationRenderer, based on SimulationRenderer by @zz85 and extended from THREE's GPUComputationRenderer. | |
| * | |
| * The GPUComputationRenderer uses the concept of variables. These variables are RGBA float textures that hold 4 floats | |
| * for each compute element (texel). | |
| * Each variable is computed via a fragment shader. Unlike the original, this version supports Multiple Render Targets (MRT), | |
| * allowing a single shader pass to output to multiple textures simultaneously (GLSL3 layout or WEBGL_draw_buffers in GLSL1). | |
| * | |
| * 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 ) | |
| * ``` | |
| * | |
| * Dependencies: Any variables declared via setVariableDependencies are automatically injected into the | |
| * generated fragment shader as uniform sampler2D depName;. You can read them directly using | |
| * texture2D(depName, uv) in GLSL1 or texture(depName, uv) in GLSL3. | |
| * | |
| * Outputs: | |
| * In GLSL1, write your result to gl_FragColor. | |
| * In GLSL3, for each variable name passed to addVariable, an output is declared as: | |
| * `layout(location = N) out vec4 gName;` where gName is the original variable name prefixed with "g" and capitalized | |
| * (e.g., variable "textureVelocity" becomes "gTextureVelocity"). | |
| * | |
| * | |
| * Basic use: | |
| * ```js | |
| * // Initialization... | |
| * | |
| * // Create computation renderer | |
| * const gpuCompute = new GPUComputationRenderer( 1024, 1024, renderer ); | |
| * | |
| * // Create initial state float textures | |
| * const vel0 = gpuCompute.createTexture(); | |
| * const dens0 = gpuCompute.createTexture(); | |
| * const pos0 = gpuCompute.createTexture(); | |
| * | |
| * | |
| * // Add texture variables | |
| * | |
| * // Add a variable with two outputs (MRT) in a single pass | |
| * const fluidVar = gpuCompute.addVariable( | |
| * [ 'textureVelocity', 'textureDensity' ], | |
| * fragmentShaderFluid, | |
| * [ velTex, densTex ], | |
| * THREE.GLSL3 | |
| * ); | |
| * | |
| * // add a variable with a single output | |
| * const posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, pos0 ); | |
| * | |
| * // Declare dependencies (self or other variables) | |
| * gpuCompute.setVariableDependencies( fluidVar, [ fluidVar ] ); | |
| * gpuCompute.setVariableDependencies( posVar, [ fluidVar ] ); | |
| * | |
| * | |
| * // Add custom uniforms | |
| * velVar.material.uniforms.time = { value: 0.0 }; | |
| * | |
| * // Check for completeness | |
| * const error = gpuCompute.init(); | |
| * if ( error !== null ) { | |
| * console.error( error ); | |
| * } | |
| * | |
| * // In each frame... | |
| * | |
| * // Compute! | |
| * gpuCompute.compute(); | |
| * | |
| * // Retrieve MRT outputs by name | |
| * const velOutput = gpuCompute.getCurrentTexture( fluidVar, 'textureVelocity' ); | |
| * const densOutput = gpuCompute.getCurrentTexture( fluidVar, 'textureDensity' ); | |
| * const posOutput = gpuCompute.getCurrentRenderTarget( posVar ).texture; // OR .getCurrentTexture( posVar ); | |
| * | |
| * // Use in visualization materials | |
| * myMaterial.uniforms.velocityMap.value = velOutput; | |
| * myMaterial.uniforms.densityMap.value = densOutput; | |
| * myMaterial.uniforms.posMap.value = posOutput; | |
| * | |
| * // Do your rendering | |
| * renderer.render( scene, camera ); | |
| * ``` | |
| * | |
| * Also, you can use utility functions to create ShaderMaterial and perform computations (rendering between textures) | |
| * Note that the shaders can have multiple input textures. | |
| * | |
| * ```js | |
| * const myFilter1 = gpuCompute.createShaderMaterial( myFilterFragmentShader1, { theTexture: { value: null } } ); | |
| * const myFilter2 = gpuCompute.createShaderMaterial( myFilterFragmentShader2, { theTexture: { value: null } } ); | |
| * | |
| * const inputTexture = gpuCompute.createTexture(); | |
| * | |
| * // Fill in here inputTexture... | |
| * | |
| * myFilter1.uniforms.theTexture.value = inputTexture; | |
| * | |
| * const myRenderTarget = gpuCompute.createRenderTarget(); | |
| * myFilter2.uniforms.theTexture.value = myRenderTarget.texture; | |
| * | |
| * const 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 ); | |
| * ``` | |
| * | |
| * @three_import import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js'; | |
| */ | |
| class GPUComputationRenderer { | |
| /** | |
| * Constructs a new GPU computation renderer. | |
| * | |
| * @param {number} sizeX - Computation problem size is always 2d: sizeX * sizeY elements. | |
| * @param {number} sizeY - Computation problem size is always 2d: sizeX * sizeY elements. | |
| * @param {WebGLRenderer} renderer - The renderer. | |
| */ | |
| constructor(sizeX, sizeY, renderer) { | |
| this._sizeX = sizeX; | |
| this._sizeY = sizeY; | |
| this._renderer = renderer; | |
| this._variables = []; | |
| this._dataType = FloatType; | |
| this._passThruUniforms = { | |
| passThruTexture: { value: null } | |
| }; | |
| // Default passThruShader for only one data output. | |
| this._passThruShader = this.createShaderMaterial(this._getPassThroughFragmentShader(), this._passThruUniforms); | |
| this._quad = new FullScreenQuad(this._passThruShader); | |
| } | |
| /** | |
| * Enables the WEBGL_draw_buffers extension if available (required for gl_FragData[] in WebGL1). | |
| * In WebGL2, this is no longer needed, use layout(location = N) out vec4 instead. | |
| */ | |
| _enableDrawBuffersExtension() { | |
| if (this._drawBuffersExt) return; // Already enabled | |
| const ext = this._renderer.getContext().getExtension('WEBGL_draw_buffers'); | |
| if (!ext) { | |
| console.warn('WEBGL_draw_buffers not supported, some features may not work as expected.'); | |
| } else { | |
| console.log('WEBGL_draw_buffers supported.'); | |
| this._drawBuffersExt = ext; | |
| } | |
| } | |
| /** | |
| * Sets the data type of the internal textures. | |
| * | |
| * @param {(FloatType|HalfFloatType)} type - The type to set. | |
| * @return {GPUComputationRenderer} A reference to this renderer. | |
| */ | |
| setDataType(type) { | |
| this._dataType = type; | |
| return this; | |
| }; | |
| _getOutputVarName(name) { | |
| return 'g' + name.charAt(0).toUpperCase() + name.slice(1); | |
| } | |
| // Shaders | |
| _getPassThroughVertexShader() { | |
| return 'void main() {\n' + | |
| '\n' + | |
| ' gl_Position = vec4( position, 1.0 );\n' + | |
| '\n' + | |
| '}\n'; | |
| } | |
| _getPassThroughFragmentShader() { | |
| return 'uniform sampler2D passThruTexture;\n' + | |
| '\n' + | |
| 'void main() {\n' + | |
| '\n' + | |
| ' vec2 uv = gl_FragCoord.xy / resolution.xy;\n' + | |
| '\n' + | |
| ' gl_FragColor = texture2D( passThruTexture, uv );\n' + | |
| '\n' + | |
| '}\n'; | |
| } | |
| _getPassThroughFragmentShaderMRT(variableName = [], initialValueTextures = [], glslVersion = GLSL1) { | |
| const names = Array.isArray(variableName) ? variableName : [variableName]; | |
| const values = Array.isArray(initialValueTextures) ? initialValueTextures : [initialValueTextures]; | |
| if (values.len === 0) | |
| values = Array(names.lenght).fill(null); | |
| while (values.length < names.length) values.push(values[0]); | |
| let fragmentShader = '\nvoid main() {\n' + | |
| '\n' + | |
| ' vec2 uv = gl_FragCoord.xy / resolution.xy;\n' + | |
| '\n'; | |
| for (let i = 0; i < names.length; i++) { | |
| const reverseCounter = (names.length - 1) - i; | |
| fragmentShader = 'uniform sampler2D passThruTexture_' + reverseCounter + ';\n' + fragmentShader; | |
| const varName = this._getOutputVarName(names[i]); | |
| fragmentShader += ' ' + varName + ' = texture2D( passThruTexture_' + i + ', uv);\n' | |
| } | |
| fragmentShader += '\n' + '}\n'; | |
| fragmentShader = this._injectsOutputDefinesFragShader(names, fragmentShader, glslVersion); | |
| return fragmentShader; | |
| } | |
| /** | |
| * Injects output declarations into the compute fragment shader source for multiple render targets (MRT). | |
| * | |
| * For GLSL3 (WebGL2), it prepends `layout(location = N) out vec4` statements for each output. | |
| * For GLSL1 (WebGL1), it defines macros mapping each output name to `gl_FragData[N]`, assuming | |
| * the WEBGL_draw_buffers extension is available (note: not compatible with `#version 100`). | |
| * | |
| * @param {Array<string>} names - Array of output variable names. | |
| * @param {string} computeFragmentShader - The original compute fragment shader source. | |
| * @param {?string} [glslVersion=THREE.GLSL3] - The GLSL version of the shader. Accepts THREE.GLSL1 or THREE.GLSL3. Default is GLSL3. | |
| * @returns {string} The patched fragment shader source with MRT output declarations. | |
| */ | |
| _injectsOutputDefinesFragShader(names, computeFragmentShader, glslVersion = GLSL3) { | |
| const namesArray = Array.isArray(names) ? names : [ names ]; | |
| let computeFSPatched = computeFragmentShader; | |
| computeFSPatched = '\n' + computeFSPatched; | |
| for (let v = namesArray.length - 1; v >= 0; v--) { | |
| const name = namesArray[v] || 'Unknown'; | |
| const varName = this._getOutputVarName(name); | |
| if (glslVersion === GLSL3) { | |
| computeFSPatched = 'layout(location = ' + v + ' ) out vec4 ' + varName + ';\n' + computeFSPatched; | |
| } else { | |
| this._enableDrawBuffersExtension(); | |
| computeFSPatched = '#define ' + varName + ' gl_FragData[' + v + ']\n' + computeFSPatched; | |
| } | |
| } | |
| return computeFSPatched; | |
| } | |
| /** | |
| * Injects a `#define` for the maximum loop iterations and a `uniform` | |
| * to track the current loop counter into a GLSL fragment shader. | |
| * | |
| * @param {string} fragmentShaderSource - The original fragment shader source code. | |
| * @param {number} maxLoopIterations - The maximum number of loop iterations allowed. | |
| * @returns {string} The modified shader source, including: | |
| * 1. #define MAX_LOOP_ITERATIONS <maxLoopIterations> | |
| * 2. uniform int u_LoopCounter; | |
| */ | |
| _injectLoopCounterIntoShader(fragmentShaderSource, maxLoopIterations) { | |
| const defineDirective = '\n#define MAX_LOOP_ITERATIONS ' + maxLoopIterations; | |
| const uniformDeclaration = '\nuniform int uLoopCounter;\n'; | |
| return defineDirective + uniformDeclaration + fragmentShaderSource; | |
| } | |
| /** | |
| * Expands or collapses an array to the specified length. | |
| * | |
| * - If `newLength` is less than or equal to 0, returns an empty array. | |
| * - If the original array is empty, returns a new array of length `newLength` filled with `null`. | |
| * - If `newLength` is less than the length of the original array, returns a copy containing the first `newLength` elements. | |
| * - If `newLength` is greater than the length of the original array, returns a copy extended by pushing null elements. | |
| * | |
| * @template T | |
| * @param {Array<T>} array – The original array. | |
| * @param {number} newLength – The desired length for the new array. | |
| * @returns {Array<T|null>} A new array of length `newLength`. | |
| */ | |
| _expandCollapseArray(array, newLength) { | |
| // If newLength is zero or negative, return an empty array | |
| if (newLength <= 0) { | |
| return []; | |
| } | |
| const length = array.length; | |
| // If the original array is empty, return an array filled with nulls | |
| if (length === 0) { | |
| return Array(newLength).fill(null); | |
| } | |
| // Create a copy truncated to newLength if necessary | |
| const result = array.slice(0, newLength); | |
| // If expansion is needed, fill with null elements | |
| if (length < newLength) { | |
| const filler = null; | |
| result.push(...Array(newLength - length).fill(filler)); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Adds a compute variable to the renderer. | |
| * | |
| * @param {string|Array<string>} variableName - The variable name, or an array of variable names (for MRT). | |
| * @param {string} computeFragmentShader - The compute (fragment) shader source. | |
| * @param {Texture|Array<Texture>} initialValueTexture - The initial value texture, or an array of textures if using MRT. | |
| * @param {Object} [options] - Optional settings. | |
| * @param {string} [options.glslVersion=THREE.GLSL1] - GLSL version of custom shader code: THREE.GLSL1 or THREE.GLSL3. | |
| * @param {number} [options.loopFor=1] - Number of render-loop iterations to perform. | |
| * @return {Object} The compute variable. | |
| */ | |
| addVariable(variableName, computeFragmentShader, initialValueTexture, options = {}) { | |
| const { | |
| glslVersion = GLSL1, | |
| loopFor = 1 } = options; | |
| const nameArray = Array.isArray(variableName) ? variableName : [variableName]; | |
| const numberOfTargets = nameArray.length; | |
| let textureArray = Array.isArray(initialValueTexture) ? initialValueTexture : [initialValueTexture]; | |
| textureArray = this._expandCollapseArray(textureArray, nameArray.length); // This way both nameArray & textureArray have the same length | |
| let passThruUniforms = {}; | |
| let passThruShader = null; | |
| // Creates a custom passThroughShader only if cannot be used the default one to copy a single texture into buffers | |
| if (nameArray.length > 1) { | |
| for (let i = 0; i < nameArray.length; i++) { | |
| passThruUniforms['passThruTexture_' + i] = { value: null }; | |
| } | |
| const passThruFragmentShader = this._getPassThroughFragmentShaderMRT(nameArray, textureArray, GLSL3); | |
| passThruShader = this.createShaderMaterial(passThruFragmentShader, passThruUniforms, GLSL3); | |
| } | |
| let computeFragmentShaderPatched = this._injectLoopCounterIntoShader(computeFragmentShader, loopFor); | |
| if (numberOfTargets > 1 || glslVersion == GLSL3) | |
| computeFragmentShaderPatched = this._injectsOutputDefinesFragShader(variableName, computeFragmentShaderPatched, glslVersion); | |
| const material = this.createShaderMaterial(computeFragmentShaderPatched, null, glslVersion); | |
| const variable = { | |
| name: nameArray[0], | |
| names: nameArray.length > 1 ? nameArray : null, | |
| initialValueTexture: textureArray[0], | |
| initialValueTextures: textureArray.length ? textureArray : null, | |
| material: material, | |
| dependencies: null, | |
| renderTargets: [], | |
| wrapS: null, | |
| wrapT: null, | |
| minFilter: NearestFilter, | |
| magFilter: NearestFilter, | |
| numberOfTargets: numberOfTargets, | |
| passThruUniforms: passThruUniforms, | |
| passThruShader: passThruShader, | |
| loopFor: loopFor, | |
| glslVersion: glslVersion, | |
| currentTextureIndex: 0 | |
| }; | |
| this._variables.push(variable); | |
| return variable; | |
| }; | |
| /** | |
| * Sets variable dependencies. | |
| * | |
| * @param {Object} variable - The compute variable. | |
| * @param {Array<Object>} dependencies - Other compute variables that represents the dependencies. | |
| */ | |
| setVariableDependencies(variable, dependencies) { | |
| variable.dependencies = dependencies; | |
| }; | |
| /** | |
| * Initializes the renderer. | |
| * | |
| * @return {?string} Returns `null` if no errors are detected. Otherwise returns the error message. | |
| */ | |
| init() { | |
| if (this._renderer.capabilities.maxVertexTextures === 0) { | |
| return 'No support for vertex shader textures.'; | |
| } | |
| for (let i = 0; i < this._variables.length; i++) { | |
| const variable = this._variables[i]; | |
| // Creates rendertargets and initialize them with input texture | |
| variable.renderTargets[0] = this._createRenderTarget(this._sizeX, this._sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter, variable.numberOfTargets); | |
| variable.renderTargets[1] = this._createRenderTarget(this._sizeX, this._sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter, variable.numberOfTargets); | |
| if (variable.initialValueTexture || variable.initialValueTextures.some(el => el !== null)) { | |
| if (variable.numberOfTargets === 1) { | |
| this._renderTexture(variable.initialValueTexture, variable.renderTargets[0]); | |
| this._renderTexture(variable.initialValueTexture, variable.renderTargets[1]); | |
| } else { | |
| this._renderTexture(variable.initialValueTextures, variable.renderTargets[0], variable.passThruShader, variable.passThruUniforms); | |
| this._renderTexture(variable.initialValueTextures, variable.renderTargets[1], variable.passThruShader, variable.passThruUniforms); | |
| } | |
| } | |
| // Adds dependencies uniforms to the ShaderMaterial | |
| const material = variable.material; | |
| const uniforms = material.uniforms; | |
| uniforms['uLoopCounter'] = { value: 0 } | |
| if (variable.dependencies !== null) { | |
| for (let d = 0; d < variable.dependencies.length; d++) { | |
| const depVar = variable.dependencies[d]; | |
| const depNames = depVar.names || [depVar.name]; | |
| for (const depName of depNames) { | |
| // Checks if variable exists | |
| let found = false; | |
| for (let j = 0; j < this._variables.length; j++) { | |
| const target = this._variables[j]; | |
| const targetNames = target.names || [target.name]; | |
| if (targetNames.includes(depName)) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| return 'Variable dependency not found. Variable=' + variable.name + ', dependency=' + depName; | |
| } | |
| uniforms[depName] = { value: null }; | |
| material.fragmentShader = '\nuniform sampler2D ' + depName + ';\n' + material.fragmentShader; | |
| } | |
| } | |
| } | |
| } | |
| return null; | |
| }; | |
| /** | |
| * Executes the compute. This method is usually called in the animation loop. | |
| */ | |
| compute() { | |
| for (let i = 0, il = this._variables.length; i < il; i++) { | |
| const variable = this._variables[i]; | |
| for (let j = 0; j < variable.loopFor; j++) { | |
| // Sets texture dependencies uniforms | |
| if (variable.dependencies !== null) { | |
| const uniforms = variable.material.uniforms; | |
| for (let d = 0, dl = variable.dependencies.length; d < dl; d++) { | |
| const depVar = variable.dependencies[d]; | |
| if (depVar.numberOfTargets === 1) { | |
| uniforms[depVar.name].value = depVar.renderTargets[depVar.currentTextureIndex].texture; | |
| } else { | |
| for (let k = 0; k < depVar.names.length; k++) { | |
| const depName = depVar.names[k]; | |
| if (uniforms[depName]) { | |
| uniforms[depName].value = depVar.renderTargets[depVar.currentTextureIndex].textures[k]; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| variable.material.uniforms.uLoopCounter.value = j; | |
| // Performs the computation for this variable | |
| const nextTextureIndex = variable.currentTextureIndex === 0 ? 1 : 0; | |
| this.doRenderTarget(variable.material, variable.renderTargets[nextTextureIndex]); | |
| variable.currentTextureIndex = nextTextureIndex; | |
| } | |
| } | |
| }; | |
| /** | |
| * Returns the current render target for the given compute variable. | |
| * | |
| * @param {Object} variable - The compute variable. | |
| * @return {WebGLRenderTarget} The current render target. | |
| */ | |
| getCurrentRenderTarget(variable) { | |
| return variable.renderTargets[variable.currentTextureIndex]; | |
| } | |
| /** | |
| * Returns the alternate (ping-pong) render target for the given compute variable. | |
| * | |
| * @param {Object} variable - The compute variable. | |
| * @return {WebGLRenderTarget} The alternate render target. | |
| */ | |
| getAlternateRenderTarget(variable) { | |
| const altIndex = variable.currentTextureIndex === 0 ? 1 : 0; | |
| return variable.renderTargets[altIndex]; | |
| } | |
| /** | |
| * Internal helper to get a texture at a specific buffer index and output name. | |
| * | |
| * @param {Object} variable - The compute variable. | |
| * @param {number} bufferIndex - Index of the ping-pong buffer (0 or 1). | |
| * @param {?string} [name=undefined] - Optional output name for multiple outputs. | |
| * @return {Texture} The selected render target. | |
| */ | |
| _getTextureAt(variable, bufferIndex, name = undefined) { | |
| if (variable.numberOfTargets === 1) { | |
| return variable.renderTargets[bufferIndex].texture; | |
| } else { | |
| const names = variable.names || [variable.name]; | |
| const location = names.indexOf(name); | |
| const finalLocation = location >= 0 ? location : 0; | |
| return variable.renderTargets[bufferIndex].textures[finalLocation]; | |
| } | |
| } | |
| /** | |
| * Returns the current Texture for the given compute variable and name if defined | |
| * | |
| * If the compute variable has multiple outputs, you can optionally specify the output name. | |
| * If the name is not provided or not found, the first output is used by default. | |
| * | |
| * @param {Object} variable - The compute variable. | |
| * @param {?string} [name=undefined] - Optional output name. | |
| * @return {Texture} The current render target. | |
| */ | |
| getCurrentTexture(variable, name = undefined) { | |
| return this._getTextureAt(variable, variable.currentTextureIndex, name); | |
| } | |
| /** | |
| * Returns the alternate (ping-pong) Texture for the given compute variable and name if defined | |
| * | |
| * If the compute variable has multiple outputs, you can optionally specify the output name. | |
| * If the name is not provided or not found, the first output is used by default. | |
| * | |
| * @param {Object} variable - The compute variable. | |
| * @param {?string} [name=undefined] - Optional output name. | |
| * @return {Texture} The current render target. | |
| */ | |
| getAlternateTexture(variable, name = undefined) { | |
| return this._getTextureAt(variable, variable.currentTextureIndex, name); | |
| } | |
| /** | |
| * Frees all internal resources. Call this method if you don't need the | |
| * renderer anymore. | |
| */ | |
| dispose() { | |
| this._quad.dispose(); | |
| const variables = this._variables; | |
| for (let i = 0; i < variables.length; i++) { | |
| const variable = variables[i]; | |
| if (variable.initialValueTexture) variable.initialValueTexture.dispose(); | |
| const renderTargets = variable.renderTargets; | |
| for (let j = 0; j < renderTargets.length; j++) { | |
| const renderTarget = renderTargets[j]; | |
| renderTarget.dispose(); | |
| } | |
| } | |
| }; | |
| /** | |
| * Adds a resolution defined for the given material shader. | |
| * | |
| * @param {Object} materialShader - The material shader. | |
| */ | |
| _addResolutionDefine(materialShader) { | |
| materialShader.defines.resolution = 'vec2( ' + this._sizeX.toFixed(1) + ', ' + this._sizeY.toFixed(1) + ' )'; | |
| }; | |
| // The following functions can be used to compute things manually | |
| /** | |
| * | |
| * @param {string} computeFragmentShader - The compute (fragment) shader source. | |
| * @param {Object} uniforms - An object specifying the uniforms to be passed to the shader code. | |
| * @param {?string} [glslVersion=GLS1] - Defines the GLSL version of custom shader code. Valid values are THREE.GLSL1 or THREE.GLSL3. | |
| * @returns {ShaderMaterial} | |
| */ | |
| createShaderMaterial(computeFragmentShader, uniforms, glslVersion = GLSL1) { | |
| uniforms = uniforms || {}; | |
| let parameters = { | |
| name: 'GPUComputationShader', | |
| uniforms: uniforms, | |
| vertexShader: this._getPassThroughVertexShader(), | |
| fragmentShader: computeFragmentShader | |
| }; | |
| if (glslVersion === GLSL3) | |
| parameters.glslVersion = glslVersion; | |
| const material = new ShaderMaterial(parameters); | |
| this._addResolutionDefine(material); | |
| return material; | |
| } | |
| /** | |
| * Creates a new render target from the given parameters. | |
| * | |
| * @param {number} sizeXTexture - The width of the render target. | |
| * @param {number} sizeYTexture - The height of the render target. | |
| * @param {number} wrapS - The wrapS value. | |
| * @param {number} wrapT - The wrapS value. | |
| * @param {number} minFilter - The minFilter value. | |
| * @param {number} magFilter - The magFilter value. | |
| * @return {WebGLRenderTarget} The new render target. | |
| */ | |
| _createRenderTarget(sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter, count = 1) { | |
| sizeXTexture = sizeXTexture || this._sizeX; | |
| sizeYTexture = sizeYTexture || this._sizeY; | |
| wrapS = wrapS || ClampToEdgeWrapping; | |
| wrapT = wrapT || ClampToEdgeWrapping; | |
| minFilter = minFilter || NearestFilter; | |
| magFilter = magFilter || NearestFilter; | |
| const renderTarget = new WebGLRenderTarget(sizeXTexture, sizeYTexture, { | |
| wrapS: wrapS, | |
| wrapT: wrapT, | |
| minFilter: minFilter, | |
| magFilter: magFilter, | |
| format: RGBAFormat, | |
| type: this._dataType, | |
| depthBuffer: false, | |
| count: count | |
| }); | |
| return renderTarget; | |
| }; | |
| /** | |
| * Creates a new data texture. | |
| * | |
| * @return {DataTexture} The new data texture. | |
| */ | |
| createTexture() { | |
| const data = new Float32Array(this._sizeX * this._sizeY * 4); | |
| const texture = new DataTexture(data, this._sizeX, this._sizeY, RGBAFormat, FloatType); | |
| texture.needsUpdate = true; | |
| return texture; | |
| }; | |
| /** | |
| * Renders the given texture into the given render target. | |
| * | |
| * @param {(Texture|Array<Texture>)} input - The input textures. | |
| * @param {WebGLRenderTarget} output - The output. | |
| * @param {Material} [customPassThruShader=null] - | |
| * @param {Object} [customPassThruUniforms=null] - | |
| */ | |
| _renderTexture(input, output, customPassThruShader = null, customPassThruUniforms = null) { | |
| if (customPassThruShader === null) { | |
| this._passThruUniforms.passThruTexture.value = input; | |
| this.doRenderTarget(this._passThruShader, output); | |
| this._passThruUniforms.passThruTexture.value = null; | |
| } else { | |
| for (let i = 0; i < input.lenght; i++) { | |
| customPassThruUniforms['passThruTexture_' + i].value = input[i]; | |
| } | |
| this.doRenderTarget(customPassThruShader, output); | |
| for (let i = 0; i < input.lenght; i++) { | |
| customPassThruUniforms['passThruTexture_' + i].value = null; | |
| } | |
| } | |
| }; | |
| /** | |
| * Renders the given material into the given render target | |
| * with a full-screen pass. | |
| * | |
| * @param {Material} material - The material. | |
| * @param {WebGLRenderTarget} output - The output. | |
| */ | |
| doRenderTarget(material, output) { | |
| const currentRenderTarget = this._renderer.getRenderTarget(); | |
| const currentXrEnabled = this._renderer.xr.enabled; | |
| const currentShadowAutoUpdate = this._renderer.shadowMap.autoUpdate; | |
| this._renderer.xr.enabled = false; // Avoid camera modification | |
| this._renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows | |
| this._quad.material = material; | |
| this._renderer.setRenderTarget(output); | |
| this._quad.render(this._renderer); | |
| this._quad.material = this._passThruShader; | |
| this._renderer.xr.enabled = currentXrEnabled; | |
| this._renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; | |
| this._renderer.setRenderTarget(currentRenderTarget); | |
| }; | |
| } | |
| export { GPUComputationRenderer }; | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment