GPUComputationRenderer constructor

GPUComputationRenderer(
  1. int sizeX,
  2. int sizeY,
  3. WebGLRenderer renderer
)

Implementation

GPUComputationRenderer(int sizeX, int sizeY, WebGLRenderer renderer ) {
	int dataType = FloatType;
	camera.position.z = 1;

	final Map<String,dynamic> passThruUniforms = {
		'passThruTexture': { 'value': null }
	};

	String getPassThroughVertexShader() {
		return	'void main()	{\n' +
				'\n' +
				'	gl_Position = vec4( position, 1.0 );\n' +
				'\n' +
				'}\n';
	}
	String 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';
	}
	void addResolutionDefine( materialShader ) {
		materialShader.defines['resolution'] = 'vec2( ' + sizeX.toStringAsFixed( 1 ) + ', ' + sizeY.toStringAsFixed( 1 ) + ' )';
	}

	ShaderMaterial createShaderMaterial(String computeFragmentShader,[Map<String,dynamic>? uniforms ]) {
		uniforms = uniforms ?? {};

		final material = ShaderMaterial.fromMap( {
			'name': 'GPUComputationShader',
			'uniforms': uniforms,
			'vertexShader': getPassThroughVertexShader(),
			'fragmentShader': computeFragmentShader
		});

		addResolutionDefine( material );

		return material;
	}

	final passThruShader = createShaderMaterial( getPassThroughFragmentShader(), passThruUniforms );

	final mesh = Mesh( PlaneGeometry( 2, 2 ), passThruShader );
	scene.add( mesh );


	setDataType = ( type ) {
		dataType = type;
		return this;
	};

	addVariable = ( variableName, computeFragmentShader, initialValueTexture ) {
		final material = this.createShaderMaterial( computeFragmentShader );

		final variable = {
			'name': variableName,
			'initialValueTexture': initialValueTexture,
			'material': material,
			'dependencies': null,
			'renderTargets': [],
			'wrapS': null,
			'wrapT': null,
			'minFilter': NearestFilter,
			'magFilter': NearestFilter
		};

		variables.add( variable );

		return variable;
	};

	setVariableDependencies = ( variable, dependencies ) {
		variable['dependencies'] = dependencies;
	};

	init = () {
		if ( renderer.capabilities.maxVertexTextures == 0 ) {
			return 'No support for vertex shader textures.';
		}

		for ( int i = 0; i < variables.length; i ++ ) {
			final variable = variables[ i ];

        // Creates rendertargets and initialize them with input texture
        variable['renderTargets'].add(createRenderTarget( sizeX, sizeY, variable['wrapS'], variable['wrapT'], variable['minFilter'], variable['magFilter'] ));
        variable['renderTargets'].add(createRenderTarget( sizeX, sizeY, variable['wrapS'], variable['wrapT'], variable['minFilter'], variable['magFilter'] ));
        renderTexture( variable['initialValueTexture'], variable['renderTargets'][ 0 ] );
        renderTexture( variable['initialValueTexture'], variable['renderTargets'][ 1 ] );

			// Adds dependencies uniforms to the ShaderMaterial
			final material = variable['material'] as Material?;
			final uniforms = material?.uniforms;

			if ( variable['dependencies'] != null ) {
				for (int d = 0; d < variable['dependencies'].length; d ++ ) {
					final Map<String,dynamic> depVar = variable['dependencies'][d];

					if ( depVar['name'] != variable['name'] ) {
						// Checks if variable exists
						bool found = false;

						for ( int j = 0; j < variables.length; j ++ ) {
							if ( depVar['name'] == 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}';
				}
			}
		}

		currentTextureIndex = 0;
		return null;
	};

	compute = () {

		final currentTextureIndex = this.currentTextureIndex;
		final nextTextureIndex = this.currentTextureIndex == 0 ? 1 : 0;

		for (int i = 0, il = variables.length; i < il; i ++ ) {

			final variable = variables[i];

			// Sets texture dependencies uniforms
			if ( variable['dependencies'] != null ) {
				final Map<String,dynamic> uniforms = variable['material'].uniforms;

				for ( int d = 0, dl = variable['dependencies'].length; d < dl; d ++ ) {
					final Map<String,dynamic> depVar = variable['dependencies'][ d ];
            uniforms[depVar['name']] = {'value' :depVar['renderTargets'][ currentTextureIndex ].texture};
				}
			}

			// Performs the computation for this variable
			doRenderTarget( variable['material'], variable['renderTargets'][ nextTextureIndex ] );
		}
		this.currentTextureIndex = nextTextureIndex;
	};

	getCurrentRenderTarget = (Map<String,dynamic> variable ) {
		return variable['renderTargets'][ currentTextureIndex ];
	};

	getAlternateRenderTarget = (Map<String,dynamic> variable ) {
		return variable['renderTargets'][ currentTextureIndex == 0 ? 1 : 0 ];
	};

	dispose = () {
		mesh.geometry?.dispose();
		mesh.material?.dispose();

		final variables = this.variables;

		for (int i = 0; i < variables.length; i ++ ) {
			final variable = variables[ i ];
			if ( variable['initialValueTexture'] ) variable['initialValueTexture'].dispose();
			final renderTargets = variable['renderTargets'];
			for ( int j = 0; j < renderTargets.length; j ++ ) {
				final renderTarget = renderTargets[ j ];
				renderTarget.dispose();
			}
		}
	};

	this.addResolutionDefine = addResolutionDefine;

	// The following functions can be used to compute things manually
	this.createShaderMaterial = createShaderMaterial;

	createRenderTarget = ( sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter ) {

		sizeXTexture = sizeXTexture ?? sizeX;
		sizeYTexture = sizeYTexture ?? sizeY;

		wrapS = wrapS ?? ClampToEdgeWrapping;
		wrapT = wrapT ?? ClampToEdgeWrapping;

		minFilter = minFilter ?? NearestFilter;
		magFilter = magFilter ?? NearestFilter;

		final renderTarget = WebGLRenderTarget( sizeXTexture, sizeYTexture, WebGLRenderTargetOptions({
			'wrapS': wrapS,
			'wrapT': wrapT,
			'minFilter': minFilter,
			'magFilter': magFilter,
			'format': RGBAFormat,
			'type': dataType,
			'depthBuffer': false
      }));

		return renderTarget;
	};

	createTexture = () {
		final data = Float32Array( sizeX * sizeY * 4 );
		final texture = DataTexture( data, sizeX, sizeY, RGBAFormat, FloatType );
		texture.needsUpdate = true;
		return texture;
	};

	renderTexture = (Texture input, RenderTarget output ) {
		// Takes a texture, and render out in rendertarget
		// input = Texture
		// output = RenderTarget
		passThruUniforms['passThruTexture'] = {'value': input};
		doRenderTarget( passThruShader, output );
		passThruUniforms['passThruTexture']= {'value': null};
	};

	doRenderTarget = (Material material, RenderTarget output ) {
		final currentRenderTarget = renderer.getRenderTarget();

		final currentXrEnabled = renderer.xr.enabled;
		final currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;

		renderer.xr.enabled = false; // Avoid camera modification
		renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
		mesh.material = material;
		renderer.setRenderTarget( output );
		renderer.render( scene, camera );
		mesh.material = passThruShader;

		renderer.xr.enabled = currentXrEnabled;
		renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
		renderer.setRenderTarget( currentRenderTarget );
	};
}