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.
		
		
		
		
		
			
		
			
				
					
					
						
							236 lines
						
					
					
						
							6.8 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							236 lines
						
					
					
						
							6.8 KiB
						
					
					
				| <!-- 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> | |
| 
 | |
| 
 |