Skip to content

Commit

Permalink
WebGPURenderer: Apply fog before tonemapping and encoding (#27850)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sunag authored Mar 13, 2024
1 parent f4a695c commit 4639edd
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 25 deletions.
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions examples/jsm/nodes/Nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
29 changes: 29 additions & 0 deletions examples/jsm/nodes/accessors/RendererReferenceNode.js
Original file line number Diff line number Diff line change
@@ -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 );
18 changes: 17 additions & 1 deletion examples/jsm/nodes/display/PassNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );

Expand All @@ -91,14 +92,29 @@ 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 ) {

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 );

}

Expand Down
12 changes: 8 additions & 4 deletions examples/jsm/nodes/display/ToneMappingNode.js
Original file line number Diff line number Diff line change
@@ -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 } ) => {

Expand Down Expand Up @@ -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' );

Expand Down Expand Up @@ -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 );
9 changes: 4 additions & 5 deletions examples/jsm/nodes/fog/FogExp2Node.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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();

}

Expand Down
16 changes: 13 additions & 3 deletions examples/jsm/nodes/fog/FogNode.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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();

}

Expand Down
7 changes: 4 additions & 3 deletions examples/jsm/nodes/fog/FogRangeNode.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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 );

}

Expand Down
16 changes: 8 additions & 8 deletions examples/jsm/nodes/materials/NodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );

}

Expand Down
2 changes: 1 addition & 1 deletion examples/jsm/renderers/common/nodes/Nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
150 changes: 150 additions & 0 deletions examples/webgpu_custom_fog_background.html
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit 4639edd

Please sign in to comment.