Просмотр исходного кода

Water: Add `WebGPURenderer` version. (#29019)

* Water: Add `WebGPURenderer` version.

* E2E: Update screenshot.

* Clean up.

* More clean up.
Michael Herzog 1 год назад
Родитель
Сommit
97d2d20dd6

+ 1 - 0
examples/files.json

@@ -363,6 +363,7 @@
 		"webgpu_multiple_rendertargets_readback",
 		"webgpu_multisampled_renderbuffers",
 		"webgpu_occlusion",
+		"webgpu_ocean",
 		"webgpu_parallax_uv",
 		"webgpu_particles",
 		"webgpu_performance_renderbundle",

+ 103 - 0
examples/jsm/objects/WaterGPU.js

@@ -0,0 +1,103 @@
+import {
+	Color,
+	Mesh,
+	NodeMaterial,
+	Vector3
+} from 'three';
+import { add, cameraPosition, div, normalize, positionWorld, sub, timerLocal, tslFn, texture, vec2, vec3, vec4, max, dot, reflect, pow, length, float, uniform, reflector, mul, mix } from 'three/tsl';
+
+/**
+ * Work based on :
+ * https://github.com/Slayvin: Flat mirror for three.js
+ * https://home.adelphi.edu/~stemkoski/ : An implementation of water shader based on the flat mirror
+ * http://29a.ch/ && http://29a.ch/slides/2012/webglwater/ : Water shader explanations in WebGL
+ */
+
+class Water extends Mesh {
+
+	constructor( geometry, options ) {
+
+		const material = new NodeMaterial();
+
+		super( geometry, material );
+
+		this.isWater = true;
+
+		this.resolution = options.resolution !== undefined ? options.resolution : 0.5;
+
+		// uniforms
+
+		this.waterNormals = texture( options.waterNormals );
+		this.alpha = uniform( options.alpha !== undefined ? options.alpha : 1.0 );
+		this.size = uniform( options.size !== undefined ? options.size : 1.0 );
+		this.sunColor = uniform( new Color( options.sunColor !== undefined ? options.sunColor : 0xffffff ) );
+		this.sunDirection = uniform( options.sunDirection !== undefined ? options.sunDirection : new Vector3( 0.70707, 0.70707, 0.0 ) );
+		this.waterColor = uniform( new Color( options.waterColor !== undefined ? options.waterColor : 0x7f7f7f ) );
+		this.distortionScale = uniform( options.distortionScale !== undefined ? options.distortionScale : 20.0 );
+
+		// TSL
+
+		const timeNode = timerLocal();
+
+		const getNoise = tslFn( ( [ uv ] ) => {
+
+			const uv0 = add( div( uv, 103 ), vec2( div( timeNode, 17 ), div( timeNode, 29 ) ) ).toVar();
+			const uv1 = div( uv, 107 ).sub( vec2( div( timeNode, - 19 ), div( timeNode, 31 ) ) ).toVar();
+			const uv2 = add( div( uv, vec2( 8907.0, 9803.0 ) ), vec2( div( timeNode, 101 ), div( timeNode, 97 ) ) ).toVar();
+			const uv3 = sub( div( uv, vec2( 1091.0, 1027.0 ) ), vec2( div( timeNode, 109 ), div( timeNode, - 113 ) ) ).toVar();
+
+			const sample0 = this.waterNormals.uv( uv0 );
+			const sample1 = this.waterNormals.uv( uv1 );
+			const sample2 = this.waterNormals.uv( uv2 );
+			const sample3 = this.waterNormals.uv( uv3 );
+
+			const noise = sample0.add( sample1 ).add( sample2 ).add( sample3 );
+
+			return noise.mul( 0.5 ).sub( 1 );
+
+		} );
+
+		const fragmentNode = tslFn( () => {
+
+			const noise = getNoise( positionWorld.xz.mul( this.size ) );
+			const surfaceNormal = normalize( noise.xzy.mul( 1.5, 1.0, 1.5 ) );
+
+			const diffuseLight = vec3( 0 ).toVar();
+			const specularLight = vec3( 0 ).toVar();
+
+			const worldToEye = cameraPosition.sub( positionWorld );
+			const eyeDirection = normalize( worldToEye );
+
+			const reflection = normalize( reflect( this.sunDirection.negate(), surfaceNormal ) );
+			const direction = max( 0.0, dot( eyeDirection, reflection ) );
+			specularLight.addAssign( pow( direction, 100 ).mul( this.sunColor ).mul( 2.0 ) );
+			diffuseLight.addAssign( max( dot( this.sunDirection, surfaceNormal ), 0.0 ).mul( this.sunColor ).mul( 0.5 ) );
+
+			const distance = length( worldToEye );
+
+			const distortion = surfaceNormal.xy.mul( float( 0.001 ).add( float( 1.0 ).div( distance ) ) ).mul( this.distortionScale );
+
+			const mirrorSampler = reflector();
+			mirrorSampler.uvNode = mirrorSampler.uvNode.add( distortion );
+			mirrorSampler.resolution = this.resolution;
+
+			this.add( mirrorSampler.target );
+
+			const theta = max( dot( eyeDirection, surfaceNormal ), 0.0 );
+			const rf0 = float( 0.3 );
+			const reflectance = mul( pow( float( 1.0 ).sub( theta ), 5.0 ), float( 1.0 ).sub( rf0 ) ).add( rf0 );
+			const scatter = max( 0.0, dot( surfaceNormal, eyeDirection ) ).mul( this.waterColor );
+			const albedo = mix( this.sunColor.mul( diffuseLight ).mul( 0.3 ).add( scatter ), mirrorSampler.rgb.mul( specularLight ).add( mirrorSampler.rgb.mul( 0.9 ) ).add( vec3( 0.1 ) ), reflectance );
+
+			return vec4( albedo, this.alpha );
+
+		} )();
+
+		material.normals = false;
+		material.fragmentNode = fragmentNode;
+
+	}
+
+}
+
+export { Water };

BIN
examples/screenshots/webgpu_ocean.jpg


+ 206 - 0
examples/webgpu_ocean.html

@@ -0,0 +1,206 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - ocean</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="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - webgpu ocean
+		</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 Stats from 'three/addons/libs/stats.module.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { Water } from 'three/addons/objects/WaterGPU.js';
+			import { Sky } from 'three/addons/objects/SkyGPU.js';
+
+			let container, stats;
+			let camera, scene, renderer;
+			let controls, water, sun, mesh;
+
+			init();
+
+			function init() {
+
+				container = document.getElementById( 'container' );
+
+				//
+
+				renderer = new THREE.WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 0.5;
+				container.appendChild( renderer.domElement );
+
+				//
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 1, 20000 );
+				camera.position.set( 30, 30, 100 );
+
+				//
+
+				sun = new THREE.Vector3();
+
+				// Water
+
+				const waterGeometry = new THREE.PlaneGeometry( 10000, 10000 );
+				const loader = new THREE.TextureLoader();
+				const waterNormals = loader.load( 'textures/waternormals.jpg' );
+				waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
+
+				water = new Water(
+					waterGeometry,
+					{
+						waterNormals: waterNormals,
+						sunDirection: new THREE.Vector3(),
+						sunColor: 0xffffff,
+						waterColor: 0x001e0f,
+						distortionScale: 3.7
+					}
+				);
+
+				water.rotation.x = - Math.PI / 2;
+
+				scene.add( water );
+
+				// Skybox
+
+				const sky = new Sky();
+				sky.scale.setScalar( 10000 );
+				scene.add( sky );
+
+				sky.turbidity.value = 10;
+				sky.rayleigh.value = 2;
+				sky.mieCoefficient.value = 0.005;
+				sky.mieDirectionalG.value = 0.8;
+
+				const parameters = {
+					elevation: 2,
+					azimuth: 180
+				};
+
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+				const sceneEnv = new THREE.Scene();
+
+				let renderTarget;
+
+				function updateSun() {
+
+					const phi = THREE.MathUtils.degToRad( 90 - parameters.elevation );
+					const theta = THREE.MathUtils.degToRad( parameters.azimuth );
+
+					sun.setFromSphericalCoords( 1, phi, theta );
+
+					sky.sunPosition.value.copy( sun );
+					water.sunDirection.value.copy( sun ).normalize();
+
+					if ( renderTarget !== undefined ) renderTarget.dispose();
+
+					sceneEnv.add( sky );
+					renderTarget = pmremGenerator.fromScene( sceneEnv );
+					scene.add( sky );
+
+					scene.environment = renderTarget.texture;
+
+				}
+
+				renderer.init().then( updateSun );
+
+				//
+
+				const geometry = new THREE.BoxGeometry( 30, 30, 30 );
+				const material = new THREE.MeshStandardMaterial( { roughness: 0 } );
+
+				mesh = new THREE.Mesh( geometry, material );
+				scene.add( mesh );
+
+				//
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.maxPolarAngle = Math.PI * 0.495;
+				controls.target.set( 0, 10, 0 );
+				controls.minDistance = 40.0;
+				controls.maxDistance = 200.0;
+				controls.update();
+
+				//
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				// GUI
+
+				const gui = new GUI();
+
+				const folderSky = gui.addFolder( 'Sky' );
+				folderSky.add( parameters, 'elevation', 0, 90, 0.1 ).onChange( updateSun );
+				folderSky.add( parameters, 'azimuth', - 180, 180, 0.1 ).onChange( updateSun );
+				folderSky.open();
+
+
+				const folderWater = gui.addFolder( 'Water' );
+				folderWater.add( water.distortionScale, 'value', 0, 8, 0.1 ).name( 'distortionScale' );
+				folderWater.add( water.size, 'value', 0.1, 10, 0.1 ).name( 'size' );
+				folderWater.open();
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				const time = performance.now() * 0.001;
+
+				mesh.position.y = Math.sin( time ) * 20 + 5;
+				mesh.rotation.x = time * 0.5;
+				mesh.rotation.z = time * 0.51;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

粤ICP备19079148号