Kaynağa Gözat

Examples: Add recursive tree to WebGPU reflection demo. (#31372)

Michael Herzog 6 ay önce
ebeveyn
işleme
a1f199efd8

BIN
examples/screenshots/webgpu_reflection.jpg


+ 228 - 48
examples/webgpu_reflection.html

@@ -9,7 +9,9 @@
 	<body>
 
 		<div id="info">
-			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu - reflection
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu - reflection<br/>
+			Based on <a href="https://web.archive.org/web/20210101053442/http://oos.moxiecode.com/js_webgl/recursive_tree_cubes/" target="_blank" rel="noopener">Recursive Tree Cubes</a>
+			by <a href="https://github.com/oosmoxiecode" target="_blank" rel="noopener">oosmoxiecode</a>
 		</div>
 
 		<script type="importmap">
@@ -26,72 +28,48 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { color, pass, reflector, normalWorldGeometry, texture, uv, screenUV } from 'three/tsl';
+			
+			import { abs, color, div, float, Fn, instancedBufferAttribute, materialColor, min, normalWorldGeometry, pass, positionGeometry, positionLocal, reflector, screenUV, sin, sub, texture, time, uniform, uv, varyingProperty } from 'three/tsl';
 			import { gaussianBlur } from 'three/addons/tsl/display/GaussianBlurNode.js';
 
-			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
 			import Stats from 'three/addons/libs/stats.module.js';
+			import TWEEN from 'three/addons/libs/tween.module.js';
 
 			let camera, scene, renderer;
-			let model, mixer, clock;
 			let postProcessing;
 			let controls;
 			let stats;
 
+			// below uniforms will be animated via TWEEN.js
+
+			const uniformLife = uniform( 0 );
+			const uniformEffector1 = uniform( - 0.2 );
+			const uniformEffector2 = uniform( - 0.2 );
+
 			init();
 
 			function init() {
 
 				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.25, 30 );
-				camera.position.set( 2, 2.5, 3 );
+				camera.position.set( 4, 2, 4 );
 
 				scene = new THREE.Scene();
 				scene.fog = new THREE.Fog( 0x0487e2, 7, 25 );
 				scene.backgroundNode = normalWorldGeometry.y.mix( color( 0x0487e2 ), 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 = 5;
-				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( .5, 3, .5 );
-
-				const waterAmbientLight = new THREE.HemisphereLight( 0x333366, 0x74ccf4, 5 );
+				const sunLight = new THREE.DirectionalLight( 0xFFE499, 3 );
+				sunLight.position.set( 7, 3, 7 );
+
+				const waterAmbientLight = new THREE.HemisphereLight( 0x333366, 0x74ccf4, 3 );
 				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 );
-
-				} );
-
 				// textures
 
 				const textureLoader = new THREE.TextureLoader();
@@ -105,6 +83,11 @@
 				floorNormal.wrapS = THREE.RepeatWrapping;
 				floorNormal.wrapT = THREE.RepeatWrapping;
 
+				// tree
+
+				const treeMesh = createTreeMesh();
+				scene.add( treeMesh );
+
 				// floor
 
 				const floorUV = uv().mul( 15 );
@@ -119,8 +102,6 @@
 				floorMaterial.colorNode = texture( floorColor, floorUV ).add( reflection );
 
 				const floor = new THREE.Mesh( new THREE.BoxGeometry( 50, .001, 50 ), floorMaterial );
-				floor.receiveShadow = true;
-
 				floor.position.set( 0, 0, 0 );
 				scene.add( floor );
 
@@ -130,26 +111,27 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.shadowMap.enabled = true;
 				document.body.appendChild( renderer.domElement );
 
 				stats = new Stats();
 				document.body.appendChild( stats.dom );
 
+				// controls
+
 				controls = new OrbitControls( camera, renderer.domElement );
 				controls.minDistance = 1;
 				controls.maxDistance = 10;
 				controls.maxPolarAngle = Math.PI / 2;
 				controls.autoRotate = true;
 				controls.autoRotateSpeed = 1;
-				controls.target.set( 0, .5, 0 );
+				controls.target.set( 0, 1, 0 );
 				controls.update();
 
 				// post-processing
 
 				const scenePass = pass( scene, camera );
 				const scenePassColor = scenePass.getTextureNode();
-				const scenePassDepth = scenePass.getLinearDepthNode().remapClamp( .3, .5 );
+				const scenePassDepth = scenePass.getLinearDepthNode().remapClamp( .3, .7 );
 
 				const scenePassColorBlurred = gaussianBlur( scenePassColor );
 				scenePassColorBlurred.directionNode = scenePassDepth;
@@ -163,6 +145,10 @@
 
 				window.addEventListener( 'resize', onWindowResize );
 
+				//
+
+				startTweens();
+
 			}
 
 			function onWindowResize() {
@@ -180,15 +166,209 @@
 
 				controls.update();
 
-				const delta = clock.getDelta();
+				TWEEN.update();
+
+				postProcessing.render();
+
+			}
+
+			function startTweens() {
+			
+				const lifeTween = new TWEEN.Tween( uniformLife )
+					.to( { value: 1 }, 5000 )
+					.easing( TWEEN.Easing.Bounce.Out );
+				lifeTween.start();
+
+				const effectTween = new TWEEN.Tween( uniformEffector1 )
+					.to( { value: 1.2 }, 2500 )
+					.delay( 3000 )
+					.easing( TWEEN.Easing.Sinusoidal.InOut )
+					.onComplete( function () {
+
+						secondaryTweens();
+			
+					} );
+				effectTween.start();
+
+			}
+
+			function secondaryTweens() {
+
+				uniformEffector1.value = - 0.2;
+				uniformEffector2.value = - 0.2;
+
+				const effect2Tween = new TWEEN.Tween( uniformEffector2 )
+					.to( { value: 1.2 }, 3000 )
+					.repeat( Infinity )
+					.easing( TWEEN.Easing.Sinusoidal.InOut );
+				effect2Tween.start();
+
+				const effectTween = new TWEEN.Tween( uniformEffector1 )
+					.to( { value: 1.2 }, 3000 )
+					.delay( 800 )
+					.repeat( Infinity )
+					.easing( TWEEN.Easing.Sinusoidal.InOut );
+				effectTween.start();
+
+			}
+
+			function createTreeMesh() {
+
+				const maxSteps = 6;
+				const angleLeft = Math.PI / 180 * 30;
+				const angleRight = Math.PI / 180 * 40;
+				const lengthMult = 0.88;
+
+				const positions = [];
+				const normals = [];
+				const colors = [];
+				const data = []; // will save seed, size and time
+
+				let instanceCount = 0;
+
+				const newPosition = new THREE.Vector3();
+				const position = new THREE.Vector3();
+				const normal = new THREE.Vector3();
+				const color = new THREE.Color();
+
+				function createTreePart( angle, x, y, z, length, count ) {
+
+					if ( count < maxSteps ) {
+
+						const newLength = length * lengthMult;
+						const newX = x + Math.cos( angle ) * length;
+						const newY = y + Math.sin( angle ) * length;
+						const countSq = Math.min( 3.2, count * count );
+						const newZ = z + ( Math.random() * countSq - countSq / 2 ) * length;
+
+						let size = 30 - ( count * 8 );
+						if ( size > 25 ) size = 25;
+						if ( size < 10 ) size = 10;
+
+						size = size / 10;
 
-				if ( model ) {
+						const subSteps = 60;
 
-					mixer.update( delta );
+						// below loop generates the instanced data for a tree part
+
+						for ( let i = 0; i < subSteps; i ++ ) {
+
+							instanceCount ++;
+
+							const percent = i / subSteps;
+							const extra = 1 / maxSteps;
+
+							// position
+
+							newPosition.set( x, y, z ).lerp( new THREE.Vector3( newX, newY, newZ ), percent );
+							position.copy( newPosition );
+
+							position.x += Math.random() * ( size * 3 ) - ( size * 1.5 );
+							position.y += Math.random() * ( size * 3 ) - ( size * 1.5 );
+							position.z += Math.random() * ( size * 3 ) - ( size * 1.5 );
+
+							positions.push( position.x, position.y, position.z );
+
+							const scale = 0.25 * Math.random();
+
+							// normal
+
+							normal.copy( position ).sub( newPosition ).normalize();
+							normals.push( normal.x, normal.y, normal.z );
+
+							// color
+
+							color.setHSL( 0.55 + Math.random() * 0.05, 1.0, 0.7 + Math.random() * 0.3 );
+							colors.push( color.r, color.g, color.b );
+
+							// to save vertex buffers, we store the size, time and seed in a single attribute
+
+							const instanceSize = size * scale;
+							const instanceTime = ( count / maxSteps ) + percent * extra;
+							const instanceSeed = Math.random();
+
+							data.push( instanceSize, instanceTime, instanceSeed );
+
+						}
+
+						createTreePart( angle - angleRight, newX, newY, newZ, newLength, count + 1 );
+						createTreePart( angle + angleLeft, newX, newY, newZ, newLength, count + 1 );
+
+					}
 
 				}
 
-				postProcessing.render();
+				const angle = Math.PI * 0.5;
+
+				// the tree is represented as a collection of instances boxes generated with below recursive function
+
+				createTreePart( angle, 0, 0, 0, 16, 0 );
+
+				const geometry = new THREE.BoxGeometry();
+				const material = new THREE.MeshStandardNodeMaterial();
+				const mesh = new THREE.Mesh( geometry, material );
+				mesh.scale.setScalar( 0.05 );
+				mesh.count = instanceCount;
+				mesh.frustumCulled = false;
+
+				// instanced data
+
+				const attributePosition = new THREE.InstancedBufferAttribute( new Float32Array( positions ), 3 );
+				const attributeNormal = new THREE.InstancedBufferAttribute( new Float32Array( normals ), 3 );
+				const attributeColor = new THREE.InstancedBufferAttribute( new Float32Array( colors ), 3 );
+				const attributeData = new THREE.InstancedBufferAttribute( new Float32Array( data ), 3 );
+
+				// TSL
+
+				const vVisbility = varyingProperty( 'float' );
+
+				const instancePosition = instancedBufferAttribute( attributePosition );
+				const instanceNormal = instancedBufferAttribute( attributeNormal );
+				const instanceColor = instancedBufferAttribute( attributeColor );
+				const instanceData = instancedBufferAttribute( attributeData );
+			
+				material.positionNode = Fn( () => {
+
+					const instanceSize = instanceData.x;
+					const instanceTime = instanceData.y;
+					const instanceSeed = instanceData.z;
+
+					// effectors (these are responsible for the blob-like scale effects)
+
+					const dif1 = abs( instanceTime.sub( uniformEffector1 ) ).toConst();
+					let effect = dif1.lessThanEqual( 0.15 ).select( sub( 0.15, dif1 ).mul( sub( 1.7, instanceTime ).mul( 10 ) ), float( 0 ) );
+
+					const dif2 = abs( instanceTime.sub( uniformEffector2 ) ).toConst();
+					effect = dif2.lessThanEqual( 0.15 ).select( sub( 0.15, dif2 ).mul( sub( 1.7, instanceTime ).mul( 10 ) ), effect );
+
+					// life (controls the visibility and initial scale of the cubes)
+
+					const scale = uniformLife.greaterThan( instanceTime ).select( min( 1, div( uniformLife.sub( instanceTime ), 0 ) ) ).oneMinus();
+					vVisbility.assign( uniformLife.greaterThan( instanceTime ).select( 1, 0 ) );
+
+					// accumulate different vertex animations
+
+					let animated = positionLocal.add( instancePosition ).toVar();
+					const direction = positionGeometry.normalize().toConst();
+
+					animated = animated.add( direction.mul( effect.add( instanceSize ) ) );
+					animated = animated.sub( direction.mul( scale ) );
+					animated = animated.add( instanceNormal.mul( effect.mul( 1 ) ) );
+					animated = animated.add( instanceNormal.mul( abs( sin( time.add( instanceSeed.mul( 2 ) ) ).mul( 1.5 ) ) ) );
+
+					return animated;
+
+				} )();
+
+				material.colorNode = Fn( () => {
+
+					vVisbility.equal( 0 ).discard();
+
+					return materialColor.mul( instanceColor );
+
+				} )();
+
+				return mesh;
 
 			}
 

粤ICP备19079148号