Просмотр исходного кода

lth demo (#30695)

* lth demo

* fix

* fix2

* up

* fix screen shot
lo-th 1 год назад
Родитель
Сommit
7d5abde86b

+ 3 - 0
examples/files.json

@@ -6,6 +6,7 @@
 		"webgl_animation_skinning_ik",
 		"webgl_animation_skinning_morph",
 		"webgl_animation_multiple",
+		"webgl_animation_walk",
 		"webgl_camera",
 		"webgl_camera_array",
 		"webgl_camera_logarithmicdepthbuffer",
@@ -187,6 +188,7 @@
 		"webgl_points_sprites",
 		"webgl_points_waves",
 		"webgl_portal",
+		"webgl_random_uv",
 		"webgl_raycaster_bvh",
 		"webgl_raycaster_sprite",
 		"webgl_raycaster_texture",
@@ -212,6 +214,7 @@
 		"webgl_tonemapping",
 		"webgl_video_kinect",
 		"webgl_video_panorama_equirectangular",
+		"webgl_watch",
 		"webgl_water",
 		"webgl_water_flowmap"
 	],

BIN
examples/models/gltf/ShaderBall2.glb


BIN
examples/models/gltf/duck.glb


BIN
examples/models/gltf/rolex.glb


BIN
examples/screenshots/webgl_animation_walk.jpg


BIN
examples/screenshots/webgl_gpgpu_water.jpg


BIN
examples/screenshots/webgl_random_uv.jpg


BIN
examples/screenshots/webgl_watch.jpg


BIN
examples/textures/equirectangular/lobe.hdr


BIN
examples/textures/jade.jpg


BIN
examples/textures/noise.png


BIN
examples/textures/shaderball_ds.jpg


+ 418 - 0
examples/webgl_animation_walk.html

@@ -0,0 +1,418 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - animation - skinning</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">
+		<style>
+			a {
+				color: #f00;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Skeletal Animation Walking
+			(model from <a href="https://www.mixamo.com/" target="_blank" rel="noopener">mixamo.com</a>)<br/>
+			use arrows to control characters
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+			let scene, renderer, camera, floor, orbitControls;
+			let group, followGroup, model, skeleton, mixer, clock;
+		
+			let actions;
+
+			let settings = {
+				show_skeleton:false,
+				fixe_transition: true,
+			};
+
+			const PI = Math.PI;
+			const PI90 = Math.PI / 2;
+
+			const controls = {
+
+				key:[0,0],
+				ease : new THREE.Vector3(),
+				position : new THREE.Vector3(),
+				up : new THREE.Vector3(0, 1, 0),
+				rotate: new THREE.Quaternion(),
+				current:'Idle',
+				fadeDuration:0.5,
+				runVelocity:5,
+				walkVelocity:1.8,
+				rotateSpeed:0.05,
+				floorDecale:0,
+
+			};
+
+
+			init();
+
+			function init() {
+
+				const container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( 0, 2, - 5 );
+				//camera.lookAt( 0, 1, 0 );
+
+
+				clock = new THREE.Clock();
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x5e5d5d );
+				scene.fog = new THREE.Fog( 0x5e5d5d, 2, 20 );
+
+				group = new THREE.Group();
+				scene.add(group);
+
+				followGroup = new THREE.Group();
+				scene.add(followGroup);
+
+				/*const hemiLight = new THREE.HemisphereLight( 0xffffff, 0xb3602b, 0.5 );
+				hemiLight.position.set( 0, 20, 0 );
+				scene.add( hemiLight );*/
+
+				const dirLight = new THREE.DirectionalLight( 0xffffff, 5 );
+				dirLight.position.set( - 2, 5, - 3 );
+				dirLight.castShadow = true;
+				let cam = dirLight.shadow.camera;
+				cam.top = cam.right = 2;
+				cam.bottom = cam.left = - 2;
+				cam.near = 3;
+				cam.far = 8;
+				dirLight.shadow.bias = -0.005;
+				dirLight.shadow.radius = 4;
+				followGroup.add( dirLight );
+				followGroup.add( dirLight.target );
+
+				//scene.add( new THREE.CameraHelper( cam ) );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 0.5;
+				renderer.shadowMap.enabled = true;
+				container.appendChild( renderer.domElement );
+
+				orbitControls = new OrbitControls( camera, renderer.domElement );
+				orbitControls.target.set( 0, 1, 0 );
+				orbitControls.enableDamping = true;
+				orbitControls.enablePan = false;
+				orbitControls.maxPolarAngle = PI90 - 0.05;
+				orbitControls.update();
+
+				// EVENTS
+
+				window.addEventListener( 'resize', onWindowResize );
+				document.addEventListener( 'keydown', onKeyDown );
+				document.addEventListener( 'keyup', onKeyUp );
+
+
+				// DEMO
+
+				new RGBELoader()
+					.setPath( 'textures/equirectangular/' )
+					.load( 'lobe.hdr', function ( texture ) {
+
+						texture.mapping = THREE.EquirectangularReflectionMapping;
+						scene.environment = texture;
+						scene.environmentIntensity = 1.5;
+
+						loadModel();
+						addFloor();
+
+					});
+
+			}
+
+			function addFloor() {
+
+				let size = 50;
+				let repeat = 16;
+
+				const maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
+
+				const floorT = new THREE.TextureLoader().load( 'textures/floors/FloorsCheckerboard_S_Diffuse.jpg' );
+				floorT.colorSpace = THREE.SRGBColorSpace;
+				floorT.repeat.set( repeat, repeat );
+				floorT.wrapS = floorT.wrapT = THREE.RepeatWrapping;
+				floorT.anisotropy = maxAnisotropy;
+
+				const floorN = new THREE.TextureLoader().load( 'textures/floors/FloorsCheckerboard_S_Normal.jpg' );
+				floorN.repeat.set( repeat, repeat );
+				floorN.wrapS = floorN.wrapT = THREE.RepeatWrapping;
+				floorN.anisotropy = maxAnisotropy;
+
+				let mat = new THREE.MeshStandardMaterial( { map:floorT, normalMap:floorN, normalScale:new THREE.Vector2(0.5,0.5), color: 0x404040, depthWrite: false, roughness:0.85 } )
+
+				let g = new THREE.PlaneGeometry( size, size, 50, 50 );
+				g.rotateX( -PI90 );
+
+				floor = new THREE.Mesh( g, mat );
+				floor.receiveShadow = true;
+				scene.add( floor );
+
+				controls.floorDecale = (size / repeat) * 4;
+
+				const bulbGeometry = new THREE.SphereGeometry( 0.05, 16, 8 );
+				let bulbLight = new THREE.PointLight( 0xffee88, 2, 500, 2 );
+
+				let bulbMat = new THREE.MeshStandardMaterial( { emissive: 0xffffee, emissiveIntensity: 1, color: 0x000000 } );
+				bulbLight.add( new THREE.Mesh( bulbGeometry, bulbMat ) );
+				bulbLight.position.set( 1, 0.1, -3 );
+				bulbLight.castShadow = true;
+				floor.add( bulbLight );
+
+			}
+
+			function loadModel() {
+
+				const loader = new GLTFLoader();
+				loader.load( 'models/gltf/Soldier.glb', function ( gltf ) {
+
+
+					model = gltf.scene;
+					group.add( model );
+					model.rotation.y = PI;
+					group.rotation.y = PI;
+
+					model.traverse( function ( object ) {
+
+						if ( object.isMesh ){
+						    if( object.name == 'vanguard_Mesh' ){
+						    	object.castShadow = true;
+								object.receiveShadow = true;
+								object.material.shadowSide = THREE.DoubleSide;
+								//object.material.envMapIntensity = 0.5;
+								object.material.metalness = 1.0;
+								object.material.roughness = 0.2;
+								object.material.color.set(1,1,1);
+								object.material.metalnessMap = object.material.map;
+						    } else {
+						    	object.material.metalness = 1;
+								object.material.roughness = 0;
+								object.material.transparent = true;
+								object.material.opacity = 0.8;
+								object.material.color.set(1,1,1);
+						    }
+						}
+
+					});
+
+					//
+
+					skeleton = new THREE.SkeletonHelper( model );
+					skeleton.visible = false;
+					scene.add( skeleton );
+
+					//
+
+					createPanel();
+
+					//
+
+					const animations = gltf.animations;
+
+					mixer = new THREE.AnimationMixer( model );
+
+					actions = {
+						Idle:mixer.clipAction( animations[ 0 ] ),
+						Walk:mixer.clipAction( animations[ 3 ] ),
+						Run:mixer.clipAction( animations[ 1 ] )
+					};
+
+					for( let m in actions ){
+						actions[m].enabled = true;
+						actions[m].setEffectiveTimeScale( 1 );
+						if(m!=='Idle') actions[m].setEffectiveWeight( 0 );
+					}
+
+					actions.Idle.play();
+
+					animate();
+
+				});
+
+			}
+
+			function updateCharacter( delta ) {
+
+				const fade = controls.fadeDuration
+				const key = controls.key;
+				const up = controls.up;
+				const ease = controls.ease;
+				const rotate = controls.rotate;
+				const position = controls.position;
+				const azimut = orbitControls.getAzimuthalAngle();
+
+				let active = key[0] === 0 && key[1] === 0 ? false : true;
+				let play = active ? (key[2] ? 'Run' : 'Walk') : 'Idle';
+
+		        // change animation
+
+		        if ( controls.current != play ){
+
+		           
+
+		        	const current = actions[play];
+		            const old = actions[controls.current];
+		            controls.current = play;
+
+		            if( settings.fixe_transition ){
+		            	current.reset()
+		            	current.weight = 1.0;
+		            	current.stopFading()
+		            	old.stopFading();
+		            	// sycro if not idle
+                        if ( play !== 'Idle' ) current.time = old.time * ( current.getClip().duration / old.getClip().duration );
+		            	old._scheduleFading( fade, old.getEffectiveWeight(), 0 );
+                        current._scheduleFading( fade, current.getEffectiveWeight(), 1 );	
+			            current.play();
+		            } else {
+		            	setWeight( current, 1.0 );
+			            old.fadeOut(fade);
+			            current.reset().fadeIn( fade ).play();
+		            }
+
+		        }
+
+		        // move object
+
+		        if ( controls.current !== 'Idle' ) {
+
+		            // run/walk velocity
+		            let velocity = controls.current == 'Run' ? controls.runVelocity : controls.walkVelocity;
+
+		            // direction with key
+		            ease.set( key[1], 0, key[0] ).multiplyScalar( velocity * delta );
+
+		            // calculate camera direction
+		            let angle = unwrapRad( Math.atan2( ease.x, ease.z ) + azimut );
+		            rotate.setFromAxisAngle( up, angle );
+		            
+		            // apply camera angle on ease
+		            controls.ease.applyAxisAngle( up, azimut );
+
+		            position.add( ease );
+		            camera.position.add( ease );
+
+		            group.position.copy( position );
+		            group.quaternion.rotateTowards( rotate, controls.rotateSpeed );
+
+		            orbitControls.target.copy( position ).add({x:0, y:1, z:0});
+		            followGroup.position.copy( position );
+
+		            // decale floor at infinie
+		            let dx = ( position.x - floor.position.x );
+		            let dz = ( position.z - floor.position.z );
+		            if( Math.abs(dx) > controls.floorDecale ) floor.position.x += dx;
+		            if( Math.abs(dz) > controls.floorDecale ) floor.position.z += dz;
+
+			    }
+
+				mixer.update( delta );
+				orbitControls.update();
+
+			}
+
+			function unwrapRad(r) {
+				return Math.atan2(Math.sin(r), Math.cos(r));
+			}
+
+			function createPanel() {
+
+				const panel = new GUI( { width: 310 } );
+
+				panel.add( settings, 'show_skeleton' ).onChange( (b) => { skeleton.visible = b; } );
+				panel.add( settings, 'fixe_transition' );
+
+			}
+
+			function setWeight( action, weight ) {
+
+				action.enabled = true;
+				action.setEffectiveTimeScale( 1 );
+				action.setEffectiveWeight( weight );
+
+			}
+
+			function onKeyDown( event ) {
+
+				const key = controls.key;
+				switch ( event.code ) {
+					case 'ArrowUp': case 'KeyW': case 'KeyZ': key[0] = -1; break;
+					case 'ArrowDown': case 'KeyS': key[0] = 1; break;
+					case 'ArrowLeft': case 'KeyA': case 'KeyQ': key[1] = -1; break;
+					case 'ArrowRight': case 'KeyD': key[1] = 1; break;
+					case 'ShiftLeft' : case 'ShiftRight' : key[2] = 1; break;
+				}
+
+			}
+
+			function onKeyUp( event ) {
+
+				const key = controls.key;
+				switch ( event.code ) {
+					case 'ArrowUp': case 'KeyW': case 'KeyZ': key[0] = key[0]<0 ? 0:key[0]; break;
+					case 'ArrowDown': case 'KeyS': key[0] = key[0]>0 ? 0:key[0]; break;
+					case 'ArrowLeft': case 'KeyA': case 'KeyQ': key[1] = key[1]<0 ? 0:key[1]; break;
+					case 'ArrowRight': case 'KeyD': key[1] = key[1]>0 ? 0:key[1]; break;
+					case 'ShiftLeft' : case 'ShiftRight' : key[2] = 0; break;
+				}
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				// Render loop
+
+				requestAnimationFrame( animate );
+
+				let delta = clock.getDelta();
+
+				updateCharacter( delta );
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 339 - 263
examples/webgl_gpgpu_water.html

@@ -10,55 +10,10 @@
 
 		<div id="info">
 			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - <span id="waterSize"></span> webgl gpgpu water<br/>
-			Move mouse to disturb water.<br>
-			'W' key toggles wireframe.
+			Click and Move mouse to disturb water.
 		</div>
 
 
-		<!-- This is the 'compute shader' for the water heightmap: -->
-		<script id="heightmapFragmentShader" type="x-shader/x-fragment">
-
-			#include <common>
-
-			uniform vec2 mousePos;
-			uniform float mouseSize;
-			uniform float viscosityConstant;
-			uniform float heightCompensation;
-
-			void main()	{
-
-				vec2 cellSize = 1.0 / resolution.xy;
-
-				vec2 uv = gl_FragCoord.xy * cellSize;
-
-				// heightmapValue.x == height from previous frame
-				// heightmapValue.y == height from penultimate frame
-				// heightmapValue.z, heightmapValue.w not used
-				vec4 heightmapValue = texture2D( heightmap, uv );
-
-				// Get neighbours
-				vec4 north = texture2D( heightmap, uv + vec2( 0.0, cellSize.y ) );
-				vec4 south = texture2D( heightmap, uv + vec2( 0.0, - cellSize.y ) );
-				vec4 east = texture2D( heightmap, uv + vec2( cellSize.x, 0.0 ) );
-				vec4 west = texture2D( heightmap, uv + vec2( - cellSize.x, 0.0 ) );
-
-				// https://web.archive.org/web/20080618181901/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm
-
-				float newHeight = ( ( north.x + south.x + east.x + west.x ) * 0.5 - heightmapValue.y ) * viscosityConstant;
-
-				// Mouse influence
-				float mousePhase = clamp( length( ( uv - vec2( 0.5 ) ) * BOUNDS - vec2( mousePos.x, - mousePos.y ) ) * PI / mouseSize, 0.0, PI );
-				newHeight += ( cos( mousePhase ) + 1.0 ) * 0.28;
-
-				heightmapValue.y = heightmapValue.x;
-				heightmapValue.x = newHeight;
-
-				gl_FragColor = heightmapValue;
-
-			}
-
-		</script>
-
 		<!-- This is just a smoothing 'compute shader' for using manually: -->
 		<script id="smoothFragmentShader" type="x-shader/x-fragment">
 
@@ -141,12 +96,12 @@
 			void main()	{
 
 				vec2 cellSize = 1.0 / resolution.xy;
-
 				float waterLevel = texture2D( levelTexture, point1 ).x;
 
 				vec2 normal = vec2(
 					( texture2D( levelTexture, point1 + vec2( - cellSize.x, 0 ) ).x - texture2D( levelTexture, point1 + vec2( cellSize.x, 0 ) ).x ) * WIDTH / BOUNDS,
 					( texture2D( levelTexture, point1 + vec2( 0, - cellSize.y ) ).x - texture2D( levelTexture, point1 + vec2( 0, cellSize.y ) ).x ) * WIDTH / BOUNDS );
+			
 
 				if ( gl_FragCoord.x < 1.5 ) {
 
@@ -170,79 +125,6 @@
 
 		</script>
 
-		<!-- This is the water visualization shader, copied from the THREE.MeshPhongMaterial and modified: -->
-		<script id="waterVertexShader" type="x-shader/x-vertex">
-
-			uniform sampler2D heightmap;
-
-			#define PHONG
-
-			varying vec3 vViewPosition;
-
-			#ifndef FLAT_SHADED
-
-				varying vec3 vNormal;
-
-			#endif
-
-			#include <common>
-			#include <uv_pars_vertex>
-			#include <displacementmap_pars_vertex>
-			#include <envmap_pars_vertex>
-			#include <color_pars_vertex>
-			#include <morphtarget_pars_vertex>
-			#include <skinning_pars_vertex>
-			#include <shadowmap_pars_vertex>
-			#include <logdepthbuf_pars_vertex>
-			#include <clipping_planes_pars_vertex>
-
-			void main() {
-
-				vec2 cellSize = vec2( 1.0 / WIDTH, 1.0 / WIDTH );
-
-				#include <uv_vertex>
-				#include <color_vertex>
-
-				// # include <beginnormal_vertex>
-				// Compute normal from heightmap
-				vec3 objectNormal = vec3(
-					( texture2D( heightmap, uv + vec2( - cellSize.x, 0 ) ).x - texture2D( heightmap, uv + vec2( cellSize.x, 0 ) ).x ) * WIDTH / BOUNDS,
-					( texture2D( heightmap, uv + vec2( 0, - cellSize.y ) ).x - texture2D( heightmap, uv + vec2( 0, cellSize.y ) ).x ) * WIDTH / BOUNDS,
-					1.0 );
-				//<beginnormal_vertex>
-
-				#include <morphnormal_vertex>
-				#include <skinbase_vertex>
-				#include <skinnormal_vertex>
-				#include <defaultnormal_vertex>
-
-			#ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
-
-				vNormal = normalize( transformedNormal );
-
-			#endif
-
-				//# include <begin_vertex>
-				float heightValue = texture2D( heightmap, uv ).x;
-				vec3 transformed = vec3( position.x, position.y, heightValue );
-				//<begin_vertex>
-
-				#include <morphtarget_vertex>
-				#include <skinning_vertex>
-				#include <displacementmap_vertex>
-				#include <project_vertex>
-				#include <logdepthbuf_vertex>
-				#include <clipping_planes_vertex>
-
-				vViewPosition = - mvPosition.xyz;
-
-				#include <worldpos_vertex>
-				#include <envmap_vertex>
-				#include <shadowmap_vertex>
-
-			}
-
-		</script>
 
 		<script type="importmap">
 			{
@@ -259,24 +141,37 @@
 
 			import Stats from 'three/addons/libs/stats.module.js';
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
 			import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
 			import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
 
 			// Texture width for simulation
 			const WIDTH = 128;
 
 			// Water size in system units
-			const BOUNDS = 512;
+			const BOUNDS = 6;
 			const BOUNDS_HALF = BOUNDS * 0.5;
 
+			let tmpHeightmap = null;
+			let tmpQuat = new THREE.Quaternion()
+			let tmpQuatX = new THREE.Quaternion()
+			let tmpQuatZ = new THREE.Quaternion()
+			let duckModel = null;
+
 			let container, stats;
-			let camera, scene, renderer;
+			let camera, scene, renderer, controls;
 			let mouseMoved = false;
+			let mousedown = false;
 			const mouseCoords = new THREE.Vector2();
 			const raycaster = new THREE.Raycaster();
 
+			let sun;
 			let waterMesh;
+			let poolBorder;
 			let meshRay;
 			let gpuCompute;
 			let heightmapVariable;
@@ -287,148 +182,151 @@
 			let readWaterLevelImage;
 			const waterNormal = new THREE.Vector3();
 
-			const NUM_SPHERES = 5;
-			const spheres = [];
-			let spheresEnabled = true;
+			const NUM_DUCK = 12;
+			const ducks = [];
+			let ducksEnabled = true;
 
 			const simplex = new SimplexNoise();
 
+			let frame = 0;
+
+			const effectController = {
+				mouseSize: 0.2,
+				mouseDeep: 0.01,
+				viscosity: 0.93,
+				speed:5,
+				ducksEnabled: ducksEnabled,
+				wireframe:false,
+				shadow:false,
+			};
+
 			init();
+			
 
-			function init() {
+			async function init() {
 
 				container = document.createElement( 'div' );
 				document.body.appendChild( container );
 
-				camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 3000 );
-				camera.position.set( 0, 200, 350 );
+				camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
+				camera.position.set( 0, 2.00, 4 );
 				camera.lookAt( 0, 0, 0 );
 
 				scene = new THREE.Scene();
 
-				const sun = new THREE.DirectionalLight( 0xFFFFFF, 3.0 );
-				sun.position.set( 300, 400, 175 );
+				sun = new THREE.DirectionalLight( 0xFFFFFF, 4.0 );
+				sun.position.set(  -1, 2.6, 1.4 );
 				scene.add( sun );
 
-				const sun2 = new THREE.DirectionalLight( 0x40A040, 2.0 );
-				sun2.position.set( - 100, 350, - 200 );
-				scene.add( sun2 );
-
-				renderer = new THREE.WebGLRenderer();
+				renderer = new THREE.WebGLRenderer({antialias:true, stencil:false});
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 0.5;
 				container.appendChild( renderer.domElement );
 
+				controls = new OrbitControls( camera, container );
+
 				stats = new Stats();
 				container.appendChild( stats.dom );
 
 				container.style.touchAction = 'none';
 				container.addEventListener( 'pointermove', onPointerMove );
+				container.addEventListener( 'pointerdown', onPointerDown );
+				container.addEventListener( 'pointerup', onPointerUp );
 
-				document.addEventListener( 'keydown', function ( event ) {
-
-					// W Pressed: Toggle wireframe
-					if ( event.keyCode === 87 ) {
+				window.addEventListener( 'resize', onWindowResize );
 
-						waterMesh.material.wireframe = ! waterMesh.material.wireframe;
-						waterMesh.material.needsUpdate = true;
 
-					}
-
-				} );
+				const rgbeLoader = new RGBELoader().setPath( './textures/equirectangular/' );
+				const glbloader = new GLTFLoader().setPath( 'models/gltf/' );
+				glbloader.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) )
+				
+				const [ env, model ] = await Promise.all( [ rgbeLoader.loadAsync( 'blouberg_sunrise_2_1k.hdr' ), glbloader.loadAsync( 'duck.glb') ]);
+				env.mapping = THREE.EquirectangularReflectionMapping;
+				scene.environment = env;
+				scene.background = env;
+				scene.backgroundBlurriness = 0.3;
+				scene.environmentIntensity = 1.25;
 
-				window.addEventListener( 'resize', onWindowResize );
+				duckModel = model.scene.children[0];
+				duckModel.receiveShadow = true;
+				duckModel.castShadow = true;
 
 
 				const gui = new GUI();
-
-				const effectController = {
-					mouseSize: 20.0,
-					viscosity: 0.98,
-					spheresEnabled: spheresEnabled
-				};
+				gui.domElement.style.right = '0px';
 
 				const valuesChanger = function () {
 
 					heightmapVariable.material.uniforms[ 'mouseSize' ].value = effectController.mouseSize;
-					heightmapVariable.material.uniforms[ 'viscosityConstant' ].value = effectController.viscosity;
-					spheresEnabled = effectController.spheresEnabled;
-					for ( let i = 0; i < NUM_SPHERES; i ++ ) {
-
-						if ( spheres[ i ] ) {
-
-							spheres[ i ].visible = spheresEnabled;
-
-						}
-
-					}
+					heightmapVariable.material.uniforms[ 'deep' ].value = effectController.mouseDeep;
+					heightmapVariable.material.uniforms[ 'viscosity' ].value = effectController.viscosity;
+					ducksEnabled = effectController.ducksEnabled;
 
+					let i = NUM_DUCK;
+					while(i--){ if ( ducks[ i ] ) ducks[ i ].visible = ducksEnabled; }
+				
 				};
 
-				gui.add( effectController, 'mouseSize', 1.0, 100.0, 1.0 ).onChange( valuesChanger );
+				gui.add( effectController, 'mouseSize', 0.1, 1.0, 0.1 ).onChange( valuesChanger );
+				gui.add( effectController, 'mouseDeep', 0.01, 1.0, 0.01 ).onChange( valuesChanger );
 				gui.add( effectController, 'viscosity', 0.9, 0.999, 0.001 ).onChange( valuesChanger );
-				gui.add( effectController, 'spheresEnabled' ).onChange( valuesChanger );
-				const buttonSmooth = {
-					smoothWater: function () {
-
-						smoothWater();
-
-					}
-				};
-				gui.add( buttonSmooth, 'smoothWater' );
-
+				gui.add( effectController, 'speed', 1, 6, 1 );
+				gui.add( effectController, 'ducksEnabled' ).onChange( valuesChanger );
+				gui.add( effectController, 'wireframe' ).onChange( (v)=>{ 
+					waterMesh.material.wireframe = v; 
+					poolBorder.material.wireframe = v; 
+				});
+				gui.add( effectController, 'shadow' ).onChange( addShadow );
+				
+				//const buttonSmooth = { smoothWater: function () {smoothWater();} };
+				//gui.add( buttonSmooth, 'smoothWater' );
+				
 
 				initWater();
 
-				createSpheres();
+				createducks();
 
 				valuesChanger();
 
+				animate();
+
 			}
 
 
 			function initWater() {
 
-				const materialColor = 0x0040C0;
-
 				const geometry = new THREE.PlaneGeometry( BOUNDS, BOUNDS, WIDTH - 1, WIDTH - 1 );
 
-				// material: make a THREE.ShaderMaterial clone of THREE.MeshPhongMaterial, with customized vertex shader
-				const material = new THREE.ShaderMaterial( {
-					uniforms: THREE.UniformsUtils.merge( [
-						THREE.ShaderLib[ 'phong' ].uniforms,
-						{
-							'heightmap': { value: null }
-						}
-					] ),
-					vertexShader: document.getElementById( 'waterVertexShader' ).textContent,
-					fragmentShader: THREE.ShaderChunk[ 'meshphong_frag' ]
-
-				} );
-
-				material.lights = true;
-
-				// Material attributes from THREE.MeshPhongMaterial
-				// Sets the uniforms with the material values
-				material.uniforms[ 'diffuse' ].value = new THREE.Color( materialColor );
-				material.uniforms[ 'specular' ].value = new THREE.Color( 0x111111 );
-				material.uniforms[ 'shininess' ].value = Math.max( 50, 1e-4 );
-				material.uniforms[ 'opacity' ].value = material.opacity;
-
-				// Defines
-				material.defines.WIDTH = WIDTH.toFixed( 1 );
-				material.defines.BOUNDS = BOUNDS.toFixed( 1 );
-
-				waterUniforms = material.uniforms;
+				const material = new WaterMaterial({
+					color:0x9bd2ec,
+					metalness:0.9,
+					roughness:0,
+					transparent:true,
+					opacity:0.8,
+					side:THREE.DoubleSide
+				});
 
 				waterMesh = new THREE.Mesh( geometry, material );
-				waterMesh.rotation.x = - Math.PI / 2;
+				waterMesh.rotation.x = - Math.PI * 0.5;
 				waterMesh.matrixAutoUpdate = false;
 				waterMesh.updateMatrix();
 
+				waterMesh.receiveShadow = true;
+				waterMesh.castShadow = true;
+
 				scene.add( waterMesh );
 
+				// pool border 
+				const borderGeom = new THREE.TorusGeometry(4.2, 0.1, 12, 4);
+				borderGeom.rotateX(Math.PI*0.5);
+				borderGeom.rotateY(Math.PI*0.25);
+				poolBorder = new THREE.Mesh( borderGeom, new THREE.MeshStandardMaterial( { color: 0x908877, roughness:0.2 } ));
+				scene.add(poolBorder);
+				borderGeom.receiveShadow = true;
+				borderGeom.castShadow = true;
+
 				// THREE.Mesh just for mouse raycasting
 				const geometryRay = new THREE.PlaneGeometry( BOUNDS, BOUNDS, 1, 1 );
 				meshRay = new THREE.Mesh( geometryRay, new THREE.MeshBasicMaterial( { color: 0xFFFFFF, visible: false } ) );
@@ -446,22 +344,18 @@
 
 				fillTexture( heightmap0 );
 
-				heightmapVariable = gpuCompute.addVariable( 'heightmap', document.getElementById( 'heightmapFragmentShader' ).textContent, heightmap0 );
+				heightmapVariable = gpuCompute.addVariable( 'heightmap', shaderChange.heightmap_frag, heightmap0 );
 
 				gpuCompute.setVariableDependencies( heightmapVariable, [ heightmapVariable ] );
 
 				heightmapVariable.material.uniforms[ 'mousePos' ] = { value: new THREE.Vector2( 10000, 10000 ) };
-				heightmapVariable.material.uniforms[ 'mouseSize' ] = { value: 20.0 };
-				heightmapVariable.material.uniforms[ 'viscosityConstant' ] = { value: 0.98 };
-				heightmapVariable.material.uniforms[ 'heightCompensation' ] = { value: 0 };
+				heightmapVariable.material.uniforms[ 'mouseSize' ] = { value: 0.2 };
+				heightmapVariable.material.uniforms[ 'viscosity' ] = { value: 0.93 };
+				heightmapVariable.material.uniforms[ 'deep' ] = { value: 0.01 };
 				heightmapVariable.material.defines.BOUNDS = BOUNDS.toFixed( 1 );
 
 				const error = gpuCompute.init();
-				if ( error !== null ) {
-
-				    console.error( error );
-
-				}
+				if ( error !== null ) console.error( error );
 
 				// Create compute shader to smooth the water surface and velocity
 				smoothShader = gpuCompute.createShaderMaterial( document.getElementById( 'smoothFragmentShader' ).textContent, { smoothTexture: { value: null } } );
@@ -491,7 +385,7 @@
 
 			function fillTexture( texture ) {
 
-				const waterMaxHeight = 10;
+				const waterMaxHeight = 0.1;
 
 				function noise( x, y ) {
 
@@ -533,6 +427,35 @@
 
 			}
 
+			function addShadow( v ) {
+
+				renderer.shadowMap.enabled = v;
+				sun.castShadow = v;
+
+				if(v){
+
+					renderer.shadowMap.type = THREE.VSMShadowMap;
+					let shadow = sun.shadow;
+					shadow.mapSize.width = shadow.mapSize.height = 2048;
+					shadow.radius = 2;
+					shadow.bias = - 0.0005;
+					let shadowCam = shadow.camera, s = 5;
+					shadowCam.near = 0.1;
+					shadowCam.far = 6;
+					shadowCam.right = shadowCam.top	= s;
+					shadowCam.left = shadowCam.bottom = -s;
+
+				} else {
+
+					if(sun.shadow) sun.shadow.dispose();
+					
+				}
+
+				// debug shadow
+				//scene.add(  new THREE.CameraHelper(shadowCam) );
+
+			}
+
 			function smoothWater() {
 
 				const currentRenderTarget = gpuCompute.getCurrentRenderTarget( heightmapVariable );
@@ -550,16 +473,14 @@
 
 			}
 
-			function createSpheres() {
-
-				const sphereTemplate = new THREE.Mesh( new THREE.SphereGeometry( 4, 24, 12 ), new THREE.MeshPhongMaterial( { color: 0xFFFF00 } ) );
+			function createducks() {
 
-				for ( let i = 0; i < NUM_SPHERES; i ++ ) {
+				for ( let i = 0; i < NUM_DUCK; i ++ ) {
 
-					let sphere = sphereTemplate;
-					if ( i < NUM_SPHERES - 1 ) {
+					let sphere = duckModel;
+					if ( i < NUM_DUCK - 1 ) {
 
-						sphere = sphereTemplate.clone();
+						sphere = duckModel.clone();
 
 					}
 
@@ -570,21 +491,19 @@
 
 					scene.add( sphere );
 
-					spheres[ i ] = sphere;
+					ducks[ i ] = sphere;
 
 				}
 
 			}
 
-			function sphereDynamics() {
+			function duckDynamics() {
 
-				const currentRenderTarget = gpuCompute.getCurrentRenderTarget( heightmapVariable );
+				readWaterLevelShader.uniforms[ 'levelTexture' ].value = tmpHeightmap;
 
-				readWaterLevelShader.uniforms[ 'levelTexture' ].value = currentRenderTarget.texture;
+				for ( let i = 0; i < NUM_DUCK; i ++ ) {
 
-				for ( let i = 0; i < NUM_SPHERES; i ++ ) {
-
-					const sphere = spheres[ i ];
+					const sphere = ducks[ i ];
 
 					if ( sphere ) {
 
@@ -602,39 +521,61 @@
 
 						const pos = sphere.position;
 
+						let startPos = pos.clone();
+
 						// Set height
 						pos.y = pixels[ 0 ];
 
 						// Move sphere
-						waterNormal.multiplyScalar( 0.1 );
+						waterNormal.multiplyScalar( 0.01 );
 						sphere.userData.velocity.add( waterNormal );
 						sphere.userData.velocity.multiplyScalar( 0.998 );
 						pos.add( sphere.userData.velocity );
 
-						if ( pos.x < - BOUNDS_HALF ) {
+						 
 
-							pos.x = - BOUNDS_HALF + 0.001;
+						let decal = 0.001;
+						let limit = BOUNDS_HALF-0.2;
+
+						if ( pos.x < - limit ) {
+
+							pos.x = - limit + decal;
 							sphere.userData.velocity.x *= - 0.3;
 
-						} else if ( pos.x > BOUNDS_HALF ) {
+						} else if ( pos.x > limit ) {
 
-							pos.x = BOUNDS_HALF - 0.001;
+							pos.x = limit - decal;
 							sphere.userData.velocity.x *= - 0.3;
 
 						}
 
-						if ( pos.z < - BOUNDS_HALF ) {
+						if ( pos.z < - limit ) {
 
-							pos.z = - BOUNDS_HALF + 0.001;
+							pos.z = - limit + decal;
 							sphere.userData.velocity.z *= - 0.3;
 
-						} else if ( pos.z > BOUNDS_HALF ) {
+						} else if ( pos.z > limit ) {
 
-							pos.z = BOUNDS_HALF - 0.001;
+							pos.z = limit - decal;
 							sphere.userData.velocity.z *= - 0.3;
 
 						}
 
+						// duck orientation test
+
+						let startNormal = new THREE.Vector3(pixels[ 1 ], 1, - pixels[ 2 ] ).normalize();
+				
+						let dir = startPos.sub(pos);
+						dir.y = 0;
+						dir.normalize();
+
+						let yAxis = new THREE.Vector3(0, 1, 0);
+						let zAxis = new THREE.Vector3(0, 0, -1);
+						tmpQuatX.setFromUnitVectors( zAxis, dir );
+						tmpQuatZ.setFromUnitVectors( yAxis, startNormal );
+						tmpQuat.multiplyQuaternions( tmpQuatZ,tmpQuatX );
+						sphere.quaternion.slerp(tmpQuat, 0.017);
+
 					}
 
 				}
@@ -650,33 +591,27 @@
 
 			}
 
-			function setMouseCoords( x, y ) {
-
-				mouseCoords.set( ( x / renderer.domElement.clientWidth ) * 2 - 1, - ( y / renderer.domElement.clientHeight ) * 2 + 1 );
-				mouseMoved = true;
-
+			function onPointerDown( event ) {
+				mousedown = true;
 			}
 
-			function onPointerMove( event ) {
-
-				if ( event.isPrimary === false ) return;
-
-				setMouseCoords( event.clientX, event.clientY );
-
+			function onPointerUp( event ) {
+				mousedown = false;
+				controls.enabled = true;
 			}
 
-			function animate() {
+			function onPointerMove( event ) {
 
-				render();
-				stats.update();
+				let dom = renderer.domElement;
+				mouseCoords.set( ( event.clientX / dom.clientWidth ) * 2 - 1, - ( event.clientY / dom.clientHeight ) * 2 + 1 );
 
 			}
 
-			function render() {
+			function raycast() {
 
 				// Set uniforms: mouse interaction
 				const uniforms = heightmapVariable.material.uniforms;
-				if ( mouseMoved ) {
+				if ( mousedown ) {
 
 					raycaster.setFromCamera( mouseCoords, camera );
 
@@ -686,6 +621,7 @@
 
 						const point = intersects[ 0 ].point;
 						uniforms[ 'mousePos' ].value.set( point.x, point.z );
+						if(controls.enabled) controls.enabled = false;
 
 					} else {
 
@@ -693,31 +629,171 @@
 
 					}
 
-					mouseMoved = false;
-
 				} else {
 
 					uniforms[ 'mousePos' ].value.set( 10000, 10000 );
 
 				}
 
-				// Do the gpu computation
-				gpuCompute.compute();
+			}
 
-				if ( spheresEnabled ) {
+			function animate() {
 
-					sphereDynamics();
+				requestAnimationFrame( animate );
+				render();
+				stats.update();
 
-				}
+			}
+
+			function render() {
+
+				raycast();
+
+				frame++
+
+				if ( frame >= 7 - effectController.speed ) {
+
+					// Do the gpu computation
+					gpuCompute.compute();
+					tmpHeightmap = gpuCompute.getCurrentRenderTarget( heightmapVariable ).texture;
+
+					if ( ducksEnabled ) duckDynamics();
+
+					// Get compute output in custom uniform
+					if( waterMesh ) waterMesh.material.heightmap = tmpHeightmap;
+
+					frame = 0;
 
-				// Get compute output in custom uniform
-				waterUniforms[ 'heightmap' ].value = gpuCompute.getCurrentRenderTarget( heightmapVariable ).texture;
+				}
 
 				// Render
 				renderer.render( scene, camera );
 
 			}
 
+
+			//----------------------
+
+			class WaterMaterial extends THREE.MeshStandardMaterial {
+
+				constructor( parameters ) {
+
+					super();
+
+					this.defines = {
+
+						'STANDARD': '',
+						'USE_UV': '',
+						'WIDTH': WIDTH.toFixed( 1 ),
+						'BOUNDS': BOUNDS.toFixed( 1 ),
+
+					};
+
+					this.extra = {};
+
+					this.addParametre( 'heightmap', null );
+					
+					this.setValues( parameters );
+
+				}
+
+				addParametre( name, value ){
+
+					this.extra[ name ] = value;
+					Object.defineProperty( this, name, {
+						get: () => ( this.extra[ name ] ),
+						set: ( v ) => {
+							this.extra[ name ] = v;
+							if( this.userData.shader ) this.userData.shader.uniforms[name].value = this.extra[ name ];
+						}
+					});
+				}
+
+				onBeforeCompile( shader ){
+
+					for(let name in this.extra ) {
+						shader.uniforms[ name ] = { value: this.extra[name] };
+					}
+
+					shader.vertexShader = shader.vertexShader.replace( '#include <common>', shaderChange.common );
+					//shader.vertexShader = 'uniform sampler2D heightmap;\n' + shader.vertexShader;
+					shader.vertexShader = shader.vertexShader.replace( '#include <beginnormal_vertex>', shaderChange.beginnormal_vertex );
+					shader.vertexShader = shader.vertexShader.replace( '#include <begin_vertex>', shaderChange.begin_vertex );
+
+					this.userData.shader = shader;
+
+				}
+
+			}
+
+
+			const shaderChange = {
+
+				heightmap_frag : /* glsl */`
+			    #include <common>
+
+				uniform vec2 mousePos;
+				uniform float mouseSize;
+				uniform float viscosity;
+				uniform float deep;
+
+				void main()	{
+
+					vec2 cellSize = 1.0 / resolution.xy;
+
+					vec2 uv = gl_FragCoord.xy * cellSize;
+
+					// heightmapValue.x == height from previous frame
+					// heightmapValue.y == height from penultimate frame
+					// heightmapValue.z, heightmapValue.w not used
+					vec4 heightmapValue = texture2D( heightmap, uv );
+
+					// Get neighbours
+					vec4 north = texture2D( heightmap, uv + vec2( 0.0, cellSize.y ) );
+					vec4 south = texture2D( heightmap, uv + vec2( 0.0, - cellSize.y ) );
+					vec4 east = texture2D( heightmap, uv + vec2( cellSize.x, 0.0 ) );
+					vec4 west = texture2D( heightmap, uv + vec2( - cellSize.x, 0.0 ) );
+
+					//float newHeight = ( ( north.x + south.x + east.x + west.x ) * 0.5 - heightmapValue.y ) * viscosity;
+					float newHeight = ( ( north.x + south.x + east.x + west.x ) * 0.5 - (heightmapValue.y) ) * viscosity;
+
+
+					// Mouse influence
+					float mousePhase = clamp( length( ( uv - vec2( 0.5 ) ) * BOUNDS - vec2( mousePos.x, - mousePos.y ) ) * PI / mouseSize, 0.0, PI );
+					//newHeight += ( cos( mousePhase ) + 1.0 ) * 0.28 * 10.0;
+					newHeight -= ( cos( mousePhase ) + 1.0 ) * deep;
+
+					heightmapValue.y = heightmapValue.x;
+					heightmapValue.x = newHeight;
+
+					gl_FragColor = heightmapValue;
+
+				}
+			    `,
+			    // FOR MATERIAL
+			    common : /* glsl */`
+				#include <common>
+				uniform sampler2D heightmap;
+				`,
+			    beginnormal_vertex : /* glsl */`
+			    vec2 cellSize = vec2( 1.0 / WIDTH, 1.0 / WIDTH );
+			    vec3 objectNormal = vec3(
+				( texture2D( heightmap, uv + vec2( - cellSize.x, 0 ) ).x - texture2D( heightmap, uv + vec2( cellSize.x, 0 ) ).x ) * WIDTH / BOUNDS,
+				( texture2D( heightmap, uv + vec2( 0, - cellSize.y ) ).x - texture2D( heightmap, uv + vec2( 0, cellSize.y ) ).x ) * WIDTH / BOUNDS,
+				1.0 );
+				#ifdef USE_TANGENT
+					vec3 objectTangent = vec3( tangent.xyz );
+				#endif
+			    `,
+			    begin_vertex : /* glsl */`
+			    float heightValue = texture2D( heightmap, uv ).x;
+			    vec3 transformed = vec3( position.x, position.y, heightValue );
+			    #ifdef USE_ALPHAHASH
+					vPosition = vec3( position );
+				#endif
+			    `,
+			}
+
 		</script>
 	</body>
 </html>

+ 347 - 0
examples/webgl_random_uv.html

@@ -0,0 +1,347 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - GLTFloader</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="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Random UV and Disolve
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let camera, scene, renderer, dirLight, ground, gui, material, materialIn, uniforms, uniformsIn;
+
+			init();
+			render();
+
+			function init() {
+
+				const container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 20 );
+				camera.position.set( - 0.8, 0.6, 1.5 );
+
+				scene = new THREE.Scene();
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 0.7;
+				renderer.shadowMap.enabled = true;
+				renderer.shadowMap.type = THREE.VSMShadowMap;
+				container.appendChild( renderer.domElement );
+
+				dirLight = new THREE.DirectionalLight( 0xFFFFFF, 3 );
+				dirLight.position.set( -0.5, 1, 0.8 );
+				dirLight.castShadow = true;
+				scene.add( dirLight );
+				let shadow = dirLight.shadow;
+				shadow.mapSize.width = shadow.mapSize.height = 1024;
+				shadow.radius = 16;
+				shadow.bias = - 0.0005;
+				let shadowCam = shadow.camera, s = 2;
+				shadowCam.near = 0.5;
+				shadowCam.far = 3;
+				shadowCam.right = shadowCam.top	= s;
+				shadowCam.left = shadowCam.bottom = -s;
+				// debug shadow
+				//scene.add( new THREE.CameraHelper(shadowCam) );
+
+				// add ground plane
+				let plane = new THREE.PlaneGeometry( 2, 2 );
+				plane.rotateX( -Math.PI*0.5 );
+				ground = new THREE.Mesh( plane, new THREE.ShadowMaterial({opacity:0.5}) );
+				ground.receiveShadow = true;
+				ground.position.z = -0.5;
+				scene.add( ground );
+
+
+
+				const map = new THREE.TextureLoader().load( 'textures/jade.jpg' );
+				map.colorSpace = THREE.SRGBColorSpace;
+				map.wrapS = map.wrapT = THREE.RepeatWrapping;
+				map.repeat.set(20,20);
+				map.flipY = false;
+
+				const disolveMap = new THREE.TextureLoader().load( 'textures/shaderball_ds.jpg' );
+				disolveMap.flipY = false;
+
+				const noise = new THREE.TextureLoader().load( 'textures/noise.png' );
+
+				new RGBELoader()
+					.setPath( 'textures/equirectangular/' )
+					.load( 'lobe.hdr', function ( texture ) {
+
+						texture.mapping = THREE.EquirectangularReflectionMapping;
+
+						scene.background = texture;
+						scene.environment = texture;
+						scene.backgroundBlurriness = 0.5;
+						scene.backgroundIntensity = 1.0;
+						scene.environmentIntensity = 1.5;
+
+						render();
+
+						// model
+
+						const loader = new GLTFLoader().setPath( 'models/gltf/' );
+						loader.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) );
+						loader.load( 'ShaderBall2.glb', function ( gltf ) {
+
+							const shaderBall = gltf.scene.children[0];
+
+							// shaderBall is a groop with 3 children : base, inside and logo
+							// ao map is include in model
+
+							let i = shaderBall.children.length, n = 0;
+
+							while(i--){
+								shaderBall.children[i].receiveShadow = true;
+								shaderBall.children[i].castShadow = true;
+								shaderBall.children[i].renderOrder = n++
+							}
+
+							material = shaderBall.children[0].material;
+							material.map = map;
+							material.alphaMap = disolveMap;
+							material.transparent = true;
+
+							materialIn = shaderBall.children[1].material;
+							materialIn.alphaMap = disolveMap;
+							materialIn.transparent = true;
+
+							material.onBeforeCompile = function ( shader ) {
+
+								shader.uniforms['disolve'] = { value: 0 };
+								shader.uniforms['threshold'] = { value: 0.2 };
+
+								shader.uniforms['noiseMap'] = { value: noise };
+								shader.uniforms['enableRandom'] = { value: 1 };
+								shader.uniforms['useNoiseMap'] = { value: 1 };
+								shader.uniforms['useSuslikMethod'] = { value: 0 };
+								shader.uniforms['debugNoise'] = { value: 0 };
+
+								shader.fragmentShader = shader.fragmentShader.replace( '#include <clipping_planes_pars_fragment>', '#include <clipping_planes_pars_fragment>' + randomUV );
+								shader.fragmentShader = shader.fragmentShader.replace( '#include <map_fragment>', mapRemplace );
+
+								// for disolve
+								shader.fragmentShader = shader.fragmentShader.replace( '#include <alphamap_pars_fragment>', alphamap_pars_fragment );
+								shader.fragmentShader = shader.fragmentShader.replace( '#include <alphamap_fragment>', alphamap_fragment );
+
+								uniforms = shader.uniforms;
+
+							}
+
+							materialIn.onBeforeCompile = function ( shader ) {
+
+								shader.uniforms['disolve'] = { value: 0 };
+								shader.uniforms['threshold'] = { value: 0.2 };
+								// for disolve
+								shader.fragmentShader = shader.fragmentShader.replace( '#include <alphamap_pars_fragment>', alphamap_pars_fragment );
+								shader.fragmentShader = shader.fragmentShader.replace( '#include <alphamap_fragment>', alphamap_fragment );
+
+								uniformsIn = shader.uniforms;
+
+							}
+
+							scene.add( shaderBall );
+
+							render();
+
+							createGUI();
+
+						} );
+
+					} );
+
+				;
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render ); // use if there is no animation loop
+				controls.minDistance = 0.3;
+				controls.maxDistance = 10;
+				controls.target.set( 0, 0.4, 0 );
+				controls.update();
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function createGUI() {
+
+				const setting = {
+
+					get 'Enabled'() { return uniforms.enableRandom.value ? true : false; },
+					set 'Enabled'( v ) {  uniforms.enableRandom.value = v ? 1 : 0; render(); },
+
+					get 'UseNoiseMap'() { return uniforms.useNoiseMap.value ? true : false; },
+					set 'UseNoiseMap'( v ) {  uniforms.useNoiseMap.value = v ? 1 : 0; render(); },
+
+					get 'SuslikMethod'() { return uniforms.useSuslikMethod.value ? true : false; },
+					set 'SuslikMethod'( v ) {  uniforms.useSuslikMethod.value = v ? 1 : 0; render(); },
+
+					get 'DebugNoise'() { return uniforms.debugNoise.value ? true : false; },
+					set 'DebugNoise'( v ) {  uniforms.debugNoise.value = v ? 1 : 0; render(); },
+
+					// disolve
+					get 'disolve'() { return uniforms.disolve.value; },
+					set 'disolve'( v ) {  uniforms.disolve.value = v; uniformsIn.disolve.value = v; ground.material.opacity = (1-v)*0.5; render(); },
+
+					get 'threshold'() { return uniforms.threshold.value; },
+					set 'threshold'( v ) {  uniforms.threshold.value = v; uniformsIn.threshold.value = v; render(); }
+
+				};
+
+				gui = new GUI();
+				gui.add( material, 'roughness', 0, 1, 0.01 ).onChange(render);
+				gui.add( material, 'metalness', 0, 1, 0.01 ).onChange(render);
+				gui.add( setting, 'disolve', 0, 1, 0.01 ).onChange(render);
+				gui.add( setting, 'threshold', 0, 1, 0.01 ).onChange(render);
+				gui.add( setting, 'Enabled' );
+				gui.add( setting, 'UseNoiseMap' );
+				gui.add( setting, 'SuslikMethod' );
+				gui.add( setting, 'DebugNoise' );
+			
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			//
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+			const randomUV = /* glsl */ `
+
+			uniform sampler2D noiseMap;
+			uniform float enableRandom;
+			uniform float useNoiseMap;
+			uniform float debugNoise;
+			uniform float useSuslikMethod;
+
+			float directNoise(vec2 p){
+			    vec2 ip = floor(p);
+			    vec2 u = fract(p);
+			    u = u*u*(3.0-2.0*u);
+			    
+			    float res = mix(
+			        mix(rand(ip),rand(ip+vec2(1.0,0.0)),u.x),
+			        mix(rand(ip+vec2(0.0,1.0)),rand(ip+vec2(1.0,1.0)),u.x),u.y);
+			    return res*res;
+			}
+
+			float sum( vec4 v ) { return v.x+v.y+v.z; }
+
+			vec4 textureNoTile( sampler2D mapper, in vec2 uv ){
+
+			    // sample variation pattern
+			    float k = 0.0;
+			    if( useNoiseMap == 1.0 ) k = texture2D( noiseMap, 0.005*uv ).x;
+			    else k = directNoise( uv );
+			    
+			    // compute index    
+			    float index = k*8.0;
+			    float f = fract( index );
+			    float ia = 0.0;
+			    float ib = 0.0;
+
+			    if( useSuslikMethod == 1.0 ){
+			    	ia = floor(index+0.5);
+			    	ib = floor(index);
+			    	f = min(f, 1.0-f)*2.0;
+			    } else {
+			    	ia = floor( index );
+			    	ib = ia + 1.0;
+			    }
+
+			    // offsets for the different virtual patterns    
+			    vec2 offa = sin(vec2(3.0,7.0)*ia); // can replace with any other hash    
+			    vec2 offb = sin(vec2(3.0,7.0)*ib); // can replace with any other hash    
+
+			    // compute derivatives for mip-mapping    
+			    vec2 dx = dFdx(uv);
+			    vec2 dy = dFdy(uv);
+			    
+			    // sample the two closest virtual patterns    
+			    vec4 cola = textureGrad( mapper, uv + offa, dx, dy );
+			    vec4 colb = textureGrad( mapper, uv + offb, dx, dy );
+			    if( debugNoise == 1.0 ){
+			    	cola = vec4( 0.1,0.0,0.0,1.0 );
+			    	colb = vec4( 0.0,0.0,1.0,1.0 );
+			    }
+
+			    // interpolate between the two virtual patterns    
+			    return mix( cola, colb, smoothstep(0.2,0.8,f-0.1*sum(cola-colb)) );
+
+			}`
+
+			const mapRemplace = /* glsl */ `
+			#ifdef USE_MAP
+
+				if( enableRandom == 1.0 ) diffuseColor *= textureNoTile( map, vMapUv );
+				else diffuseColor *= texture2D( map, vMapUv );
+
+			#endif
+			`
+
+			const alphamap_pars_fragment = /* glsl */ `
+			#ifdef USE_ALPHAMAP
+				uniform sampler2D alphaMap;
+				uniform float disolve;
+				uniform float threshold;
+			#endif
+			`;
+
+			const alphamap_fragment = /* glsl */ `
+			#ifdef USE_ALPHAMAP
+			    float vv = texture2D( alphaMap, vAlphaMapUv ).g;
+			    float r = disolve * (1.0 + threshold * 2.0) - threshold;
+			    float mixf = clamp((vv - r)*(1.0/threshold), 0.0, 1.0);
+				diffuseColor.a = mixf;
+			#endif
+			`
+
+		</script>
+
+	</body>
+</html>

+ 343 - 0
examples/webgl_watch.html

@@ -0,0 +1,343 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - GLTFloader</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="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Rolex + aomap - 610 ko
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"tween": "./jsm/libs/tween.module.js" 
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import * as TWEEN from 'tween';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+			import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+			import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
+			import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+			import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
+			import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
+
+			let composer, camera, scene, renderer; 
+			let gui, dirLight, pointLight, controls, bloomPass, fxaaPass;
+			let ready = false;
+
+			const meshs = {};
+			const materials = {};
+			const torad = Math.PI / 180;
+
+			const setting = {
+				roughness:0.09,
+				metalness:1.0,
+				opacity:0.4,
+				threshold: 0,
+				strength: 0.08,
+				radius: 0.0,
+				postProcess:false
+			}
+
+			init();
+			render();
+
+			function init() {
+
+				const container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.1, 20 );
+				camera.position.set(  0.8, 0.5, -1.5 );
+
+				scene = new THREE.Scene();
+
+				renderer = new THREE.WebGLRenderer( { antialias: true, alpha:false } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 0.7;
+				renderer.shadowMap.enabled = true;
+				renderer.shadowMap.type = THREE.VSMShadowMap;
+				container.appendChild( renderer.domElement );
+
+				new RGBELoader()
+					.setPath( 'textures/equirectangular/' )
+					.load( 'lobe.hdr', function ( texture ) {
+
+						texture.mapping = THREE.EquirectangularReflectionMapping;
+						scene.background = texture;
+						scene.environment = texture;
+						scene.backgroundBlurriness = 0.5;
+						scene.backgroundIntensity = 1.0;
+						scene.environmentIntensity = 1.5;
+
+						render();
+
+						// model
+
+						const loader = new GLTFLoader().setPath( 'models/gltf/' );
+						loader.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) );
+						loader.load( 'rolex.glb', function ( gltf ) {
+
+							gltf.scene.rotation.x = Math.PI*0.25
+
+							gltf.scene.traverse( ( child ) => {
+
+								if ( child.isMesh || child.isGroup ){
+									if ( child.isMesh ){ 
+										child.material.vertexColors = false;
+										materials[child.material.name] = child.material;
+										if(child.name!=='glass'){
+											child.receiveShadow = true;
+										    child.castShadow = true;
+										}
+									}
+									meshs[child.name] = child;
+								}
+							})
+
+							scene.add( gltf.scene );
+
+							meshs.glass.material = new THREE.MeshPhysicalMaterial({
+								color:0x020205,
+								transparent:true, opacity:setting.opacity, 
+								metalness:0, roughness:0,
+								iridescence:0.3, 
+								clearcoat:1.0,
+								blending:THREE.AdditiveBlending
+							})
+
+							ready = true;
+
+							createGUI()
+
+						} );
+
+					} );
+
+				;
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 0.3;
+				controls.maxDistance = 10;
+				controls.target.set( 0, -0.1, 0 );
+				controls.enableDamping = true;
+				controls.dampingFactor = 0.05;
+				controls.update();
+
+				dirLight = new THREE.DirectionalLight( 0xFFFFFF, 6 );
+				dirLight.position.set( -0.1, 0.6, 0.4 );
+				dirLight.castShadow = true;
+				scene.add( dirLight );
+				let shadow = dirLight.shadow;
+				shadow.mapSize.width = shadow.mapSize.height = 1024;
+				shadow.radius = 8;
+				shadow.bias = - 0.0005;
+				let shadowCam = shadow.camera, s = 0.5;
+				shadowCam.near = 0.1;
+				shadowCam.far = 2;
+				shadowCam.right = shadowCam.top	= s;
+				shadowCam.left = shadowCam.bottom = -s;
+				// debug shadow
+				//scene.add(  new THREE.CameraHelper(shadowCam) );
+				
+
+				pointLight = new THREE.PointLight( 0x7b8cad, 1, 0, 2 );
+				pointLight.position.set(0.3, -0.2, -0.2);
+				scene.add( pointLight );
+
+				
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				moveCamera();
+
+			}
+
+			function moveCamera(){
+
+				controls.enabled = false;
+				controls.enableDamping = false;
+
+				const sph = new THREE.Spherical();
+				const target = controls.target;
+				const tmp = {
+				    distance:controls.getDistance(),
+				    phi:controls.getPolarAngle(),
+				    theta: controls.getAzimuthalAngle()
+				}
+
+				new TWEEN.Tween( tmp )
+					.to( { distance:1, theta: -Math.PI*0.2 }, 6000 )
+					.easing( TWEEN.Easing.Quadratic.Out )
+					.onUpdate( function( n ) { 
+					   sph.set( n.distance, n.phi, n.theta );
+					   camera.position.setFromSpherical( sph ).add( target )
+					   camera.lookAt( target )
+					})
+					.onComplete( function() { 
+						controls.enabled = true;
+						controls.enableDamping = true;
+					})
+					.start();
+
+			}
+
+			function postProcess ( b ) {
+
+				if(b){
+
+					if( composer ) return;
+
+					const renderPass = new RenderPass( scene, camera );
+					const outputPass = new OutputPass();
+
+					bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 1.5, 0.4, 0.85 );
+					bloomPass.threshold = setting.threshold;
+					bloomPass.strength = setting.strength;
+					bloomPass.radius = setting.radius;
+					
+					fxaaPass = new ShaderPass( FXAAShader );
+					const pixelRatio = renderer.getPixelRatio();
+					fxaaPass.material.uniforms[ 'resolution' ].value.set( 1 / ( window.innerWidth * pixelRatio ), 1 / ( window.innerHeight * pixelRatio ) );
+
+					composer = new EffectComposer( renderer );
+					composer.setPixelRatio( pixelRatio );
+					
+					composer.addPass( renderPass );
+					composer.addPass( bloomPass );
+					composer.addPass( fxaaPass );
+					composer.addPass( outputPass );
+					composer.addPass( fxaaPass );
+					composer.addPass( fxaaPass );
+
+				} else {
+
+					if(!composer) return;
+					composer.dispose();
+					composer = null;
+					bloomPass = null;
+					fxaaPass = null;
+
+				}
+
+			}
+
+			function createGUI() {
+
+				gui = new GUI();
+				gui.add( setting, 'roughness', 0, 1, 0.01 ).onChange(upMaterial);
+				gui.add( setting, 'metalness', 0, 1, 0.01 ).onChange(upMaterial);
+				gui.add( setting, 'opacity', 0, 1, 0.01 ).onChange(upMaterial);
+
+				//
+
+				gui.add( setting, 'postProcess' ).onChange(postProcess);
+				gui.add( setting, 'threshold', 0, 1, 0.01 ).onChange(upBloom);
+				gui.add( setting, 'strength', 0, 3, 0.01 ).onChange(upBloom);
+				gui.add( setting, 'radius', 0, 1, 0.01 ).onChange(upBloom);
+				
+			}
+
+			function upMaterial() {
+
+				materials.Gold.metalness = materials.Silver.metalness = setting.metalness;
+				materials.Gold.roughness = materials.Silver.roughness = setting.roughness;
+				meshs.glass.material.opacity = setting.opacity;
+			
+			}
+
+			function upBloom() {
+
+				if( !bloomPass ) return;
+				bloomPass.threshold = setting.threshold;
+				bloomPass.strength = setting.strength;
+				bloomPass.radius = setting.radius;
+
+			}
+
+			function getTime() {
+
+				const currentDate = new Date();
+				let hour = currentDate.getHours();
+				let minute = currentDate.getMinutes();
+				let second = currentDate.getSeconds();
+				let day = currentDate.getDay();
+				let mounth = currentDate.getMonth();
+				let mili = currentDate.getMilliseconds()
+				if(hour >= 12 ) hour-=12
+				if(day > 30 ) day=30
+
+				meshs.hour.rotation.y = -hour*30 * torad
+				meshs.minute.rotation.y = -minute * 6 * torad;
+				meshs.second.rotation.y = -second * 6 * torad;
+				meshs.mini_03.rotation.y = -day * 12 * torad;
+				meshs.mini_02.rotation.y = -mounth * 30 * torad;
+				meshs.mini_01.rotation.y = -mili * 0.36 * torad;
+
+			}
+
+			function onWindowResize() {
+
+				const width = window.innerWidth;
+				const height = window.innerHeight;
+
+				camera.aspect = width / height;
+				camera.updateProjectionMatrix();
+				renderer.setSize( width, height );
+				if( composer ){ 
+					composer.setSize( width, height );
+					if(fxaaPass){
+						const pr = renderer.getPixelRatio();
+						fxaaPass.material.uniforms[ 'resolution' ].value.set( 1 / ( width * pr ), 1 / ( height * pr ) );
+					}
+					
+				}
+
+			}
+
+			//
+
+			function render() {
+
+				requestAnimationFrame( render );
+				controls.update();
+
+				TWEEN.update();
+				
+				if( composer ) composer.render();
+				else renderer.render( scene, camera );
+
+				if(ready) getTime()
+
+			}
+
+		</script>
+
+	</body>
+</html>

粤ICP备19079148号