Skip to content

Instantly share code, notes, and snippets.

@eipporko
Last active July 25, 2025 22:03
Show Gist options
  • Save eipporko/28f5e37831a720cedaa3e3f5a7885995 to your computer and use it in GitHub Desktop.
Save eipporko/28f5e37831a720cedaa3e3f5a7885995 to your computer and use it in GitHub Desktop.
THREE's GPUComputationRenderer – MRT Support
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