|
@@ -0,0 +1,183 @@
|
|
|
+<html lang="en">
|
|
|
+ <head>
|
|
|
+ <title>three.js - WebGPU - Custom Lighting Model</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 - Custom Lighting Model<br/>(Chrome Canary with flag: --enable-unsafe-webgpu)
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script type="importmap">
|
|
|
+ {
|
|
|
+ "imports": {
|
|
|
+ "three": "../build/three.module.js"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ </script>
|
|
|
+ <script type="module">
|
|
|
+
|
|
|
+ import * as THREE from 'three';
|
|
|
+
|
|
|
+ import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js';
|
|
|
+ import WebGPU from './jsm/renderers/webgpu/WebGPU.js';
|
|
|
+
|
|
|
+ import { OrbitControls } from './jsm/controls/OrbitControls.js';
|
|
|
+
|
|
|
+ import ContextNode from './jsm/renderers/nodes/core/ContextNode.js';
|
|
|
+ import FunctionNode from './jsm/renderers/nodes/core/FunctionNode.js';
|
|
|
+
|
|
|
+ import LightsNode from './jsm/renderers/nodes/lights/LightsNode.js';
|
|
|
+
|
|
|
+ let camera, scene, renderer;
|
|
|
+
|
|
|
+ let light1, light2, light3;
|
|
|
+
|
|
|
+ init().then( animate ).catch( error );
|
|
|
+
|
|
|
+ async function init() {
|
|
|
+
|
|
|
+ if ( WebGPU.isAvailable() === false ) {
|
|
|
+
|
|
|
+ document.body.appendChild( WebGPU.getErrorMessage() );
|
|
|
+
|
|
|
+ throw 'No WebGPU support';
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 10 );
|
|
|
+ camera.position.z = 2;
|
|
|
+
|
|
|
+ scene = new THREE.Scene();
|
|
|
+ scene.background = new THREE.Color( 0x222222 );
|
|
|
+
|
|
|
+ // lights
|
|
|
+
|
|
|
+ const sphere = new THREE.SphereGeometry( 0.02, 16, 8 );
|
|
|
+
|
|
|
+ light1 = new THREE.PointLight( 0xffaa00, 2, 1 );
|
|
|
+ light1.add( new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( { color: 0xffaa00 } ) ) );
|
|
|
+ scene.add( light1 );
|
|
|
+
|
|
|
+ light2 = new THREE.PointLight( 0x0040ff, 2, 1 );
|
|
|
+ light2.add( new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( { color: 0x0040ff } ) ) );
|
|
|
+ scene.add( light2 );
|
|
|
+
|
|
|
+ light3 = new THREE.PointLight( 0x80ff80, 2, 1 );
|
|
|
+ light3.add( new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( { color: 0x80ff80 } ) ) );
|
|
|
+ scene.add( light3 );
|
|
|
+
|
|
|
+ //light nodes ( selective lights )
|
|
|
+
|
|
|
+ const allLightsNode = LightsNode.fromLights( [ light1, light2, light3 ] );
|
|
|
+
|
|
|
+ // points
|
|
|
+
|
|
|
+ const points = [];
|
|
|
+
|
|
|
+ for ( let i = 0; i < 3000; i ++ ) {
|
|
|
+
|
|
|
+ const point = new THREE.Vector3().random().subScalar( 0.5 ).multiplyScalar( 2 );
|
|
|
+ points.push( point );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ const geometryPoints = new THREE.BufferGeometry().setFromPoints( points );
|
|
|
+ const materialPoints = new THREE.PointsMaterial();
|
|
|
+
|
|
|
+ // custom lighting model
|
|
|
+
|
|
|
+ const RE_Direct = new FunctionNode( `
|
|
|
+ void ( vec3 lightColor ) {
|
|
|
+
|
|
|
+ // lightColor returns the light color with the intensity calculated
|
|
|
+
|
|
|
+ ReflectedLightDirectDiffuse += lightColor;
|
|
|
+
|
|
|
+ }` );
|
|
|
+
|
|
|
+
|
|
|
+ const RE_IndirectDiffuse = new FunctionNode( `
|
|
|
+ void ( ) {
|
|
|
+
|
|
|
+ //ReflectedLightIndirectDiffuse += vec3( 0.0 );
|
|
|
+
|
|
|
+ }` );
|
|
|
+
|
|
|
+ const lightingModelContext = new ContextNode( allLightsNode );
|
|
|
+ lightingModelContext.setParameter( 'RE_Direct', RE_Direct );
|
|
|
+ lightingModelContext.setParameter( 'RE_IndirectDiffuse', RE_IndirectDiffuse );
|
|
|
+
|
|
|
+ materialPoints.lightNode = lightingModelContext;
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ const pointCloud = new THREE.Points( geometryPoints, materialPoints );
|
|
|
+ scene.add( pointCloud );
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ renderer = new WebGPURenderer();
|
|
|
+ renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
+ document.body.appendChild( renderer.domElement );
|
|
|
+
|
|
|
+ // controls
|
|
|
+
|
|
|
+ const controls = new OrbitControls( camera, renderer.domElement );
|
|
|
+ controls.minDistance = 0;
|
|
|
+ controls.maxDistance = 4;
|
|
|
+
|
|
|
+ // events
|
|
|
+
|
|
|
+ window.addEventListener( 'resize', onWindowResize, false );
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ return renderer.init();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function onWindowResize() {
|
|
|
+
|
|
|
+ camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
+
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function animate() {
|
|
|
+
|
|
|
+ requestAnimationFrame( animate );
|
|
|
+
|
|
|
+ const time = Date.now() * 0.0005;
|
|
|
+ const scale = .5;
|
|
|
+
|
|
|
+ light1.position.x = Math.sin( time * 0.7 ) * scale;
|
|
|
+ light1.position.y = Math.cos( time * 0.5 ) * scale;
|
|
|
+ light1.position.z = Math.cos( time * 0.3 ) * scale;
|
|
|
+
|
|
|
+ light2.position.x = Math.cos( time * 0.3 ) * scale;
|
|
|
+ light2.position.y = Math.sin( time * 0.5 ) * scale;
|
|
|
+ light2.position.z = Math.sin( time * 0.7 ) * scale;
|
|
|
+
|
|
|
+ light3.position.x = Math.sin( time * 0.7 ) * scale;
|
|
|
+ light3.position.y = Math.cos( time * 0.3 ) * scale;
|
|
|
+ light3.position.z = Math.sin( time * 0.5 ) * scale;
|
|
|
+
|
|
|
+ renderer.render( scene, camera );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function error( error ) {
|
|
|
+
|
|
|
+ console.error( error );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ </script>
|
|
|
+ </body>
|
|
|
+</html>
|