Browse Source

WebGPURenderer: Apply fog before tonemapping and encoding (#27850)

* WebGPURenderer: Apply fog before tonemapping and encoding

* TSL: Add `rendererReference`

* PassNode: Add `getViewZNode()`

* FogNode: Add `.getViewZNode()`, removed `mixAssign()`

* TSL: Add `toneMappingExposure`

* PostProcessing: Add `.renderAsync()`

* Add `webgpu_custom_fog_background` example

* cleanup

* cleanup

* improve description

* update example

* FogNode: Use getViewZ() in context instead of viewZ as Node
sunag 1 year ago
parent
commit
4639edd29f

+ 1 - 0
examples/files.json

@@ -333,6 +333,7 @@
 		"webgpu_cubemap_dynamic",
 		"webgpu_cubemap_mix",
 		"webgpu_custom_fog",
+		"webgpu_custom_fog_background",
 		"webgpu_depth_texture",
 		"webgpu_equirectangular",
 		"webgpu_instance_mesh",

+ 1 - 0
examples/jsm/nodes/Nodes.js

@@ -86,6 +86,7 @@ export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTexture
 export { default as InstanceNode, instance } from './accessors/InstanceNode.js';
 export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecularColor, materialSpecularStrength, materialReflectivity, materialRoughness, materialMetalness, materialNormal, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialRotation, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLineScale, materialLineDashSize, materialLineGapSize, materialLineWidth, materialLineDashOffset, materialPointWidth } from './accessors/MaterialNode.js';
 export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js';
+export { default as RendererReferenceNode, rendererReference } from './accessors/RendererReferenceNode.js';
 export { default as MorphNode, morphReference } from './accessors/MorphNode.js';
 export { default as TextureBicubicNode, textureBicubic } from './accessors/TextureBicubicNode.js';
 export { default as ModelNode, modelDirection, modelViewMatrix, modelNormalMatrix, modelWorldMatrix, modelPosition, modelViewPosition, modelScale } from './accessors/ModelNode.js';

+ 29 - 0
examples/jsm/nodes/accessors/RendererReferenceNode.js

@@ -0,0 +1,29 @@
+import ReferenceNode from './ReferenceNode.js';
+import { addNodeClass } from '../core/Node.js';
+import { nodeObject } from '../shadernode/ShaderNode.js';
+
+class RendererReferenceNode extends ReferenceNode {
+
+	constructor( property, inputType, renderer = null ) {
+
+		super( property, inputType, renderer );
+
+		this.renderer = renderer;
+
+	}
+
+	setReference( state ) {
+
+		this.reference = this.renderer !== null ? this.renderer : state.renderer;
+
+		return this.reference;
+
+	}
+
+}
+
+export default RendererReferenceNode;
+
+export const rendererReference = ( name, type, renderer ) => nodeObject( new RendererReferenceNode( name, type, renderer ) );
+
+addNodeClass( 'RendererReferenceNode', RendererReferenceNode );

+ 17 - 1
examples/jsm/nodes/display/PassNode.js

@@ -66,6 +66,7 @@ class PassNode extends TempNode {
 		this._depthTextureNode = nodeObject( new PassTextureNode( this, depthTexture ) );
 
 		this._depthNode = null;
+		this._viewZNode = null;
 		this._cameraNear = uniform( 0 );
 		this._cameraFar = uniform( 0 );
 
@@ -91,6 +92,21 @@ class PassNode extends TempNode {
 
 	}
 
+	getViewZNode() {
+
+		if ( this._viewZNode === null ) {
+
+			const cameraNear = this._cameraNear;
+			const cameraFar = this._cameraFar;
+
+			this._viewZNode = perspectiveDepthToViewZ( this._depthTextureNode, cameraNear, cameraFar );
+
+		}
+
+		return this._viewZNode;
+
+	}
+
 	getDepthNode() {
 
 		if ( this._depthNode === null ) {
@@ -98,7 +114,7 @@ class PassNode extends TempNode {
 			const cameraNear = this._cameraNear;
 			const cameraFar = this._cameraFar;
 
-			this._depthNode = viewZToOrthographicDepth( perspectiveDepthToViewZ( this._depthTextureNode, cameraNear, cameraFar ), cameraNear, cameraFar );
+			this._depthNode = viewZToOrthographicDepth( this.getViewZNode(), cameraNear, cameraFar );
 
 		}
 

+ 8 - 4
examples/jsm/nodes/display/ToneMappingNode.js

@@ -1,11 +1,12 @@
 import TempNode from '../core/TempNode.js';
 import { addNodeClass } from '../core/Node.js';
-import { tslFn, nodeObject, float, mat3, vec3 } from '../shadernode/ShaderNode.js';
-
-import { NoToneMapping, LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping, AgXToneMapping } from 'three';
+import { addNodeElement, tslFn, nodeObject, float, mat3, vec3 } from '../shadernode/ShaderNode.js';
+import { rendererReference } from '../accessors/RendererReferenceNode.js';
 import { clamp, log2, max, pow } from '../math/MathNode.js';
 import { mul } from '../math/OperatorNode.js';
 
+import { NoToneMapping, LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping, AgXToneMapping } from 'three';
+
 // exposure only
 const LinearToneMappingNode = tslFn( ( { color, exposure } ) => {
 
@@ -127,7 +128,7 @@ const toneMappingLib = {
 
 class ToneMappingNode extends TempNode {
 
-	constructor( toneMapping = NoToneMapping, exposureNode = float( 1 ), colorNode = null ) {
+	constructor( toneMapping = NoToneMapping, exposureNode = toneMappingExposure, colorNode = null ) {
 
 		super( 'vec3' );
 
@@ -180,5 +181,8 @@ class ToneMappingNode extends TempNode {
 export default ToneMappingNode;
 
 export const toneMapping = ( mapping, exposure, color ) => nodeObject( new ToneMappingNode( mapping, nodeObject( exposure ), nodeObject( color ) ) );
+export const toneMappingExposure = rendererReference( 'toneMappingExposure', 'float' );
+
+addNodeElement( 'toneMapping', ( color, mapping, exposure ) => toneMapping( mapping, exposure, color ) );
 
 addNodeClass( 'ToneMappingNode', ToneMappingNode );

+ 4 - 5
examples/jsm/nodes/fog/FogExp2Node.js

@@ -1,5 +1,4 @@
 import FogNode from './FogNode.js';
-import { positionView } from '../accessors/PositionNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
 
@@ -15,12 +14,12 @@ class FogExp2Node extends FogNode {
 
 	}
 
-	setup() {
+	setup( builder ) {
 
-		const depthNode = positionView.z.negate();
-		const densityNode = this.densityNode;
+		const viewZ = this.getViewZNode( builder );
+		const density = this.densityNode;
 
-		return densityNode.mul( densityNode, depthNode, depthNode ).negate().exp().oneMinus();
+		return density.mul( density, viewZ, viewZ ).negate().exp().oneMinus();
 
 	}
 

+ 13 - 3
examples/jsm/nodes/fog/FogNode.js

@@ -1,5 +1,5 @@
 import Node, { addNodeClass } from '../core/Node.js';
-import { mix } from '../math/MathNode.js';
+import { positionView } from '../accessors/PositionNode.js';
 import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
 
 class FogNode extends Node {
@@ -15,9 +15,19 @@ class FogNode extends Node {
 
 	}
 
-	mixAssign( outputNode ) {
+	getViewZNode( builder ) {
 
-		return mix( outputNode, this.colorNode, this );
+		let viewZ;
+
+		const getViewZ = builder.context.getViewZ;
+
+		if ( getViewZ !== undefined ) {
+
+			viewZ = getViewZ( this );
+
+		}
+
+		return ( viewZ || positionView.z ).negate();
 
 	}
 

+ 4 - 3
examples/jsm/nodes/fog/FogRangeNode.js

@@ -1,6 +1,5 @@
 import FogNode from './FogNode.js';
 import { smoothstep } from '../math/MathNode.js';
-import { positionView } from '../accessors/PositionNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
 
@@ -17,9 +16,11 @@ class FogRangeNode extends FogNode {
 
 	}
 
-	setup() {
+	setup( builder ) {
 
-		return smoothstep( this.nearNode, this.farNode, positionView.z.negate() );
+		const viewZ = this.getViewZNode( builder );
+
+		return smoothstep( this.nearNode, this.farNode, viewZ );
 
 	}
 

+ 8 - 8
examples/jsm/nodes/materials/NodeMaterial.js

@@ -380,23 +380,23 @@ class NodeMaterial extends ShaderMaterial {
 
 		const renderer = builder.renderer;
 
-		// TONE MAPPING
+		// FOG
 
-		const toneMappingNode = builder.toneMappingNode;
+		if ( this.fog === true ) {
 
-		if ( this.toneMapped === true && toneMappingNode ) {
+			const fogNode = builder.fogNode;
 
-			outputNode = vec4( toneMappingNode.context( { color: outputNode.rgb } ), outputNode.a );
+			if ( fogNode ) outputNode = vec4( fogNode.mix( outputNode.rgb, fogNode.colorNode ), outputNode.a );
 
 		}
 
-		// FOG
+		// TONE MAPPING
 
-		if ( this.fog === true ) {
+		const toneMappingNode = builder.toneMappingNode;
 
-			const fogNode = builder.fogNode;
+		if ( this.toneMapped === true && toneMappingNode ) {
 
-			if ( fogNode ) outputNode = vec4( fogNode.mixAssign( outputNode.rgb ), outputNode.a );
+			outputNode = vec4( toneMappingNode.context( { color: outputNode.rgb } ), outputNode.a );
 
 		}
 

+ 1 - 1
examples/jsm/renderers/common/nodes/Nodes.js

@@ -275,7 +275,7 @@ class Nodes extends DataMap {
 
 			if ( rendererData.toneMapping !== rendererToneMapping ) {
 
-				const rendererToneMappingNode = rendererData.rendererToneMappingNode || toneMapping( rendererToneMapping, reference( 'toneMappingExposure', 'float', renderer ) );
+				const rendererToneMappingNode = rendererData.rendererToneMappingNode || toneMapping( rendererToneMapping );
 				rendererToneMappingNode.toneMapping = rendererToneMapping;
 
 				rendererData.rendererToneMappingNode = rendererToneMappingNode;

BIN
examples/screenshots/webgpu_custom_fog_background.jpg


+ 150 - 0
examples/webgpu_custom_fog_background.html

@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - custom fog background</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">
+		<style>
+
+			#info {
+				background-color: #0066ff;
+			}
+
+		</style>
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - webgpu custom fog background<br />
+			Battle Damaged Sci-fi Helmet by
+			<a href="https://sketchfab.com/theblueturtle_" target="_blank" rel="noopener">theblueturtle_</a><br />
+			<a href="https://hdrihaven.com/hdri/?h=royal_esplanade" target="_blank" rel="noopener">Royal Esplanade</a> by <a href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a>
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import PostProcessing from 'three/addons/renderers/common/PostProcessing.js';
+
+			import { pass, color, rangeFog } from 'three/nodes';
+
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+			let camera, scene, renderer;
+			let postProcessing;
+
+			init();
+
+			function init() {
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 20 );
+				camera.position.set( - 1.8, 0.6, 2.7 );
+
+				scene = new THREE.Scene();
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				//renderer.toneMapping = THREE.ACESFilmicToneMapping; // apply tone mapping in post processing
+				container.appendChild( renderer.domElement );
+
+				// post processing
+
+				// render scene pass ( the same of css )
+				const scenePass = pass( scene, camera );
+				const scenePassViewZ = scenePass.getViewZNode();
+
+				// background color
+				const backgroundColor = color( 0x0066ff );
+
+				// get fog factor from scene pass context
+				// equivalent to: scene.fog = new THREE.Fog( 0x0066ff, 2.7, 4 );
+				const fogFactor = rangeFog( null, 2.7, 4 ).context( { getViewZ: () => scenePassViewZ } );
+
+				// tone mapping scene pass
+				const scenePassTM = scenePass.toneMapping( THREE.ACESFilmicToneMapping );
+
+				// mix fog from fog factor and background color
+				const compose = fogFactor.mix( scenePassTM, backgroundColor );
+
+				postProcessing = new PostProcessing( renderer );
+				postProcessing.outputNode = compose;
+
+				//
+
+				new RGBELoader()
+					.setPath( 'textures/equirectangular/' )
+					.load( 'royal_esplanade_1k.hdr', function ( texture ) {
+
+						texture.mapping = THREE.EquirectangularReflectionMapping;
+
+						scene.environment = texture;
+
+						// model
+
+						const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );
+						loader.load( 'DamagedHelmet.gltf', function ( gltf ) {
+
+							scene.add( gltf.scene );
+
+							render();
+
+						} );
+
+					} );
+
+				//
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 2;
+				controls.maxDistance = 5;
+				controls.target.set( 0, - 0.1, - 0.2 );
+				controls.update();
+				controls.addEventListener( 'change', render ); // use if there is no animation loop
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			//
+
+			function render() {
+
+				postProcessing.renderAsync();
+
+			}
+
+		</script>
+
+	</body>
+</html>