Bruno Simon пре 1 година
родитељ
комит
9fd398d8e9

+ 1 - 0
examples/files.json

@@ -405,6 +405,7 @@
 		"webgpu_tsl_compute_attractors_particles",
 		"webgpu_tsl_editor",
 		"webgpu_tsl_galaxy",
+		"webgpu_tsl_halftone",
 		"webgpu_tsl_transpiler",
 		"webgpu_tsl_vfx_flames",
 		"webgpu_video_panorama",

BIN
examples/screenshots/webgpu_tsl_halftone.jpg


+ 261 - 0
examples/webgpu_tsl_halftone.html

@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - halftone</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 - Halftone
+		</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 { color, mix, normalWorld, output, tslFn, uniform, vec4, viewportCoordinate, viewportResolution } from 'three/tsl';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+			let camera, scene, renderer, controls, clock, halftoneSettings;
+
+			init();
+
+			function init() {
+
+				camera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( 6, 3, 10 );
+
+				scene = new THREE.Scene();
+
+				clock = new THREE.Clock();
+
+				const gui = new GUI();
+
+				// lights
+
+				const ambientLight = new THREE.AmbientLight( '#ffffff', 3 );
+				scene.add( ambientLight );
+
+				const directionalLight = new THREE.DirectionalLight( '#ffffff', 8 );
+				directionalLight.position.set( 4, 3, 1 );
+				scene.add( directionalLight );
+
+				const lightsFolder = gui.addFolder( '💡 lights' );
+				lightsFolder.add( ambientLight, 'intensity', 0, 10, 0.001 ).name( 'ambientIntensity' );
+				lightsFolder.add( directionalLight, 'intensity', 0, 20, 0.001 ).name( 'directionalIntensity' );
+
+				// halftone settings
+
+				halftoneSettings = [
+
+					// purple shade
+
+					{
+						count: 140,
+						color: '#fb00ff',
+						direction: new THREE.Vector3( - 0.4, - 1, 0.5 ),
+						start: 1,
+						end: 0,
+						mixLow: 0,
+						mixHigh: 0.5,
+						radius: 0.8
+					},
+
+					// cyan highlight
+
+					{
+						count: 180,
+						color: '#94ffd1',
+						direction: new THREE.Vector3( 0.5, 0.5, - 0.2 ),
+						start: 0.55,
+						end: 0.2,
+						mixLow: 0.5,
+						mixHigh: 1,
+						radius: 0.8
+					}
+				];
+
+				for ( const index in halftoneSettings ) {
+
+					const settings = halftoneSettings[ index ];
+
+					// uniforms
+
+					const uniforms = {};
+
+					uniforms.count = uniform( settings.count );
+					uniforms.color = uniform( color( settings.color ) );
+					uniforms.direction = uniform( settings.direction );
+					uniforms.start = uniform( settings.start );
+					uniforms.end = uniform( settings.end );
+					uniforms.mixLow = uniform( settings.mixLow );
+					uniforms.mixHigh = uniform( settings.mixHigh );
+					uniforms.radius = uniform( settings.radius );
+
+					settings.uniforms = uniforms;
+
+					// debug
+
+					const folder = gui.addFolder( `⚪️ halftone ${index}` );
+
+					folder.addColor( { color: uniforms.color.value.getHexString( THREE.SRGBColorSpace ) }, 'color' )
+						.onChange( value => uniforms.color.value.set( value ) );
+					folder.add( uniforms.count, 'value', 1, 200, 1 ).name( 'count' );
+					folder.add( uniforms.direction.value, 'x', - 1, 1, 0.01 ).listen();
+					folder.add( uniforms.direction.value, 'y', - 1, 1, 0.01 ).listen();
+					folder.add( uniforms.direction.value, 'z', - 1, 1, 0.01 ).listen();
+					folder.add( uniforms.start, 'value', - 1, 1, 0.01 ).name( 'start' );
+					folder.add( uniforms.end, 'value', - 1, 1, 0.01 ).name( 'end' );
+					folder.add( uniforms.mixLow, 'value', 0, 1, 0.01 ).name( 'mixLow' );
+					folder.add( uniforms.mixHigh, 'value', 0, 1, 0.01 ).name( 'mixHigh' );
+					folder.add( uniforms.radius, 'value', 0, 1, 0.01 ).name( 'radius' );
+
+				}
+
+				// halftone functions
+
+				const halftone = tslFn( ( [ count, color, direction, start, end, radius, mixLow, mixHigh ] ) => {
+
+					// grid pattern
+
+					const gridUv = viewportCoordinate.xy
+						.div( viewportResolution.yy )
+						.mul( count )
+						.rotate( Math.PI * 0.25 )
+						.mod( 1 );
+
+					// orientation strength
+
+					const orientationStrength = normalWorld
+						.dot( direction.normalize() )
+						.remapClamp( end, start, 0, 1 );
+
+					// mask
+
+					const mask = gridUv
+						.sub( 0.5 )
+						.length()
+						.step( orientationStrength.mul( radius ).mul( 0.5 ) )
+						.mul( mix( mixLow, mixHigh, orientationStrength ) );
+
+					return vec4( color, mask );
+			
+				} );
+
+				const halftones = tslFn( ( [ input ] ) => {
+
+					const halftonesOutput = input;
+
+					for ( const settings of halftoneSettings ) {
+
+						const halfToneOutput = halftone( settings.uniforms.count, settings.uniforms.color, settings.uniforms.direction, settings.uniforms.start, settings.uniforms.end, settings.uniforms.radius, settings.uniforms.mixLow, settings.uniforms.mixHigh );
+						halftonesOutput.rgb.assign( mix( halftonesOutput.rgb, halfToneOutput.rgb, halfToneOutput.a ) );
+
+					}
+
+					return halftonesOutput;
+
+				} );
+
+				// default material
+
+				const defaultMaterial = new THREE.MeshStandardNodeMaterial( { color: '#ff622e' } );
+				defaultMaterial.outputNode = halftones( output );
+
+				const folder = gui.addFolder( '🎨 default material' );
+				folder.addColor( { color: defaultMaterial.color.getHexString( THREE.SRGBColorSpace ) }, 'color' )
+					.onChange( value => defaultMaterial.color.set( value ) );
+
+				// objects
+
+				const torusKnot = new THREE.Mesh(
+					new THREE.TorusKnotGeometry( 0.6, 0.25, 128, 32 ),
+					defaultMaterial
+				);
+				torusKnot.position.x = 3;
+				scene.add( torusKnot );
+
+				const sphere = new THREE.Mesh(
+					new THREE.SphereGeometry( 1, 64, 64 ),
+					defaultMaterial
+				);
+				sphere.position.x = - 3;
+				scene.add( sphere );
+
+				const gltfLoader = new GLTFLoader();
+				gltfLoader.load(
+					'./models/gltf/Michelle.glb',
+					( gltf ) => {
+
+						const model = gltf.scene;
+						model.position.y = - 2;
+						model.scale.setScalar( 2.5, 2.5, 2.5 );
+						model.traverse( ( child ) => {
+
+							if ( child.isMesh )
+								child.material.outputNode = halftones( output );
+
+						} );
+
+						scene.add( model );
+			
+					}
+				);
+
+				// renderer
+
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.setClearColor( '#000000' );
+				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();
+
+				const time = clock.getElapsedTime();
+				halftoneSettings[ 1 ].uniforms.direction.value.x = Math.cos( time );
+				halftoneSettings[ 1 ].uniforms.direction.value.y = Math.sin( time );
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 0
test/e2e/puppeteer.js

@@ -153,6 +153,7 @@ const exceptionList = [
 	'webgpu_tsl_vfx_flames',
 	'webgpu_tsl_galaxy',
 	'webgpu_tsl_compute_attractors_particles',
+	'webgpu_tsl_halftone',
 
 	// WebGPU idleTime and parseTime too low
 	'webgpu_compute_particles',

粤ICP备19079148号