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: Backdrop Water Example #27397

Merged
merged 14 commits into from
Dec 19, 2023
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@
"webgpu (wip)": [
"webgpu_backdrop",
"webgpu_backdrop_area",
"webgpu_backdrop_water",
"webgpu_camera_logarithmicdepthbuffer",
"webgpu_clearcoat",
"webgpu_compute_audio",
Expand Down
6 changes: 3 additions & 3 deletions examples/jsm/nodes/utils/TriplanarTexturesNode.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import Node, { addNodeClass } from '../core/Node.js';
import { add } from '../math/OperatorNode.js';
import { normalWorld } from '../accessors/NormalNode.js';
import { positionWorld } from '../accessors/PositionNode.js';
import { normalLocal } from '../accessors/NormalNode.js';
import { positionLocal } from '../accessors/PositionNode.js';
import { texture } from '../accessors/TextureNode.js';
import { addNodeElement, nodeProxy, float, vec3 } from '../shadernode/ShaderNode.js';

class TriplanarTexturesNode extends Node {

constructor( textureXNode, textureYNode = null, textureZNode = null, scaleNode = float( 1 ), positionNode = positionWorld, normalNode = normalWorld ) {
constructor( textureXNode, textureYNode = null, textureZNode = null, scaleNode = float( 1 ), positionNode = positionLocal, normalNode = normalLocal ) {

super( 'vec4' );

Expand Down
Binary file added examples/screenshots/webgpu_backdrop_water.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_morphtargets_face.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
251 changes: 251 additions & 0 deletions examples/webgpu_backdrop_water.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js - WebGPU - Backdrop Water</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 - Backdrop Water
</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 { color, depth, depthTexture, normalWorld, triplanarTexture, texture, viewportSharedTexture, mx_worley_noise_float, positionWorld, timerLocal, MeshStandardNodeMaterial, MeshBasicNodeMaterial } from 'three/nodes';

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

import WebGPU from 'three/addons/capabilities/WebGPU.js';
import WebGL from 'three/addons/capabilities/WebGL.js';

import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';

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

import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

let camera, scene, renderer;
let mixer, objects, clock;
let model, floor, floorPosition;

init();

function init() {

if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {

document.body.appendChild( WebGPU.getErrorMessage() );

throw new Error( 'No WebGPU or WebGL2 support' );

}

camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.25, 30 );
camera.position.set( 3, 3, 4 );

scene = new THREE.Scene();
scene.fog = new THREE.Fog( 0x74ccf4, 7, 25 );
scene.backgroundNode = normalWorld.y.mix( color( 0x74ccf4 ), color( 0x0066ff ) );
camera.lookAt( 0, 1, 0 );

const sunLight = new THREE.DirectionalLight( 0xFFE499, 5 );
sunLight.castShadow = true;
sunLight.shadow.camera.near = .1;
sunLight.shadow.camera.far = 3;
sunLight.shadow.camera.right = 2;
sunLight.shadow.camera.left = - 2;
sunLight.shadow.camera.top = 2;
sunLight.shadow.camera.bottom = - 2;
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
sunLight.shadow.bias = - 0.001;
sunLight.position.set( 1, 3, 1 );

const waterAmbientLight = new THREE.HemisphereLight( 0x333366, 0x74ccf4, 5 );
const skyAmbientLight = new THREE.HemisphereLight( 0x74ccf4, 0, 1 );

scene.add( sunLight );
scene.add( skyAmbientLight );
scene.add( waterAmbientLight );

clock = new THREE.Clock();

// animated model

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

model = gltf.scene;
model.children[ 0 ].children[ 0 ].castShadow = true;

mixer = new THREE.AnimationMixer( model );

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

scene.add( model );

} );

// objects

const textureLoader = new THREE.TextureLoader();
const brick_diffuse = textureLoader.load( './textures/brick_diffuse.jpg' );
brick_diffuse.wrapS = THREE.RepeatWrapping;
brick_diffuse.wrapT = THREE.RepeatWrapping;
brick_diffuse.colorSpace = THREE.NoColorSpace;

const brick = triplanarTexture( texture( brick_diffuse ) );

const geometry = new THREE.TorusKnotGeometry( .6, 0.3, 128, 64 );
const material = new MeshStandardNodeMaterial();
material.colorNode = brick;

const count = 100;
const scale = 3.5;
const column = 10;

objects = new THREE.Group();

for ( let i = 0; i < count; i ++ ) {

const x = i % column;
const y = i / column;

const mesh = new THREE.Mesh( geometry, material );
mesh.position.set( x * scale, 0, y * scale );
mesh.rotation.set( Math.random(), Math.random(), Math.random() );
objects.add( mesh );

}

objects.position.set(
( ( column - 1 ) * scale ) * - .5,
- .3,
( ( count / column ) * scale ) * - .5
);

scene.add( objects );

// water

const depthEffect = depthTexture().distance( depth ).remapClamp( 0, .05 );

const timer = timerLocal( .8 );
const floorUV = positionWorld.xzy;

const waterLayer0 = mx_worley_noise_float( floorUV.mul( 4 ).add( timer ) );
const waterLayer1 = mx_worley_noise_float( floorUV.mul( 2 ).add( timer ) );
const waterLayer2 = mx_worley_noise_float( floorUV.mul( 3 ).add( timer ) );

const waterIntensity = waterLayer0.mul( waterLayer1 ).mul( waterLayer2 ).mul( 5 );
const waterColor = waterIntensity.mix( color( 0x0f5e9c ), color( 0x74ccf4 ) );
const viewportTexture = viewportSharedTexture();

const waterMaterial = new MeshBasicNodeMaterial();
waterMaterial.colorNode = waterColor;
waterMaterial.backdropNode = depthEffect.mul( 3 ).min( 1.4 ).mix( viewportTexture, viewportTexture.mul( color( 0x74ccf4 ) ) );
waterMaterial.backdropAlphaNode = depthEffect.oneMinus();
waterMaterial.transparent = true;

const water = new THREE.Mesh( new THREE.BoxGeometry( 100, .001, 100 ), waterMaterial );
water.position.set( 0, .8, 0 );
scene.add( water );

// floor

floor = new THREE.Mesh( new THREE.BoxGeometry( 1.7, 10, 1.7 ), new MeshStandardNodeMaterial( { colorNode: brick } ) );
floor.position.set( 0, - 5, 0 );
scene.add( floor );

// caustics

const waterPosY = positionWorld.y.sub( water.position.y );

let transition = waterPosY.add( .1 ).saturate().oneMinus();
transition = waterPosY.lessThan( 0 ).cond( transition, normalWorld.y.mix( transition, 0 ) ).toVar();

material.colorNode = transition.mix( material.colorNode, material.colorNode.add( waterLayer0 ) );
floor.material.colorNode = material.colorNode;

// renderer

renderer = new WebGPURenderer( /*{ antialias: true }*/ );
renderer.stencil = false;
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
document.body.appendChild( renderer.domElement );

const controls = new OrbitControls( camera, renderer.domElement );
controls.minDistance = 1;
controls.maxDistance = 10;
controls.maxPolarAngle = Math.PI * 0.9;
controls.target.set( 0, 1, 0 );
controls.update();

// gui

const gui = new GUI();

floorPosition = new THREE.Vector3( 0, 1, 0 );

gui.add( floorPosition, 'y', 0, 2, .001 ).name( 'position' );

//

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

floor.position.y = floorPosition.y - 5;

if ( model ) {

mixer.update( delta );

model.position.y = floorPosition.y;

}

for ( const object of objects.children ) {

object.position.y = Math.sin( clock.elapsedTime + object.id ) * .3;
object.rotation.y += delta * .3;

}

renderer.render( scene, camera );

}

</script>
</body>
</html>
1 change: 1 addition & 0 deletions test/e2e/puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const exceptionList = [
'webgpu_loader_materialx',
'webgpu_materials_video',
'webgpu_materialx_noise',
'webgpu_morphtargets_face',
'webgpu_occlusion',
'webgpu_particles',
'webgpu_shadertoy',
Expand Down