Procházet zdrojové kódy

Examples > Add VFX flames (#28969)

Bruno Simon před 1 rokem
rodič
revize
30a028895b

+ 1 - 0
examples/files.json

@@ -403,6 +403,7 @@
 		"webgpu_tsl_editor",
 		"webgpu_tsl_editor",
 		"webgpu_tsl_galaxy",
 		"webgpu_tsl_galaxy",
 		"webgpu_tsl_transpiler",
 		"webgpu_tsl_transpiler",
+		"webgpu_tsl_vfx_flames",
 		"webgpu_video_panorama",
 		"webgpu_video_panorama",
 		"webgpu_volume_cloud",
 		"webgpu_volume_cloud",
 		"webgpu_volume_perlin"
 		"webgpu_volume_perlin"

binární
examples/screenshots/webgpu_tsl_vfx_flames.jpg


binární
examples/textures/noises/perlin/rgb-256x256.png


binární
examples/textures/noises/voronoi/grayscale-256x256.png


+ 240 - 0
examples/webgpu_tsl_vfx_flames.html

@@ -0,0 +1,240 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - vfx flames</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 webgpu</a> - vfx flames
+			<br>
+			Inspired by <a href="https://x.com/cmzw_/status/1799648702338158747" target="_blank" rel="noopener">@cmzw_</a>
+		</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 { PI2, dot, sin, step, texture, timerLocal, tslFn, uv, vec2, vec3, vec4, mix } from 'three/tsl';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			let camera, scene, renderer, controls;
+
+			init();
+
+			function init() {
+
+				camera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( 1, 1, 3 );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x201919 );
+
+				// textures
+
+				const textureLoader = new THREE.TextureLoader();
+
+				const cellularTexture = textureLoader.load( './textures/noises/voronoi/grayscale-256x256.png' );
+				const perlinTexture = textureLoader.load( './textures/noises/perlin/rgb-256x256.png' );
+
+				// TSL functions
+
+				const spherizeUv = tslFn( ( [ input, center, strength, offset ] ) => {
+
+					const delta = input.sub( center );
+					const delta2 = dot( delta, delta );
+					const delta4 = delta2.mul( delta2 );
+					const deltaOffset = delta4.mul( strength );
+					return input.add( delta.mul( deltaOffset ) ).add( offset );
+
+				} );
+
+				// gradient canvas
+
+				const gradient = {};
+				gradient.element = document.createElement( 'canvas' );
+				gradient.element.width = 128;
+				gradient.element.height = 1;
+				gradient.context = gradient.element.getContext( '2d' );
+
+				gradient.colors = [
+					'#090033',
+					'#5f1f93',
+					'#e02e96',
+					'#ffbd80',
+					'#fff0db',
+				];
+
+				gradient.texture = new THREE.CanvasTexture( gradient.element );
+				gradient.texture.colorSpace = THREE.SRGBColorSpace;
+
+				gradient.update = () => {
+
+					const fillGradient = gradient.context.createLinearGradient( 0, 0, gradient.element.width, 0 );
+
+					for ( let i = 0; i < gradient.colors.length; i ++ ) {
+
+						const progress = i / ( gradient.colors.length - 1 );
+						const color = gradient.colors[ i ];
+						fillGradient.addColorStop( progress, color );
+			
+					}
+
+					gradient.context.fillStyle = fillGradient;
+					gradient.context.fillRect( 0, 0, gradient.element.width, gradient.element.height );
+			
+					gradient.texture.needsUpdate = true;
+			
+				};
+
+				gradient.update();
+
+				// flame 1 material
+
+				const flame1Material = new THREE.MeshBasicNodeMaterial( { transparent: true, side: THREE.DoubleSide } );
+
+				flame1Material.colorNode = tslFn( () => {
+
+					const time = timerLocal();
+
+					// main UV
+					const mainUv = uv().toVar();
+					mainUv.assign( spherizeUv( mainUv, vec2( 0.5 ), 10, vec2( 0 ) ).mul( 0.6 ).add( 0.2 ) ); // spherize
+					mainUv.assign( mainUv.pow( vec2( 1, 2 ) ) ); // stretch
+					mainUv.assign( mainUv.mul( 2, 1 ).sub( vec2( 0.5, 0 ) ) ); // scale
+
+					// gradients
+					const gradient1 = sin( time.mul( 10 ).sub( mainUv.y.mul( PI2 ).mul( 2 ) ) ).toVar();
+					const gradient2 = mainUv.y.smoothstep( 0, 1 ).toVar();
+					mainUv.x.addAssign( gradient1.mul( gradient2 ).mul( 0.2 ) );
+
+					// cellular noise
+					const cellularUv = mainUv.mul( 0.5 ).add( vec2( 0, time.negate().mul( 0.5 ) ) ).mod( 1 );
+					const cellularNoise = texture( cellularTexture, cellularUv, 0 ).r.oneMinus().smoothstep( 0, 0.5 ).oneMinus();
+					cellularNoise.mulAssign( gradient2 );
+
+					// shape
+					const shape = mainUv.sub( 0.5 ).mul( vec2( 3, 2 ) ).length().oneMinus().toVar();
+					shape.assign( shape.sub( cellularNoise ) );
+
+					// gradient color
+					const gradientColor = texture( gradient.texture, vec2( shape.remap( 0, 1, 0, 1 ), 0 ) );
+
+					// output
+					const color = mix( gradientColor, vec3( 1 ), shape.step( 0.8 ).oneMinus() );
+					const alpha = shape.smoothstep( 0, 0.3 );
+					return vec4( color.rgb, alpha );
+			
+				} )();
+
+				// flame 2 material
+
+				const flame2Material = new THREE.MeshBasicNodeMaterial( { transparent: true, side: THREE.DoubleSide } );
+
+				flame2Material.colorNode = tslFn( () => {
+
+					const time = timerLocal();
+
+					// main UV
+					const mainUv = uv().toVar();
+					mainUv.assign( spherizeUv( mainUv, vec2( 0.5 ), 10, vec2( 0 ) ).mul( 0.6 ).add( 0.2 ) ); // spherize
+					mainUv.assign( mainUv.pow( vec2( 1, 3 ) ) ); // stretch
+					mainUv.assign( mainUv.mul( 2, 1 ).sub( vec2( 0.5, 0 ) ) ); // scale
+
+					// perlin noise
+					const perlinUv = mainUv.add( vec2( 0, time.negate().mul( 1 ) ) ).mod( 1 );
+					const perlinNoise = texture( perlinTexture, perlinUv, 0 ).sub( 0.5 ).mul( 1 );
+					mainUv.x.addAssign( perlinNoise.x.mul( 0.5 ) );
+
+					// gradients
+					const gradient1 = sin( time.mul( 10 ).sub( mainUv.y.mul( PI2 ).mul( 2 ) ) );
+					const gradient2 = mainUv.y.smoothstep( 0, 1 );
+					const gradient3 = mainUv.y.smoothstep( 1, 0.7 );
+					mainUv.x.addAssign( gradient1.mul( gradient2 ).mul( 0.2 ) );
+
+					// displaced perlin noise
+					const displacementPerlinUv = mainUv.mul( 0.5 ).add( vec2( 0, time.negate().mul( 0.25 ) ) ).mod( 1 );
+					const displacementPerlinNoise = texture( perlinTexture, displacementPerlinUv, 0 ).sub( 0.5 ).mul( 1 );
+					const displacedPerlinUv = mainUv.add( vec2( 0, time.negate().mul( 0.5 ) ) ).add( displacementPerlinNoise ).mod( 1 );
+					const displacedPerlinNoise = texture( perlinTexture, displacedPerlinUv, 0 ).sub( 0.5 ).mul( 1 );
+					mainUv.x.addAssign( displacedPerlinNoise.mul( 0.5 ) );
+
+					// cellular noise
+					const cellularUv = mainUv.add( vec2( 0, time.negate().mul( 1.5 ) ) ).mod( 1 );
+					const cellularNoise = texture( cellularTexture, cellularUv, 0 ).r.oneMinus().smoothstep( 0.25, 1 );
+
+					// shape
+					const shape = mainUv.sub( 0.5 ).mul( vec2( 6, 1 ) ).length().step( 0.5 );
+					shape.assign( shape.mul( cellularNoise ) );
+					shape.mulAssign( gradient3 );
+					shape.assign( step( 0.01, shape ) );
+
+					// output
+					return vec4( vec3( 1 ), shape );
+			
+				} )();
+
+				// geometry
+
+				const geometry = new THREE.PlaneGeometry( 1, 1, 64, 64 );
+
+				// meshes
+
+				const flame1 = new THREE.Mesh( geometry, flame1Material );
+				flame1.position.x = - 0.5;
+				scene.add( flame1 );
+
+				const flame2 = new THREE.Mesh( geometry, flame2Material );
+				flame2.position.x = 0.5;
+				scene.add( flame2 );
+
+				// renderer
+
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+				controls.minDistance = 0.1;
+				controls.maxDistance = 50;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			async function animate() {
+
+				controls.update();
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 0
test/e2e/puppeteer.js

@@ -149,6 +149,7 @@ const exceptionList = [
 	'webgpu_texturegrad',
 	'webgpu_texturegrad',
 	'webgpu_performance_renderbundle',
 	'webgpu_performance_renderbundle',
 	'webgpu_lights_rectarealight',
 	'webgpu_lights_rectarealight',
+	'webgpu_tsl_vfx_flames',
 
 
 	// WebGPU idleTime and parseTime too low
 	// WebGPU idleTime and parseTime too low
 	'webgpu_compute_particles',
 	'webgpu_compute_particles',

粤ICP备19079148号