You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					237 lines
				
				6.8 KiB
			
		
		
			
		
	
	
					237 lines
				
				6.8 KiB
			| 
											3 years ago
										 | <!-- Licensed under a BSD license. See license.html for license --> | ||
|  | <!DOCTYPE html> | ||
|  | <html> | ||
|  |   <head> | ||
|  |     <meta charset="utf-8"> | ||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> | ||
|  |     <title>Three.js - Shadows - Spot Light w/CameraHelper</title> | ||
|  |     <style> | ||
|  |     html, body { | ||
|  |         margin: 0; | ||
|  |         height: 100%; | ||
|  |     } | ||
|  |     #c { | ||
|  |         width: 100%; | ||
|  |         height: 100%; | ||
|  |         display: block; | ||
|  |     } | ||
|  |     </style> | ||
|  |   </head> | ||
|  |   <body> | ||
|  |     <canvas id="c"></canvas> | ||
|  |   </body> | ||
|  | <!-- Import maps polyfill --> | ||
|  | <!-- Remove this when import maps will be widely supported --> | ||
|  | <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script> | ||
|  | 
 | ||
|  | <script type="importmap"> | ||
|  | { | ||
|  |   "imports": { | ||
|  |     "three": "../../build/three.module.js", | ||
|  |     "three/addons/": "../../examples/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'; | ||
|  | 
 | ||
|  | function main() { | ||
|  |   const canvas = document.querySelector('#c'); | ||
|  |   const renderer = new THREE.WebGLRenderer({canvas}); | ||
|  |   renderer.shadowMap.enabled = true; | ||
|  | 
 | ||
|  |   const fov = 45; | ||
|  |   const aspect = 2;  // the canvas default | ||
|  |   const near = 0.1; | ||
|  |   const far = 100; | ||
|  |   const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); | ||
|  |   camera.position.set(0, 10, 20); | ||
|  | 
 | ||
|  |   const controls = new OrbitControls(camera, canvas); | ||
|  |   controls.target.set(0, 5, 0); | ||
|  |   controls.update(); | ||
|  | 
 | ||
|  |   const scene = new THREE.Scene(); | ||
|  |   scene.background = new THREE.Color('black'); | ||
|  | 
 | ||
|  |   { | ||
|  |     const planeSize = 40; | ||
|  | 
 | ||
|  |     const loader = new THREE.TextureLoader(); | ||
|  |     const texture = loader.load('resources/images/checker.png'); | ||
|  |     texture.wrapS = THREE.RepeatWrapping; | ||
|  |     texture.wrapT = THREE.RepeatWrapping; | ||
|  |     texture.magFilter = THREE.NearestFilter; | ||
|  |     const repeats = planeSize / 2; | ||
|  |     texture.repeat.set(repeats, repeats); | ||
|  | 
 | ||
|  |     const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize); | ||
|  |     const planeMat = new THREE.MeshPhongMaterial({ | ||
|  |       map: texture, | ||
|  |       side: THREE.DoubleSide, | ||
|  |     }); | ||
|  |     const mesh = new THREE.Mesh(planeGeo, planeMat); | ||
|  |     mesh.receiveShadow = true; | ||
|  |     mesh.rotation.x = Math.PI * -.5; | ||
|  |     scene.add(mesh); | ||
|  |   } | ||
|  |   { | ||
|  |     const cubeSize = 4; | ||
|  |     const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize); | ||
|  |     const cubeMat = new THREE.MeshPhongMaterial({color: '#8AC'}); | ||
|  |     const mesh = new THREE.Mesh(cubeGeo, cubeMat); | ||
|  |     mesh.castShadow = true; | ||
|  |     mesh.receiveShadow = true; | ||
|  |     mesh.position.set(cubeSize + 1, cubeSize / 2, 0); | ||
|  |     scene.add(mesh); | ||
|  |   } | ||
|  |   { | ||
|  |     const sphereRadius = 3; | ||
|  |     const sphereWidthDivisions = 32; | ||
|  |     const sphereHeightDivisions = 16; | ||
|  |     const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions); | ||
|  |     const sphereMat = new THREE.MeshPhongMaterial({color: '#CA8'}); | ||
|  |     const mesh = new THREE.Mesh(sphereGeo, sphereMat); | ||
|  |     mesh.castShadow = true; | ||
|  |     mesh.receiveShadow = true; | ||
|  |     mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0); | ||
|  |     scene.add(mesh); | ||
|  |   } | ||
|  | 
 | ||
|  |   class ColorGUIHelper { | ||
|  |     constructor(object, prop) { | ||
|  |       this.object = object; | ||
|  |       this.prop = prop; | ||
|  |     } | ||
|  |     get value() { | ||
|  |       return `#${this.object[this.prop].getHexString()}`; | ||
|  |     } | ||
|  |     set value(hexString) { | ||
|  |       this.object[this.prop].set(hexString); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   function makeXYZGUI(gui, vector3, name, onChangeFn) { | ||
|  |     const folder = gui.addFolder(name); | ||
|  |     folder.add(vector3, 'x', -10, 10).onChange(onChangeFn); | ||
|  |     folder.add(vector3, 'y', 0, 10).onChange(onChangeFn); | ||
|  |     folder.add(vector3, 'z', -10, 10).onChange(onChangeFn); | ||
|  |     // folder.open(); | ||
|  |   } | ||
|  | 
 | ||
|  |   { | ||
|  |     const color = 0xFFFFFF; | ||
|  |     const intensity = 1; | ||
|  |     const light = new THREE.SpotLight(color, intensity); | ||
|  |     light.castShadow = true; | ||
|  |     light.position.set(0, 10, 0); | ||
|  |     light.target.position.set(-4, 0, -4); | ||
|  |     scene.add(light); | ||
|  |     scene.add(light.target); | ||
|  | 
 | ||
|  |     const cameraHelper = new THREE.CameraHelper(light.shadow.camera); | ||
|  |     scene.add(cameraHelper); | ||
|  | 
 | ||
|  |     function updateCamera() { | ||
|  |       // update the light target's matrixWorld because it's needed by the helper | ||
|  |       light.target.updateMatrixWorld(); | ||
|  |       // update the light's shadow camera's projection matrix | ||
|  |       light.shadow.camera.updateProjectionMatrix(); | ||
|  |       // and now update the camera helper we're using to show the light's shadow camera | ||
|  |       cameraHelper.update(); | ||
|  |     } | ||
|  |     updateCamera(); | ||
|  |     setTimeout(updateCamera); | ||
|  | 
 | ||
|  |     class MinMaxGUIHelper { | ||
|  |       constructor(obj, minProp, maxProp, minDif) { | ||
|  |         this.obj = obj; | ||
|  |         this.minProp = minProp; | ||
|  |         this.maxProp = maxProp; | ||
|  |         this.minDif = minDif; | ||
|  |       } | ||
|  |       get min() { | ||
|  |         return this.obj[this.minProp]; | ||
|  |       } | ||
|  |       set min(v) { | ||
|  |         this.obj[this.minProp] = v; | ||
|  |         this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif); | ||
|  |       } | ||
|  |       get max() { | ||
|  |         return this.obj[this.maxProp]; | ||
|  |       } | ||
|  |       set max(v) { | ||
|  |         this.obj[this.maxProp] = v; | ||
|  |         this.min = this.min;  // this will call the min setter | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |   class DegRadHelper { | ||
|  |     constructor(obj, prop) { | ||
|  |       this.obj = obj; | ||
|  |       this.prop = prop; | ||
|  |     } | ||
|  |     get value() { | ||
|  |       return THREE.MathUtils.radToDeg(this.obj[this.prop]); | ||
|  |     } | ||
|  |     set value(v) { | ||
|  |       this.obj[this.prop] = THREE.MathUtils.degToRad(v); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |     const gui = new GUI(); | ||
|  |     gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color'); | ||
|  |     gui.add(light, 'intensity', 0, 2, 0.01); | ||
|  |     gui.add(light, 'distance', 0, 40).onChange(updateCamera); | ||
|  |     gui.add(new DegRadHelper(light, 'angle'), 'value', 0, 90).name('angle').onChange(updateCamera); | ||
|  |     gui.add(light, 'penumbra', 0, 1, 0.01); | ||
|  | 
 | ||
|  |     { | ||
|  |       const folder = gui.addFolder('Shadow Camera'); | ||
|  |       folder.open(); | ||
|  |       const minMaxGUIHelper = new MinMaxGUIHelper(light.shadow.camera, 'near', 'far', 0.1); | ||
|  |       folder.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera); | ||
|  |       folder.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far').onChange(updateCamera); | ||
|  |     } | ||
|  | 
 | ||
|  |     makeXYZGUI(gui, light.position, 'position', updateCamera); | ||
|  |     makeXYZGUI(gui, light.target.position, 'target', updateCamera); | ||
|  |   } | ||
|  | 
 | ||
|  |   function resizeRendererToDisplaySize(renderer) { | ||
|  |     const canvas = renderer.domElement; | ||
|  |     const width = canvas.clientWidth; | ||
|  |     const height = canvas.clientHeight; | ||
|  |     const needResize = canvas.width !== width || canvas.height !== height; | ||
|  |     if (needResize) { | ||
|  |       renderer.setSize(width, height, false); | ||
|  |     } | ||
|  |     return needResize; | ||
|  |   } | ||
|  | 
 | ||
|  |   function render() { | ||
|  | 
 | ||
|  |     resizeRendererToDisplaySize(renderer); | ||
|  | 
 | ||
|  |     { | ||
|  |       const canvas = renderer.domElement; | ||
|  |       camera.aspect = canvas.clientWidth / canvas.clientHeight; | ||
|  |       camera.updateProjectionMatrix(); | ||
|  |     } | ||
|  | 
 | ||
|  |     renderer.render(scene, camera); | ||
|  | 
 | ||
|  |     requestAnimationFrame(render); | ||
|  |   } | ||
|  | 
 | ||
|  |   requestAnimationFrame(render); | ||
|  | } | ||
|  | 
 | ||
|  | main(); | ||
|  | </script> | ||
|  | </html> | ||
|  | 
 |