Bläddra i källkod

TSL: Introduce `renderOutput()` (#28781)

* Introduce `renderOutput()`

* cleanup

* cleanup

* update

* Update webgpu_postprocessing_3dlut.html

* cleanup

* rename defaultColorTransform  -> outputColorTransform
sunag 1 år sedan
förälder
incheckning
74d2d41176

+ 3 - 2
examples/webgpu_backdrop.html

@@ -25,7 +25,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { float, vec3, color, toneMapping, viewportSharedTexture, viewportTopLeft, checker, uv, timerLocal, oscSine, output } from 'three/tsl';
+			import { float, vec3, color, viewportSharedTexture, viewportTopLeft, checker, uv, timerLocal, oscSine, output } from 'three/tsl';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
@@ -121,7 +121,8 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, .3 );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 0.3;
 				document.body.appendChild( renderer.domElement );
 
 				const controls = new OrbitControls( camera, renderer.domElement );

+ 3 - 2
examples/webgpu_backdrop_area.html

@@ -25,7 +25,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { color, linearDepth, toneMapping, viewportLinearDepth, viewportSharedTexture, viewportMipTexture, viewportTopLeft, checker, uv, modelScale } from 'three/tsl';
+			import { color, linearDepth, viewportLinearDepth, viewportSharedTexture, viewportMipTexture, viewportTopLeft, checker, uv, modelScale } from 'three/tsl';
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 
@@ -118,7 +118,8 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, .2 );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 0.2;
 				document.body.appendChild( renderer.domElement );
 
 				const controls = new OrbitControls( camera, renderer.domElement );

+ 2 - 2
examples/webgpu_cubemap_adjustments.html

@@ -27,7 +27,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { uniform, mix, pmremTexture, reference, positionLocal, positionWorld, normalWorld, positionWorldDirection, reflectVector, toneMapping } from 'three/tsl';
+			import { uniform, mix, pmremTexture, reference, positionLocal, positionWorld, normalWorld, positionWorldDirection, reflectVector } from 'three/tsl';
 
 			import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
 
@@ -135,7 +135,7 @@
 				renderer = new THREE.WebGPURenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, 1 );
+				renderer.toneMapping = THREE.LinearToneMapping;
 				renderer.setAnimationLoop( render );
 				container.appendChild( renderer.domElement );
 

+ 2 - 2
examples/webgpu_cubemap_mix.html

@@ -27,7 +27,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { mix, oscSine, timerLocal, pmremTexture, float, toneMapping } from 'three/tsl';
+			import { mix, oscSine, timerLocal, pmremTexture, float } from 'three/tsl';
 
 			import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
 
@@ -80,7 +80,7 @@
 
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, 1 );
+				renderer.toneMapping = THREE.LinearToneMapping;
 				renderer.setAnimationLoop( render );
 				container.appendChild( renderer.domElement );
 

+ 3 - 2
examples/webgpu_portal.html

@@ -25,7 +25,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { pass, color, mx_worley_noise_float, timerLocal, viewportTopLeft, vec2, uv, normalWorld, mx_fractal_noise_vec3, toneMapping } from 'three/tsl';
+			import { pass, color, mx_worley_noise_float, timerLocal, viewportTopLeft, vec2, uv, normalWorld, mx_fractal_noise_vec3 } from 'three/tsl';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
@@ -143,7 +143,8 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, .15 );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 0.15;
 				document.body.appendChild( renderer.domElement );
 
 				//

+ 12 - 7
examples/webgpu_postprocessing_3dlut.html

@@ -27,8 +27,9 @@
 		</script>
 
 		<script type="module">
+
 			import * as THREE from 'three';
-			import { pass, texture3D, uniform } from 'three/tsl';
+			import { pass, texture3D, uniform, renderOutput } from 'three/tsl';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
@@ -126,18 +127,22 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMapping = THREE.NoToneMapping;
-				renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
 				container.appendChild( renderer.domElement );
 
-				// postprocessing
+				// post processing
 
 				postProcessing = new THREE.PostProcessing( renderer );
 
-				const scenePass = pass( scene, camera );
-				const scenePassColor = scenePass.getTextureNode();
+				// ignore default output color transform ( toneMapping and outputColorSpace )
+				// use renderOutput() for control the sequence
+
+				postProcessing.outputColorTransform = false;
 
-				const outputPass = scenePassColor.toneMapping( THREE.ACESFilmicToneMapping ).linearTosRGB();
+				// scene pass
+
+				const scenePass = pass( scene, camera );
+				const outputPass = renderOutput( scenePass );
 
 				lutPass = outputPass.lut3D();
 				lutPass.lutNode = texture3D( lutMap[ params.lut ] );

+ 3 - 2
examples/webgpu_skinning.html

@@ -25,7 +25,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { toneMapping, color, viewportTopLeft } from 'three/tsl';
+			import { color, viewportTopLeft } from 'three/tsl';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
@@ -75,7 +75,8 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, .4 );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 0.4;
 				document.body.appendChild( renderer.domElement );
 
 				window.addEventListener( 'resize', onWindowResize );

+ 1 - 0
src/nodes/Nodes.js

@@ -133,6 +133,7 @@ export { default as DotScreenNode, dotScreen } from './display/DotScreenNode.js'
 export { default as RGBShiftNode, rgbShift } from './display/RGBShiftNode.js';
 export { default as FilmNode, film } from './display/FilmNode.js';
 export { default as Lut3DNode, lut3D } from './display/Lut3DNode.js';
+export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js';
 
 export { default as PassNode, pass, texturePass, depthPass } from './display/PassNode.js';
 

+ 0 - 9
src/nodes/display/AfterImageNode.js

@@ -9,7 +9,6 @@ import { sign, max } from '../math/MathNode.js';
 import QuadMesh from '../../renderers/common/QuadMesh.js';
 
 import { Vector2 } from '../../math/Vector2.js';
-import { NoToneMapping } from '../../constants.js';
 import { RenderTarget } from '../../core/RenderTarget.js';
 
 const _size = new Vector2();
@@ -67,17 +66,12 @@ class AfterImageNode extends TempNode {
 
 		this.setSize( _size.x, _size.y );
 
-
-		const currentToneMapping = renderer.toneMapping;
-		const currentToneMappingNode = renderer.toneMappingNode;
 		const currentRenderTarget = renderer.getRenderTarget();
 		const currentTexture = textureNode.value;
 
 		this.textureNodeOld.value = this._oldRT.texture;
 
 		// comp
-		renderer.toneMapping = NoToneMapping;
-		renderer.toneMappingNode = null;
 		renderer.setRenderTarget( this._compRT );
 		quadMeshComp.render( renderer );
 
@@ -86,9 +80,6 @@ class AfterImageNode extends TempNode {
 		this._oldRT = this._compRT;
 		this._compRT = temp;
 
-
-		renderer.toneMapping = currentToneMapping;
-		renderer.toneMappingNode = currentToneMappingNode;
 		renderer.setRenderTarget( currentRenderTarget );
 		textureNode.value = currentTexture;
 

+ 1 - 7
src/nodes/display/PassNode.js

@@ -6,7 +6,7 @@ import { nodeObject } from '../shadernode/ShaderNode.js';
 import { uniform } from '../core/UniformNode.js';
 import { viewZToOrthographicDepth, perspectiveDepthToViewZ } from './ViewportDepthNode.js';
 
-import { HalfFloatType, NoToneMapping/*, FloatType*/ } from '../../constants.js';
+import { HalfFloatType/*, FloatType*/ } from '../../constants.js';
 import { Vector2 } from '../../math/Vector2.js';
 import { DepthTexture } from '../../textures/DepthTexture.js';
 import { RenderTarget } from '../../core/RenderTarget.js';
@@ -147,21 +147,15 @@ class PassNode extends TempNode {
 
 		this.setSize( size.width, size.height );
 
-		const currentToneMapping = renderer.toneMapping;
-		const currentToneMappingNode = renderer.toneMappingNode;
 		const currentRenderTarget = renderer.getRenderTarget();
 
 		this._cameraNear.value = camera.near;
 		this._cameraFar.value = camera.far;
 
-		renderer.toneMapping = NoToneMapping;
-		renderer.toneMappingNode = null;
 		renderer.setRenderTarget( this.renderTarget );
 
 		renderer.render( scene, camera );
 
-		renderer.toneMapping = currentToneMapping;
-		renderer.toneMappingNode = currentToneMappingNode;
 		renderer.setRenderTarget( currentRenderTarget );
 
 	}

+ 56 - 0
src/nodes/display/RenderOutputNode.js

@@ -0,0 +1,56 @@
+import TempNode from '../core/TempNode.js';
+import { addNodeClass } from '../core/Node.js';
+import { addNodeElement, nodeObject } from '../shadernode/ShaderNode.js';
+
+import { SRGBColorSpace, NoToneMapping } from '../../constants.js';
+
+class RenderOutputNode extends TempNode {
+
+	constructor( colorNode, toneMapping, outputColorSpace ) {
+
+		super( 'vec4' );
+
+		this.colorNode = colorNode;
+		this.toneMapping = toneMapping;
+		this.outputColorSpace = outputColorSpace;
+
+		this.isRenderOutput = true;
+
+	}
+
+	setup( { context } ) {
+
+		let outputNode = this.colorNode || context.color;
+
+		// tone mapping
+
+		const toneMapping = this.toneMapping !== null ? this.toneMapping : context.toneMapping;
+		const outputColorSpace = this.outputColorSpace !== null ? this.outputColorSpace : context.outputColorSpace;
+
+		if ( toneMapping !== NoToneMapping ) {
+
+			outputNode = outputNode.toneMapping( toneMapping );
+
+		}
+
+		// output color space
+
+		if ( outputColorSpace === SRGBColorSpace ) {
+
+			outputNode = outputNode.linearToColorSpace( outputColorSpace );
+
+		}
+
+		return outputNode;
+
+	}
+
+}
+
+export default RenderOutputNode;
+
+export const renderOutput = ( color, toneMapping = null, outputColorSpace = null ) => nodeObject( new RenderOutputNode( nodeObject( color ), toneMapping, outputColorSpace ) );
+
+addNodeElement( 'renderOutput', renderOutput );
+
+addNodeClass( 'RenderOutputNode', RenderOutputNode );

+ 54 - 7
src/renderers/common/PostProcessing.js

@@ -1,4 +1,5 @@
-import { vec4, NodeMaterial } from '../../nodes/Nodes.js';
+import { vec4, renderOutput, NodeMaterial } from '../../nodes/Nodes.js';
+import { LinearSRGBColorSpace, NoToneMapping } from '../../constants.js';
 import QuadMesh from '../../renderers/common/QuadMesh.js';
 
 const quadMesh = new QuadMesh( new NodeMaterial() );
@@ -10,27 +11,73 @@ class PostProcessing {
 		this.renderer = renderer;
 		this.outputNode = outputNode;
 
+		this.outputColorTransform = true;
+
+		this.needsUpdate = true;
+
 	}
 
 	render() {
 
-		quadMesh.material.fragmentNode = this.outputNode;
+		this.update();
+
+		const renderer = this.renderer;
+
+		const toneMapping = renderer.toneMapping;
+		const outputColorSpace = renderer.outputColorSpace;
+
+		renderer.toneMapping = NoToneMapping;
+		renderer.outputColorSpace = LinearSRGBColorSpace;
+
+		//
 
 		quadMesh.render( this.renderer );
 
+		//
+
+		renderer.toneMapping = toneMapping;
+		renderer.outputColorSpace = outputColorSpace;
+
 	}
 
-	renderAsync() {
+	update() {
+
+		if ( this.needsUpdate === true ) {
+
+			const renderer = this.renderer;
 
-		quadMesh.material.fragmentNode = this.outputNode;
+			const toneMapping = renderer.toneMapping;
+			const outputColorSpace = renderer.outputColorSpace;
 
-		return quadMesh.renderAsync( this.renderer );
+			quadMesh.material.fragmentNode = this.outputColorTransform === true ? renderOutput( this.outputNode, toneMapping, outputColorSpace ) : this.outputNode.context( { toneMapping, outputColorSpace } );
+			quadMesh.material.needsUpdate = true;
+
+			this.needsUpdate = false;
+
+		}
 
 	}
 
-	set needsUpdate( value ) {
+	async renderAsync() {
+
+		this.update();
+
+		const renderer = this.renderer;
+
+		const toneMapping = renderer.toneMapping;
+		const outputColorSpace = renderer.outputColorSpace;
+
+		renderer.toneMapping = NoToneMapping;
+		renderer.outputColorSpace = LinearSRGBColorSpace;
+
+		//
+
+		await quadMesh.renderAsync( this.renderer );
+
+		//
 
-		quadMesh.material.needsUpdate = value;
+		renderer.toneMapping = toneMapping;
+		renderer.outputColorSpace = outputColorSpace;
 
 	}
 

+ 14 - 7
src/renderers/common/Renderer.js

@@ -76,10 +76,6 @@ class Renderer {
 
 		this.info = new Info();
 
-		// nodes
-
-		this.toneMappingNode = null;
-
 		// internals
 
 		this._pixelRatio = 1;
@@ -439,7 +435,7 @@ class Renderer {
 
 		const { currentColorSpace } = this;
 
-		const useToneMapping = this._renderTarget === null && ( this.toneMapping !== NoToneMapping || this.toneMappingNode !== null );
+		const useToneMapping = this._renderTarget === null && ( this.toneMapping !== NoToneMapping );
 		const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace;
 
 		if ( useToneMapping === false && useColorSpace === false ) return null;
@@ -683,7 +679,12 @@ class Renderer {
 
 			this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel );
 
-			_quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture );
+			if ( this._nodes.hasOutputChange( renderTarget.texture ) ) {
+
+				_quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture );
+				_quad.material.needsUpdate = true;
+
+			}
 
 			this._renderScene( _quad, _quad.camera, false );
 
@@ -966,7 +967,13 @@ class Renderer {
 			// If a color space transform or tone mapping is required,
 			// the clear operation clears the intermediate renderTarget texture, but does not update the screen canvas.
 
-			_quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture );
+			if ( this._nodes.hasOutputChange( renderTarget.texture ) ) {
+
+				_quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture );
+				_quad.material.needsUpdate = true;
+
+			}
+
 			this._renderScene( _quad, _quad.camera, false );
 
 		}

+ 17 - 14
src/renderers/common/nodes/Nodes.js

@@ -1,9 +1,11 @@
 import DataMap from '../DataMap.js';
 import ChainMap from '../ChainMap.js';
 import NodeBuilderState from './NodeBuilderState.js';
-import { NodeFrame, vec4, objectGroup, renderGroup, frameGroup, cubeTexture, texture, rangeFog, densityFog, reference, viewportBottomLeft, normalWorld, pmremTexture, viewportTopLeft } from '../../../nodes/Nodes.js';
+import { NodeFrame, objectGroup, renderGroup, frameGroup, cubeTexture, texture, rangeFog, densityFog, reference, viewportBottomLeft, normalWorld, pmremTexture, viewportTopLeft } from '../../../nodes/Nodes.js';
 
-import { EquirectangularReflectionMapping, EquirectangularRefractionMapping, NoToneMapping, SRGBColorSpace } from '../../../constants.js';
+import { EquirectangularReflectionMapping, EquirectangularRefractionMapping } from '../../../constants.js';
+
+const outputNodeMap = new WeakMap();
 
 class Nodes extends DataMap {
 
@@ -391,29 +393,30 @@ class Nodes extends DataMap {
 
 	}
 
-	getOutputNode( outputTexture ) {
+	getOutputCacheKey() {
 
-		let output = texture( outputTexture, viewportTopLeft );
+		const renderer = this.renderer;
 
-		if ( this.isToneMappingState ) {
+		return renderer.toneMapping + ',' + renderer.currentColorSpace;
 
-			if ( this.renderer.toneMappingNode ) {
+	}
 
-				output = vec4( this.renderer.toneMappingNode.context( { color: output.rgb } ), output.a );
+	hasOutputChange( outputTarget ) {
 
-			} else if ( this.renderer.toneMapping !== NoToneMapping ) {
+		const cacheKey = outputNodeMap.get( outputTarget );
 
-				output = output.toneMapping( this.renderer.toneMapping );
+		return cacheKey !== this.getOutputCacheKey();
 
-			}
+	}
 
-		}
+	getOutputNode( outputTexture ) {
 
-		if ( this.renderer.currentColorSpace === SRGBColorSpace ) {
+		const renderer = this.renderer;
+		const cacheKey = this.getOutputCacheKey();
 
-			output = output.linearToColorSpace( this.renderer.currentColorSpace );
+		const output = texture( outputTexture, viewportTopLeft ).renderOutput( renderer.toneMapping, renderer.currentColorSpace );
 
-		}
+		outputNodeMap.set( outputTexture, cacheKey );
 
 		return output;