|
@@ -0,0 +1,232 @@
|
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
|
+<html lang="en">
|
|
|
|
|
+ <head>
|
|
|
|
|
+ <title>three.js webgpu - angular slicing</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> - angular slicing
|
|
|
|
|
+ <br>
|
|
|
|
|
+ Based on <a href="https://threejs-journey.com/lessons/sliced-model-shader" target="_blank" rel="noopener">Three.js Journey</a> lesson
|
|
|
|
|
+ </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 { If, PI2, atan2, color, frontFacing, output, positionLocal, tslFn, uniform, vec4 } from 'three/tsl';
|
|
|
|
|
+
|
|
|
|
|
+ import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
|
|
|
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
|
|
|
+ import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
|
|
|
|
|
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
|
|
+ import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
|
|
|
|
+
|
|
|
|
|
+ let camera, scene, renderer, controls;
|
|
|
|
|
+
|
|
|
|
|
+ init();
|
|
|
|
|
+
|
|
|
|
|
+ function init() {
|
|
|
|
|
+
|
|
|
|
|
+ camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 0.1, 100 );
|
|
|
|
|
+ camera.position.set( - 5, 5, 12 );
|
|
|
|
|
+
|
|
|
|
|
+ scene = new THREE.Scene();
|
|
|
|
|
+
|
|
|
|
|
+ const gui = new GUI();
|
|
|
|
|
+
|
|
|
|
|
+ // environment
|
|
|
|
|
+
|
|
|
|
|
+ const rgbeLoader = new RGBELoader();
|
|
|
|
|
+ rgbeLoader.load( './textures/equirectangular/royal_esplanade_1k.hdr', ( environmentMap ) => {
|
|
|
|
|
+
|
|
|
|
|
+ environmentMap.mapping = THREE.EquirectangularReflectionMapping;
|
|
|
|
|
+
|
|
|
|
|
+ scene.background = environmentMap;
|
|
|
|
|
+ scene.environment = environmentMap;
|
|
|
|
|
+
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // lights
|
|
|
|
|
+
|
|
|
|
|
+ const directionalLight = new THREE.DirectionalLight( '#ffffff', 4 );
|
|
|
|
|
+ directionalLight.position.set( 6.25, 3, 4 );
|
|
|
|
|
+ directionalLight.castShadow = true;
|
|
|
|
|
+ directionalLight.shadow.mapSize.set( 1024, 1024 );
|
|
|
|
|
+ directionalLight.shadow.camera.near = 0.1;
|
|
|
|
|
+ directionalLight.shadow.camera.far = 30;
|
|
|
|
|
+ directionalLight.shadow.camera.top = 8;
|
|
|
|
|
+ directionalLight.shadow.camera.right = 8;
|
|
|
|
|
+ directionalLight.shadow.camera.bottom = - 8;
|
|
|
|
|
+ directionalLight.shadow.camera.left = - 8;
|
|
|
|
|
+ directionalLight.shadow.normalBias = 0.05;
|
|
|
|
|
+ scene.add( directionalLight );
|
|
|
|
|
+
|
|
|
|
|
+ // TSL functions
|
|
|
|
|
+
|
|
|
|
|
+ const inAngle = tslFn( ( [ position, angleStart, angleArc ] ) => {
|
|
|
|
|
+
|
|
|
|
|
+ const angle = atan2( position.y, position.x ).sub( angleStart ).mod( PI2 ).toVar();
|
|
|
|
|
+ return angle.greaterThan( 0 ).and( angle.lessThan( angleArc ) );
|
|
|
|
|
+
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // materials
|
|
|
|
|
+
|
|
|
|
|
+ const defaultMaterial = new THREE.MeshPhysicalNodeMaterial( {
|
|
|
|
|
+ metalness: 0.5,
|
|
|
|
|
+ roughness: 0.25,
|
|
|
|
|
+ envMapIntensity: 0.5,
|
|
|
|
|
+ color: '#858080'
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ const slicedMaterial = new THREE.MeshPhysicalNodeMaterial( {
|
|
|
|
|
+ metalness: 0.5,
|
|
|
|
|
+ roughness: 0.25,
|
|
|
|
|
+ envMapIntensity: 0.5,
|
|
|
|
|
+ color: '#858080',
|
|
|
|
|
+ side: THREE.DoubleSide
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // uniforms
|
|
|
|
|
+
|
|
|
|
|
+ const sliceStart = uniform( 1.75 );
|
|
|
|
|
+ const sliceArc = uniform( 1.25 );
|
|
|
|
|
+ const sliceColor = uniform( color( '#b62f58' ) );
|
|
|
|
|
+
|
|
|
|
|
+ // debug
|
|
|
|
|
+
|
|
|
|
|
+ gui.add( sliceStart, 'value', - Math.PI, Math.PI, 0.001 ).name( 'sliceStart' );
|
|
|
|
|
+ gui.add( sliceArc, 'value', 0, Math.PI * 2, 0.001 ).name( 'sliceArc' );
|
|
|
|
|
+ gui.addColor( { color: sliceColor.value.getHexString( THREE.SRGBColorSpace ) }, 'color' ).onChange( value => sliceColor.value.set( value ) );
|
|
|
|
|
+
|
|
|
|
|
+ // output
|
|
|
|
|
+
|
|
|
|
|
+ slicedMaterial.outputNode = tslFn( () => {
|
|
|
|
|
+
|
|
|
|
|
+ // discard
|
|
|
|
|
+
|
|
|
|
|
+ inAngle( positionLocal.xy, sliceStart, sliceArc ).discard();
|
|
|
|
|
+
|
|
|
|
|
+ // backface color
|
|
|
|
|
+
|
|
|
|
|
+ const finalOutput = output;
|
|
|
|
|
+ If( frontFacing.not(), () => {
|
|
|
|
|
+
|
|
|
|
|
+ finalOutput.assign( vec4( sliceColor, 1 ) );
|
|
|
|
|
+
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ return finalOutput;
|
|
|
|
|
+
|
|
|
|
|
+ } )();
|
|
|
|
|
+
|
|
|
|
|
+ // shadow
|
|
|
|
|
+
|
|
|
|
|
+ slicedMaterial.shadowNode = tslFn( () => {
|
|
|
|
|
+
|
|
|
|
|
+ // discard
|
|
|
|
|
+
|
|
|
|
|
+ inAngle( positionLocal.xy, sliceStart, sliceArc ).discard();
|
|
|
|
|
+
|
|
|
|
|
+ return vec4( 0, 0, 0, 1 );
|
|
|
|
|
+
|
|
|
|
|
+ } )();
|
|
|
|
|
+
|
|
|
|
|
+ // model
|
|
|
|
|
+
|
|
|
|
|
+ const dracoLoader = new DRACOLoader();
|
|
|
|
|
+ dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
|
|
|
|
|
+
|
|
|
|
|
+ const gltfLoader = new GLTFLoader();
|
|
|
|
|
+ gltfLoader.setDRACOLoader( dracoLoader );
|
|
|
|
|
+
|
|
|
|
|
+ gltfLoader.load( './models/gltf/gears.glb', ( gltf ) => {
|
|
|
|
|
+
|
|
|
|
|
+ const model = gltf.scene;
|
|
|
|
|
+
|
|
|
|
|
+ model.traverse( ( child ) => {
|
|
|
|
|
+
|
|
|
|
|
+ if ( child.isMesh ) {
|
|
|
|
|
+
|
|
|
|
|
+ if ( child.name === 'outerHull' )
|
|
|
|
|
+ child.material = slicedMaterial;
|
|
|
|
|
+ else
|
|
|
|
|
+ child.material = defaultMaterial;
|
|
|
|
|
+
|
|
|
|
|
+ child.castShadow = true;
|
|
|
|
|
+ child.receiveShadow = true;
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ scene.add( model );
|
|
|
|
|
+
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // plane
|
|
|
|
|
+
|
|
|
|
|
+ const plane = new THREE.Mesh(
|
|
|
|
|
+ new THREE.PlaneGeometry( 10, 10, 10 ),
|
|
|
|
|
+ new THREE.MeshStandardMaterial( { color: '#aaaaaa' } )
|
|
|
|
|
+ );
|
|
|
|
|
+ plane.receiveShadow = true;
|
|
|
|
|
+ plane.position.set( - 4, - 3, - 4 );
|
|
|
|
|
+ plane.lookAt( new THREE.Vector3( 0, 0, 0 ) );
|
|
|
|
|
+ scene.add( plane );
|
|
|
|
|
+
|
|
|
|
|
+ // renderer
|
|
|
|
|
+
|
|
|
|
|
+ renderer = new THREE.WebGPURenderer( { antialias: true } );
|
|
|
|
|
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
|
|
|
+ renderer.toneMappingExposure = 1;
|
|
|
|
|
+ renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
|
+ renderer.setAnimationLoop( animate );
|
|
|
|
|
+ document.body.appendChild( renderer.domElement );
|
|
|
|
|
+
|
|
|
|
|
+ // controls
|
|
|
|
|
+
|
|
|
|
|
+ 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>
|