Просмотр исходного кода

WebGPURenderer: Support for access previous frame textures using `pass()` (#29069)

* PassNode: Support for previous frame textures

* add support to change render target textures

* add `webgpu_postprocessing_difference` example

* revision

* Update webgpu_postprocessing_difference.html

* revision

* revision

* revision

* update screenshot
sunag 1 год назад
Родитель
Сommit
fffc94136f

+ 1 - 0
examples/files.json

@@ -378,6 +378,7 @@
 		"webgpu_postprocessing_bloom",
 		"webgpu_postprocessing_bloom",
 		"webgpu_postprocessing_bloom_emissive",
 		"webgpu_postprocessing_bloom_emissive",
 		"webgpu_postprocessing_bloom_selective",
 		"webgpu_postprocessing_bloom_selective",
+		"webgpu_postprocessing_difference",
 		"webgpu_postprocessing_dof",
 		"webgpu_postprocessing_dof",
 		"webgpu_postprocessing_pixel",
 		"webgpu_postprocessing_pixel",
 		"webgpu_postprocessing_fxaa",
 		"webgpu_postprocessing_fxaa",

BIN
examples/screenshots/webgpu_postprocessing_bloom_emissive.jpg


BIN
examples/screenshots/webgpu_postprocessing_bloom_selective.jpg


BIN
examples/screenshots/webgpu_postprocessing_difference.jpg


+ 130 - 0
examples/webgpu_postprocessing_difference.html

@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - frame difference</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a>
+			<br/>saturated color of objects according to the difference from one frame to another
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.webgpu.js",
+					"three/tsl": "../build/three.webgpu.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import { pass, luminance } from 'three/tsl';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { Timer } from 'three/addons/misc/Timer.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			const params = {
+				speed: 0
+			};
+
+			let camera, renderer, postProcessing;
+			let timer, mesh, controls;
+
+			init();
+
+			function init() {
+
+				renderer = new THREE.WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.NeutralToneMapping;
+				document.body.appendChild( renderer.domElement );
+
+				//
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 100 );
+				camera.position.set( 1, 2, 3 );
+
+				const scene = new THREE.Scene();
+				scene.fog = new THREE.Fog( 0x0487e2, 7, 25 );
+				scene.background = new THREE.Color( 0x0487e2 );
+
+				timer = new Timer();
+
+				const texture = new THREE.TextureLoader().load( 'textures/crate.gif' );
+				texture.colorSpace = THREE.SRGBColorSpace;
+
+				const geometry = new THREE.BoxGeometry();
+				const material = new THREE.MeshBasicMaterial( { map: texture } );
+
+				mesh = new THREE.Mesh( geometry, material );
+				scene.add( mesh );
+
+				// post processing
+
+				postProcessing = new THREE.PostProcessing( renderer );
+
+				const scenePass = pass( scene, camera );
+
+				const currentTexture = scenePass.getTextureNode();
+				const previousTexture = scenePass.getPreviousTextureNode();
+
+				const frameDiff = previousTexture.sub( currentTexture ).abs();
+
+				const saturationAmount = luminance( frameDiff ).mul( 1000 ).clamp( 0, 3 );
+
+				postProcessing.outputNode = currentTexture.saturation( saturationAmount );
+
+				//
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 2;
+				controls.maxDistance = 10;
+				controls.enableDamping = true;
+				controls.dampingFactor = 0.01;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				//
+
+				const gui = new GUI();
+				gui.add( params, 'speed', 0, 2 );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				timer.update();
+
+				controls.update();
+
+				mesh.rotation.y += timer.getDelta() * 5 * params.speed;
+
+				postProcessing.render();
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 71 - 3
src/nodes/display/PassNode.js

@@ -43,17 +43,24 @@ class PassTextureNode extends TextureNode {
 
 
 class PassMultipleTextureNode extends PassTextureNode {
 class PassMultipleTextureNode extends PassTextureNode {
 
 
-	constructor( passNode, textureName ) {
+	constructor( passNode, textureName, previousTexture = false ) {
 
 
 		super( passNode, null );
 		super( passNode, null );
 
 
 		this.textureName = textureName;
 		this.textureName = textureName;
+		this.previousTexture = previousTexture;
+
+	}
+
+	updateTexture() {
+
+		this.value = this.previousTexture ? this.passNode.getPreviousTexture( this.textureName ) : this.passNode.getTexture( this.textureName );
 
 
 	}
 	}
 
 
 	setup( builder ) {
 	setup( builder ) {
 
 
-		this.value = this.passNode.getTexture( this.textureName );
+		this.updateTexture();
 
 
 		return super.setup( builder );
 		return super.setup( builder );
 
 
@@ -61,7 +68,7 @@ class PassMultipleTextureNode extends PassTextureNode {
 
 
 	clone() {
 	clone() {
 
 
-		return new this.constructor( this.passNode, this.textureName );
+		return new this.constructor( this.passNode, this.textureName, this.previousTexture );
 
 
 	}
 	}
 
 
@@ -104,6 +111,9 @@ class PassNode extends TempNode {
 		this._linearDepthNodes = {};
 		this._linearDepthNodes = {};
 		this._viewZNodes = {};
 		this._viewZNodes = {};
 
 
+		this._previousTextures = {};
+		this._previousTextureNodes = {};
+
 		this._cameraNear = uniform( 0 );
 		this._cameraNear = uniform( 0 );
 		this._cameraFar = uniform( 0 );
 		this._cameraFar = uniform( 0 );
 
 
@@ -155,6 +165,44 @@ class PassNode extends TempNode {
 
 
 	}
 	}
 
 
+	getPreviousTexture( name ) {
+
+		let texture = this._previousTextures[ name ];
+
+		if ( texture === undefined ) {
+
+			texture = this.getTexture( name ).clone();
+			texture.isRenderTargetTexture = true;
+
+			this._previousTextures[ name ] = texture;
+
+		}
+
+		return texture;
+
+	}
+
+	toggleTexture( name ) {
+
+		const prevTexture = this._previousTextures[ name ];
+
+		if ( prevTexture !== undefined ) {
+
+			const texture = this._textures[ name ];
+
+			const index = this.renderTarget.textures.indexOf( texture );
+			this.renderTarget.textures[ index ] = prevTexture;
+
+			this._textures[ name ] = prevTexture;
+			this._previousTextures[ name ] = texture;
+
+			this._textureNodes[ name ].updateTexture();
+			this._previousTextureNodes[ name ].updateTexture();
+
+		}
+
+	}
+
 	getTextureNode( name = 'output' ) {
 	getTextureNode( name = 'output' ) {
 
 
 		let textureNode = this._textureNodes[ name ];
 		let textureNode = this._textureNodes[ name ];
@@ -169,6 +217,20 @@ class PassNode extends TempNode {
 
 
 	}
 	}
 
 
+	getPreviousTextureNode( name = 'output' ) {
+
+		let textureNode = this._previousTextureNodes[ name ];
+
+		if ( textureNode === undefined ) {
+
+			this._previousTextureNodes[ name ] = textureNode = nodeObject( new PassMultipleTextureNode( this, name, true ) );
+
+		}
+
+		return textureNode;
+
+	}
+
 	getViewZNode( name = 'depth' ) {
 	getViewZNode( name = 'depth' ) {
 
 
 		let viewZNode = this._viewZNodes[ name ];
 		let viewZNode = this._viewZNodes[ name ];
@@ -240,6 +302,12 @@ class PassNode extends TempNode {
 		this._cameraNear.value = camera.near;
 		this._cameraNear.value = camera.near;
 		this._cameraFar.value = camera.far;
 		this._cameraFar.value = camera.far;
 
 
+		for ( const name in this._previousTextures ) {
+
+			this.toggleTexture( name );
+
+		}
+
 		renderer.setRenderTarget( this.renderTarget );
 		renderer.setRenderTarget( this.renderTarget );
 		renderer.setMRT( this._mrt );
 		renderer.setMRT( this._mrt );
 
 

+ 24 - 0
src/renderers/common/RenderContext.js

@@ -38,6 +38,30 @@ class RenderContext {
 
 
 	}
 	}
 
 
+	getCacheKey() {
+
+		return getCacheKey( this );
+
+	}
+
+}
+
+export function getCacheKey( renderContext ) {
+
+	const { textures, activeCubeFace } = renderContext;
+
+	let key = '';
+
+	for ( const texture of textures ) {
+
+		key += texture.id + ',';
+
+	}
+
+	key += activeCubeFace;
+
+	return key;
+
 }
 }
 
 
 export default RenderContext;
 export default RenderContext;

+ 31 - 27
src/renderers/webgl-fallback/WebGLBackend.js

@@ -1,5 +1,6 @@
 import GLSLNodeBuilder from './nodes/GLSLNodeBuilder.js';
 import GLSLNodeBuilder from './nodes/GLSLNodeBuilder.js';
 import Backend from '../common/Backend.js';
 import Backend from '../common/Backend.js';
+import { getCacheKey } from '../common/RenderContext.js';
 
 
 import WebGLAttributeUtils from './utils/WebGLAttributeUtils.js';
 import WebGLAttributeUtils from './utils/WebGLAttributeUtils.js';
 import WebGLState from './utils/WebGLState.js';
 import WebGLState from './utils/WebGLState.js';
@@ -255,12 +256,13 @@ class WebGLBackend extends Backend {
 			const renderTargetContextData = this.get( renderContext.renderTarget );
 			const renderTargetContextData = this.get( renderContext.renderTarget );
 
 
 			const { samples } = renderContext.renderTarget;
 			const { samples } = renderContext.renderTarget;
-			const fb = renderTargetContextData.framebuffer;
-
-			const mask = gl.COLOR_BUFFER_BIT;
 
 
 			if ( samples > 0 ) {
 			if ( samples > 0 ) {
 
 
+				const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ];
+
+				const mask = gl.COLOR_BUFFER_BIT;
+
 				const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer;
 				const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer;
 
 
 				const textures = renderContext.textures;
 				const textures = renderContext.textures;
@@ -1236,38 +1238,38 @@ class WebGLBackend extends Backend {
 
 
 	}
 	}
 
 
-	_setFramebuffer( renderContext ) {
+	_setFramebuffer( descriptor ) {
 
 
 		const { gl, state } = this;
 		const { gl, state } = this;
 
 
 		let currentFrameBuffer = null;
 		let currentFrameBuffer = null;
 
 
-		if ( renderContext.textures !== null ) {
+		if ( descriptor.textures !== null ) {
 
 
-			const renderTarget = renderContext.renderTarget;
+			const renderTarget = descriptor.renderTarget;
 			const renderTargetContextData = this.get( renderTarget );
 			const renderTargetContextData = this.get( renderTarget );
 			const { samples, depthBuffer, stencilBuffer } = renderTarget;
 			const { samples, depthBuffer, stencilBuffer } = renderTarget;
-			const cubeFace = this.renderer._activeCubeFace;
+
 			const isCube = renderTarget.isWebGLCubeRenderTarget === true;
 			const isCube = renderTarget.isWebGLCubeRenderTarget === true;
 
 
 			let msaaFb = renderTargetContextData.msaaFrameBuffer;
 			let msaaFb = renderTargetContextData.msaaFrameBuffer;
 			let depthRenderbuffer = renderTargetContextData.depthRenderbuffer;
 			let depthRenderbuffer = renderTargetContextData.depthRenderbuffer;
 
 
+			const cacheKey = getCacheKey( descriptor );
+
 			let fb;
 			let fb;
 
 
 			if ( isCube ) {
 			if ( isCube ) {
 
 
-				if ( renderTargetContextData.cubeFramebuffers === undefined ) {
-
-					renderTargetContextData.cubeFramebuffers = [];
-
-				}
+				renderTargetContextData.cubeFramebuffers || ( renderTargetContextData.cubeFramebuffers = {} );
 
 
-				fb = renderTargetContextData.cubeFramebuffers[ cubeFace ];
+				fb = renderTargetContextData.cubeFramebuffers[ cacheKey ];
 
 
 			} else {
 			} else {
 
 
-				fb = renderTargetContextData.framebuffer;
+				renderTargetContextData.framebuffers || ( renderTargetContextData.framebuffers = {} );
+
+				fb = renderTargetContextData.framebuffers[ cacheKey ];
 
 
 			}
 			}
 
 
@@ -1277,22 +1279,27 @@ class WebGLBackend extends Backend {
 
 
 				state.bindFramebuffer( gl.FRAMEBUFFER, fb );
 				state.bindFramebuffer( gl.FRAMEBUFFER, fb );
 
 
-				const textures = renderContext.textures;
+				const textures = descriptor.textures;
 
 
 				if ( isCube ) {
 				if ( isCube ) {
 
 
-					renderTargetContextData.cubeFramebuffers[ cubeFace ] = fb;
+					renderTargetContextData.cubeFramebuffers[ cacheKey ] = fb;
+
 					const { textureGPU } = this.get( textures[ 0 ] );
 					const { textureGPU } = this.get( textures[ 0 ] );
 
 
+					const cubeFace = this.renderer._activeCubeFace;
+
 					gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace, textureGPU, 0 );
 					gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace, textureGPU, 0 );
 
 
 				} else {
 				} else {
 
 
+					renderTargetContextData.framebuffers[ cacheKey ] = fb;
+
 					for ( let i = 0; i < textures.length; i ++ ) {
 					for ( let i = 0; i < textures.length; i ++ ) {
 
 
 						const texture = textures[ i ];
 						const texture = textures[ i ];
 						const textureData = this.get( texture );
 						const textureData = this.get( texture );
-						textureData.renderTarget = renderContext.renderTarget;
+						textureData.renderTarget = descriptor.renderTarget;
 
 
 						const attachment = gl.COLOR_ATTACHMENT0 + i;
 						const attachment = gl.COLOR_ATTACHMENT0 + i;
 
 
@@ -1300,15 +1307,13 @@ class WebGLBackend extends Backend {
 
 
 					}
 					}
 
 
-					renderTargetContextData.framebuffer = fb;
-
-					state.drawBuffers( renderContext, fb );
+					state.drawBuffers( descriptor, fb );
 
 
 				}
 				}
 
 
-				if ( renderContext.depthTexture !== null ) {
+				if ( descriptor.depthTexture !== null ) {
 
 
-					const textureData = this.get( renderContext.depthTexture );
+					const textureData = this.get( descriptor.depthTexture );
 					const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
 					const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
 
 
 					gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 );
 					gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 );
@@ -1329,11 +1334,10 @@ class WebGLBackend extends Backend {
 
 
 					const msaaRenderbuffers = [];
 					const msaaRenderbuffers = [];
 
 
-					const textures = renderContext.textures;
+					const textures = descriptor.textures;
 
 
 					for ( let i = 0; i < textures.length; i ++ ) {
 					for ( let i = 0; i < textures.length; i ++ ) {
 
 
-
 						msaaRenderbuffers[ i ] = gl.createRenderbuffer();
 						msaaRenderbuffers[ i ] = gl.createRenderbuffer();
 
 
 						gl.bindRenderbuffer( gl.RENDERBUFFER, msaaRenderbuffers[ i ] );
 						gl.bindRenderbuffer( gl.RENDERBUFFER, msaaRenderbuffers[ i ] );
@@ -1347,10 +1351,10 @@ class WebGLBackend extends Backend {
 
 
 						}
 						}
 
 
-						const texture = renderContext.textures[ i ];
+						const texture = descriptor.textures[ i ];
 						const textureData = this.get( texture );
 						const textureData = this.get( texture );
 
 
-						gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, textureData.glInternalFormat, renderContext.width, renderContext.height );
+						gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, textureData.glInternalFormat, descriptor.width, descriptor.height );
 						gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, msaaRenderbuffers[ i ] );
 						gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, msaaRenderbuffers[ i ] );
 
 
 
 
@@ -1362,7 +1366,7 @@ class WebGLBackend extends Backend {
 					if ( depthRenderbuffer === undefined ) {
 					if ( depthRenderbuffer === undefined ) {
 
 
 						depthRenderbuffer = gl.createRenderbuffer();
 						depthRenderbuffer = gl.createRenderbuffer();
-						this.textureUtils.setupRenderBufferStorage( depthRenderbuffer, renderContext );
+						this.textureUtils.setupRenderBufferStorage( depthRenderbuffer, descriptor );
 
 
 						renderTargetContextData.depthRenderbuffer = depthRenderbuffer;
 						renderTargetContextData.depthRenderbuffer = depthRenderbuffer;
 
 

+ 12 - 8
src/renderers/webgl-fallback/utils/WebGLTextureUtils.js

@@ -527,7 +527,6 @@ class WebGLTextureUtils {
 
 
 	deallocateRenderBuffers( renderTarget ) {
 	deallocateRenderBuffers( renderTarget ) {
 
 
-
 		const { gl, backend } = this;
 		const { gl, backend } = this;
 
 
 		// remove framebuffer reference
 		// remove framebuffer reference
@@ -537,31 +536,36 @@ class WebGLTextureUtils {
 
 
 			renderContextData.renderBufferStorageSetup = undefined;
 			renderContextData.renderBufferStorageSetup = undefined;
 
 
-			if ( renderContextData.framebuffer ) {
+			if ( renderContextData.framebuffers ) {
+
+				for ( const cacheKey in renderContextData.framebuffers ) {
+
+					gl.deleteFramebuffer( renderContextData.framebuffers[ cacheKey ] );
+
+				}
 
 
-				gl.deleteFramebuffer( renderContextData.framebuffer );
-				renderContextData.framebuffer = undefined;
+				delete renderContextData.framebuffers;
 
 
 			}
 			}
 
 
 			if ( renderContextData.depthRenderbuffer ) {
 			if ( renderContextData.depthRenderbuffer ) {
 
 
 				gl.deleteRenderbuffer( renderContextData.depthRenderbuffer );
 				gl.deleteRenderbuffer( renderContextData.depthRenderbuffer );
-				renderContextData.depthRenderbuffer = undefined;
+				delete renderContextData.depthRenderbuffer;
 
 
 			}
 			}
 
 
 			if ( renderContextData.stencilRenderbuffer ) {
 			if ( renderContextData.stencilRenderbuffer ) {
 
 
 				gl.deleteRenderbuffer( renderContextData.stencilRenderbuffer );
 				gl.deleteRenderbuffer( renderContextData.stencilRenderbuffer );
-				renderContextData.stencilRenderbuffer = undefined;
+				delete renderContextData.stencilRenderbuffer;
 
 
 			}
 			}
 
 
 			if ( renderContextData.msaaFrameBuffer ) {
 			if ( renderContextData.msaaFrameBuffer ) {
 
 
 				gl.deleteFramebuffer( renderContextData.msaaFrameBuffer );
 				gl.deleteFramebuffer( renderContextData.msaaFrameBuffer );
-				renderContextData.msaaFrameBuffer = undefined;
+				delete renderContextData.msaaFrameBuffer;
 
 
 			}
 			}
 
 
@@ -573,7 +577,7 @@ class WebGLTextureUtils {
 
 
 				}
 				}
 
 
-				renderContextData.msaaRenderbuffers = undefined;
+				delete renderContextData.msaaRenderbuffers;
 
 
 			}
 			}
 
 

+ 10 - 14
src/renderers/webgpu/WebGPUBackend.js

@@ -193,22 +193,16 @@ class WebGPUBackend extends Backend {
 
 
 		let descriptors = renderTargetData.descriptors;
 		let descriptors = renderTargetData.descriptors;
 
 
-		if ( descriptors === undefined ) {
-
-			descriptors = [];
-
-			renderTargetData.descriptors = descriptors;
-
-		}
-
-		if ( renderTargetData.width !== renderTarget.width ||
+		if ( descriptors === undefined ||
+			renderTargetData.width !== renderTarget.width ||
 			renderTargetData.height !== renderTarget.height ||
 			renderTargetData.height !== renderTarget.height ||
 			renderTargetData.activeMipmapLevel !== renderTarget.activeMipmapLevel ||
 			renderTargetData.activeMipmapLevel !== renderTarget.activeMipmapLevel ||
-			renderTargetData.samples !== renderTarget.samples ||
-			descriptors.length !== renderTarget.textures.length
+			renderTargetData.samples !== renderTarget.samples
 		) {
 		) {
 
 
-			descriptors.length = 0;
+			descriptors = {};
+
+			renderTargetData.descriptors = descriptors;
 
 
 			// dispose
 			// dispose
 
 
@@ -224,7 +218,9 @@ class WebGPUBackend extends Backend {
 
 
 		}
 		}
 
 
-		let descriptor = descriptors[ renderContext.activeCubeFace ];
+		const cacheKey = renderContext.getCacheKey();
+
+		let descriptor = descriptors[ cacheKey ];
 
 
 		if ( descriptor === undefined ) {
 		if ( descriptor === undefined ) {
 
 
@@ -276,7 +272,7 @@ class WebGPUBackend extends Backend {
 				depthStencilAttachment
 				depthStencilAttachment
 			};
 			};
 
 
-			descriptors[ renderContext.activeCubeFace ] = descriptor;
+			descriptors[ cacheKey ] = descriptor;
 
 
 			renderTargetData.width = renderTarget.width;
 			renderTargetData.width = renderTarget.width;
 			renderTargetData.height = renderTarget.height;
 			renderTargetData.height = renderTarget.height;

粤ICP备19079148号