Last active
January 19, 2026 11:04
-
-
Save greggman/7413449cf7b4b4db09a2373d0aad5bee to your computer and use it in GitHub Desktop.
Three.js - Shadows - Spot Light w/CameraHelper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| html, body { | |
| margin: 0; | |
| height: 100%; | |
| } | |
| #c { | |
| width: 100%; | |
| height: 100%; | |
| display: block; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <canvas id="c"></canvas> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://raw.githack.com/mrdoob/three.js/dev/build/three.webgpu.js", | |
| "three/webgpu": "https://raw.githack.com/mrdoob/three.js/dev/three.webgpu.js", | |
| "three/tsl": "https://raw.githack.com/mrdoob/three.js/dev/build/three.tsl.js", | |
| "three/addons/": "https://raw.githack.com/mrdoob/three.js/dev/examples/jsm/" | |
| } | |
| } | |
| </script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Three.js - Shadows - Spot Light w/CameraHelper | |
| // from https://threejs.org/manual/examples/shadows-point-light.html | |
| import * as THREE from 'three'; | |
| import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; | |
| import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; | |
| GPUDevice.prototype.createShaderModule = (function(origFn) { | |
| return function(desc) { | |
| const {code} = desc; | |
| // nodeVar3 = dot( normalView, nodeVar2 ); | |
| // nodeVar42 = clamp( dot( positionViewDirection, nodeVar41 ), 0.0, 1.0 ); | |
| // nodeVar57 = dot( normalView, positionViewDirection ); | |
| // nodeVar77 = dot( normalView, positionViewDirection ); | |
| // nodeVar113 = dot( normalView, positionViewDirection ); | |
| const newCode = code.replace( | |
| 'output.color = nodeVar128;', | |
| 'output.color = nodeVar128 + vec4f(0,0,0,0);' | |
| ) | |
| .replace('nodeVar3 = dot( normalView, nodeVar2 );', | |
| 'nodeVar3 = 1.0;') | |
| .replace('nodeVar42 = clamp( dot( positionViewDirection, nodeVar41 ), 0.0, 1.0 );', | |
| 'nodeVar42 = 1.0;') | |
| .replace('nodeVar57 = dot( normalView, positionViewDirection );', | |
| 'nodeVar57 = 1.0;') | |
| .replace('nodeVar77 = dot( normalView, positionViewDirection );', | |
| 'nodeVar77 = 1.0;') | |
| .replace('nodeVar113 = dot( normalView, positionViewDirection );', | |
| 'nodeVar113 = 1.0;', | |
| ); | |
| if (code !== newCode) { | |
| console.log(code); | |
| } | |
| const result = origFn.call(this, {code: newCode}); | |
| return result; | |
| }; | |
| })(GPUDevice.prototype.createShaderModule); | |
| async function main() { | |
| const canvas = document.querySelector( '#c' ); | |
| const renderer = new THREE.WebGPURenderer( { 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( 'https://threejs.org/manual/examples/resources/images/checker.png' ); | |
| texture.wrapS = THREE.RepeatWrapping; | |
| texture.wrapT = THREE.RepeatWrapping; | |
| texture.magFilter = THREE.NearestFilter; | |
| texture.colorSpace = THREE.SRGBColorSpace; | |
| const repeats = planeSize / 2; | |
| texture.repeat.set( repeats, repeats ); | |
| const planeGeo = new THREE.PlaneGeometry( planeSize, planeSize ); | |
| const planeMat = new THREE.MeshStandardMaterial( { | |
| 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.MeshStandardMaterial( { 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 cubeSize = 30; | |
| const cubeGeo = new THREE.BoxGeometry( cubeSize, cubeSize, cubeSize ); | |
| const cubeMat = new THREE.MeshStandardMaterial( { | |
| color: '#CCC', | |
| side: THREE.BackSide, | |
| } ); | |
| const mesh = new THREE.Mesh( cubeGeo, cubeMat ); | |
| mesh.receiveShadow = true; | |
| mesh.position.set( 0, cubeSize / 2 - 0.1, 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.MeshStandardMaterial( { 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 = 100; | |
| const light = new THREE.PointLight( color, intensity ); | |
| light.castShadow = true; | |
| light.position.set( 0, 10, 0 ); | |
| scene.add( light ); | |
| const helper = new THREE.PointLightHelper( light ); | |
| scene.add( helper ); | |
| function 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 | |
| } | |
| } | |
| const gui = new GUI(); | |
| gui.addColor( new ColorGUIHelper( light, 'color' ), 'value' ).name( 'color' ); | |
| gui.add( light, 'intensity', 0, 200 ); | |
| // gui.add( light, 'distance', 0, 40 ).onChange( updateCamera ); | |
| { | |
| 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 ); | |
| } | |
| 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 ); | |
| } | |
| await renderer.init(); | |
| requestAnimationFrame( render ); | |
| } | |
| main(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {"name":"Three.js - Shadows - Spot Light w/CameraHelper","settings":{},"filenames":["index.html","index.css","index.js"]} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment