-
-
Notifications
You must be signed in to change notification settings - Fork 35.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WebGPURenderer: Backdrop Water Example (#27397)
* fix triplanar with dynamic mesh * add example: `webgpu_backdrop_water` * fix names * cleanup * cleanup * revision * Update puppeteer.js * revision * revision * caustic * revision * revision * Update webgpu_backdrop_water.html * update shadow
- Loading branch information
Showing
6 changed files
with
256 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters