Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGPURenderer: Auto-MRT #28833

Merged
merged 25 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@
"webgpu_mirror",
"webgpu_morphtargets",
"webgpu_morphtargets_face",
"webgpu_mrt",
"webgpu_mrt_mask",
"webgpu_multiple_rendertargets",
"webgpu_multiple_rendertargets_readback",
"webgpu_multisampled_renderbuffers",
Expand Down
Binary file modified examples/screenshots/webgpu_backdrop.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/screenshots/webgpu_mrt.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/screenshots/webgpu_mrt_mask.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgpu_multiple_rendertargets.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgpu_multiple_rendertargets_readback.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion examples/webgpu_backdrop.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.toneMapping = THREE.LinearToneMapping;
renderer.toneMapping = THREE.NeutralToneMapping;
renderer.toneMappingExposure = 0.3;
document.body.appendChild( renderer.domElement );

Expand Down
152 changes: 152 additions & 0 deletions examples/webgpu_mrt.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - mrt</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> webgpu - mrt<br />
Final / Beauty / Normal / Emissive / Diffuse
</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 { output, transformedNormalWorld, pass, step, diffuseColor, emissive, viewportTopLeft, mix, mrt, tslFn } from 'three/tsl';

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

// scene

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

new RGBELoader()
.setPath( 'textures/equirectangular/' )
.load( 'royal_esplanade_1k.hdr', function ( texture ) {

texture.mapping = THREE.EquirectangularReflectionMapping;

scene.background = texture;
scene.environment = texture;

// model

const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );
loader.load( 'DamagedHelmet.gltf', function ( gltf ) {

scene.add( gltf.scene );

} );

} );

// renderer

renderer = new THREE.WebGPURenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( render );
renderer.toneMapping = THREE.ACESFilmicToneMapping;
container.appendChild( renderer.domElement );

// post processing

const scenePass = pass( scene, camera, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter } );
scenePass.setMRT( mrt( {
output: output,
normal: transformedNormalWorld.directionToColor(),
diffuse: diffuseColor,
emissive: emissive
} ) );

// optimize textures

const normalTexture = scenePass.getTexture( 'normal' );
const diffuseTexture = scenePass.getTexture( 'diffuse' );
const emissiveTexture = scenePass.getTexture( 'emissive' );

normalTexture.type = diffuseTexture.type = emissiveTexture.type = THREE.UnsignedByteType;

// post processing - mrt

postProcessing = new THREE.PostProcessing( renderer );
postProcessing.outputColorTransform = false;
postProcessing.outputNode = tslFn( () => {

const output = scenePass.getTextureNode( 'output' ); // output name is optional here
const normal = scenePass.getTextureNode( 'normal' );
const diffuse = scenePass.getTextureNode( 'diffuse' );
const emissive = scenePass.getTextureNode( 'emissive' );

const out = mix( output.renderOutput(), output, step( 0.2, viewportTopLeft.x ) );
const nor = mix( out, normal, step( 0.4, viewportTopLeft.x ) );
const emi = mix( nor, emissive, step( 0.6, viewportTopLeft.x ) );
const dif = mix( emi, diffuse, step( 0.8, viewportTopLeft.x ) );

return dif;

} )();

// controls

const controls = new OrbitControls( camera, renderer.domElement );
controls.minDistance = 2;
controls.maxDistance = 10;
controls.target.set( 0, 0, - 0.2 );
controls.update();

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

//

function render() {

postProcessing.render();

}

</script>

</body>
</html>
170 changes: 170 additions & 0 deletions examples/webgpu_mrt_mask.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - mrt mask</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> webgpu - mrt mask
<br>The mask is applied followed by a gaussian blur only on some selected materials.
</div>

<script type="importmap">
{
"imports": {
"three": "../src/Three.WebGPU.js",
"three/tsl": "../src/Three.WebGPU.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';
import { color, viewportTopLeft, mrt, output, pass, vec4 } from 'three/tsl';

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

let camera, scene, renderer;
let postProcessing;
let spheres, rotate = true;
let mixer, clock;

init();

function init() {

camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 100 );
camera.position.set( 1, 2, 3 );

scene = new THREE.Scene();
scene.backgroundNode = viewportTopLeft.y.mix( color( 0x66bbff ), color( 0x4466ff ) ).mul( .05 );
camera.lookAt( 0, 1, 0 );

clock = new THREE.Clock();

// lights

const light = new THREE.SpotLight( 0xffffff, 1 );
light.power = 2000;
camera.add( light );
scene.add( camera );

const loader = new GLTFLoader();
loader.load( 'models/gltf/Michelle.glb', function ( gltf ) {

const object = gltf.scene;
mixer = new THREE.AnimationMixer( object );

const material = object.children[ 0 ].children[ 0 ].material;

// add glow effect
material.mrtNode = mrt( { mask: output.add( 1 ) } );

const action = mixer.clipAction( gltf.animations[ 0 ] );
action.play();

scene.add( object );

} );

// spheres

const geometry = new THREE.SphereGeometry( .3, 32, 16 );

spheres = new THREE.Group();
scene.add( spheres );

function addSphere( color, mrtNode = null ) {

const distance = 1;
const id = spheres.children.length;
const rotation = THREE.MathUtils.degToRad( id * 90 );

const material = new THREE.MeshStandardNodeMaterial( { color } );
material.mrtNode = mrtNode;

const mesh = new THREE.Mesh( geometry, material );
mesh.position.set(
Math.cos( rotation ) * distance,
1,
Math.sin( rotation ) * distance
);

spheres.add( mesh );

}

addSphere( 0x0000ff, mrt( { mask: output } ) );
addSphere( 0x00ff00 );
addSphere( 0xff0000 );
addSphere( 0x00ffff );

// renderer

renderer = new THREE.WebGPURenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.toneMapping = THREE.NeutralToneMapping;
renderer.toneMappingExposure = 0.4;
document.body.appendChild( renderer.domElement );

// post processing

const scenePass = pass( scene, camera );
scenePass.setMRT( mrt( {
output: output.renderOutput(),
mask: vec4( 0 ) // empty as default, custom materials can set this
} ) );

const colorPass = scenePass.getTextureNode();
const maskPass = scenePass.getTextureNode( 'mask' );

postProcessing = new THREE.PostProcessing( renderer );
postProcessing.outputColorTransform = false;
postProcessing.outputNode = colorPass.add( maskPass.gaussianBlur( 1, 10 ).mul( .3 ) ).renderOutput();

// controls

const controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 1, 0 );
controls.addEventListener( 'start', () => rotate = false );
controls.addEventListener( 'end', () => rotate = true );
controls.update();

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

function animate() {

const delta = clock.getDelta();

if ( mixer ) mixer.update( delta );

if ( rotate ) spheres.rotation.y += delta * 0.5;

postProcessing.render();

}

</script>
</body>
</html>
Loading