<!DOCTYPE html> <html lang="en"> <head> <title>three.js webgl - lights - physical lights</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> - Physically accurate incandescent bulb by <a href="http://clara.io" target="_blank" rel="noopener">Ben Houston</a><br /> Real world scale: Brick cube is 50 cm in size. Globe is 50 cm in diameter.<br/> Reinhard inline tonemapping with real-world light falloff (decay = 2). </div> <!-- 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/": "./jsm/" } } </script> <script type="module"> import * as THREE from 'three'; 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'; let camera, scene, renderer, bulbLight, bulbMat, hemiLight, stats; let ballMat, cubeMat, floorMat; let previousShadowMap = false; // ref for lumens: http://www.power-sure.com/lumens.htm const bulbLuminousPowers = { '110000 lm (1000W)': 110000, '3500 lm (300W)': 3500, '1700 lm (100W)': 1700, '800 lm (60W)': 800, '400 lm (40W)': 400, '180 lm (25W)': 180, '20 lm (4W)': 20, 'Off': 0 }; // ref for solar irradiances: https://en.wikipedia.org/wiki/Lux const hemiLuminousIrradiances = { '0.0001 lx (Moonless Night)': 0.0001, '0.002 lx (Night Airglow)': 0.002, '0.5 lx (Full Moon)': 0.5, '3.4 lx (City Twilight)': 3.4, '50 lx (Living Room)': 50, '100 lx (Very Overcast)': 100, '350 lx (Office Room)': 350, '400 lx (Sunrise/Sunset)': 400, '1000 lx (Overcast)': 1000, '18000 lx (Daylight)': 18000, '50000 lx (Direct Sun)': 50000 }; const params = { shadows: true, exposure: 0.68, bulbPower: Object.keys( bulbLuminousPowers )[ 4 ], hemiIrradiance: Object.keys( hemiLuminousIrradiances )[ 0 ] }; init(); animate(); function init() { const container = document.getElementById( 'container' ); stats = new Stats(); container.appendChild( stats.dom ); camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 100 ); camera.position.x = - 4; camera.position.z = 4; camera.position.y = 2; scene = new THREE.Scene(); const bulbGeometry = new THREE.SphereGeometry( 0.02, 16, 8 ); bulbLight = new THREE.PointLight( 0xffee88, 1, 100, 2 ); bulbMat = new THREE.MeshStandardMaterial( { emissive: 0xffffee, emissiveIntensity: 1, color: 0x000000 } ); bulbLight.add( new THREE.Mesh( bulbGeometry, bulbMat ) ); bulbLight.position.set( 0, 2, 0 ); bulbLight.castShadow = true; scene.add( bulbLight ); hemiLight = new THREE.HemisphereLight( 0xddeeff, 0x0f0e0d, 0.02 ); scene.add( hemiLight ); floorMat = new THREE.MeshStandardMaterial( { roughness: 0.8, color: 0xffffff, metalness: 0.2, bumpScale: 0.0005 } ); const textureLoader = new THREE.TextureLoader(); textureLoader.load( 'textures/hardwood2_diffuse.jpg', function ( map ) { map.wrapS = THREE.RepeatWrapping; map.wrapT = THREE.RepeatWrapping; map.anisotropy = 4; map.repeat.set( 10, 24 ); map.encoding = THREE.sRGBEncoding; floorMat.map = map; floorMat.needsUpdate = true; } ); textureLoader.load( 'textures/hardwood2_bump.jpg', function ( map ) { map.wrapS = THREE.RepeatWrapping; map.wrapT = THREE.RepeatWrapping; map.anisotropy = 4; map.repeat.set( 10, 24 ); floorMat.bumpMap = map; floorMat.needsUpdate = true; } ); textureLoader.load( 'textures/hardwood2_roughness.jpg', function ( map ) { map.wrapS = THREE.RepeatWrapping; map.wrapT = THREE.RepeatWrapping; map.anisotropy = 4; map.repeat.set( 10, 24 ); floorMat.roughnessMap = map; floorMat.needsUpdate = true; } ); cubeMat = new THREE.MeshStandardMaterial( { roughness: 0.7, color: 0xffffff, bumpScale: 0.002, metalness: 0.2 } ); textureLoader.load( 'textures/brick_diffuse.jpg', function ( map ) { map.wrapS = THREE.RepeatWrapping; map.wrapT = THREE.RepeatWrapping; map.anisotropy = 4; map.repeat.set( 1, 1 ); map.encoding = THREE.sRGBEncoding; cubeMat.map = map; cubeMat.needsUpdate = true; } ); textureLoader.load( 'textures/brick_bump.jpg', function ( map ) { map.wrapS = THREE.RepeatWrapping; map.wrapT = THREE.RepeatWrapping; map.anisotropy = 4; map.repeat.set( 1, 1 ); cubeMat.bumpMap = map; cubeMat.needsUpdate = true; } ); ballMat = new THREE.MeshStandardMaterial( { color: 0xffffff, roughness: 0.5, metalness: 1.0 } ); textureLoader.load( 'textures/planets/earth_atmos_2048.jpg', function ( map ) { map.anisotropy = 4; map.encoding = THREE.sRGBEncoding; ballMat.map = map; ballMat.needsUpdate = true; } ); textureLoader.load( 'textures/planets/earth_specular_2048.jpg', function ( map ) { map.anisotropy = 4; map.encoding = THREE.sRGBEncoding; ballMat.metalnessMap = map; ballMat.needsUpdate = true; } ); const floorGeometry = new THREE.PlaneGeometry( 20, 20 ); const floorMesh = new THREE.Mesh( floorGeometry, floorMat ); floorMesh.receiveShadow = true; floorMesh.rotation.x = - Math.PI / 2.0; scene.add( floorMesh ); const ballGeometry = new THREE.SphereGeometry( 0.25, 32, 32 ); const ballMesh = new THREE.Mesh( ballGeometry, ballMat ); ballMesh.position.set( 1, 0.25, 1 ); ballMesh.rotation.y = Math.PI; ballMesh.castShadow = true; scene.add( ballMesh ); const boxGeometry = new THREE.BoxGeometry( 0.5, 0.5, 0.5 ); const boxMesh = new THREE.Mesh( boxGeometry, cubeMat ); boxMesh.position.set( - 0.5, 0.25, - 1 ); boxMesh.castShadow = true; scene.add( boxMesh ); const boxMesh2 = new THREE.Mesh( boxGeometry, cubeMat ); boxMesh2.position.set( 0, 0.25, - 5 ); boxMesh2.castShadow = true; scene.add( boxMesh2 ); const boxMesh3 = new THREE.Mesh( boxGeometry, cubeMat ); boxMesh3.position.set( 7, 0.25, 0 ); boxMesh3.castShadow = true; scene.add( boxMesh3 ); renderer = new THREE.WebGLRenderer(); renderer.physicallyCorrectLights = true; renderer.outputEncoding = THREE.sRGBEncoding; renderer.shadowMap.enabled = true; renderer.toneMapping = THREE.ReinhardToneMapping; renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); container.appendChild( renderer.domElement ); const controls = new OrbitControls( camera, renderer.domElement ); controls.minDistance = 1; controls.maxDistance = 20; window.addEventListener( 'resize', onWindowResize ); const gui = new GUI(); gui.add( params, 'hemiIrradiance', Object.keys( hemiLuminousIrradiances ) ); gui.add( params, 'bulbPower', Object.keys( bulbLuminousPowers ) ); gui.add( params, 'exposure', 0, 1 ); gui.add( params, 'shadows' ); gui.open(); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } // function animate() { requestAnimationFrame( animate ); render(); } function render() { renderer.toneMappingExposure = Math.pow( params.exposure, 5.0 ); // to allow for very bright scenes. renderer.shadowMap.enabled = params.shadows; bulbLight.castShadow = params.shadows; if ( params.shadows !== previousShadowMap ) { ballMat.needsUpdate = true; cubeMat.needsUpdate = true; floorMat.needsUpdate = true; previousShadowMap = params.shadows; } bulbLight.power = bulbLuminousPowers[ params.bulbPower ]; bulbMat.emissiveIntensity = bulbLight.intensity / Math.pow( 0.02, 2.0 ); // convert from intensity to irradiance at bulb surface hemiLight.intensity = hemiLuminousIrradiances[ params.hemiIrradiance ]; const time = Date.now() * 0.0005; bulbLight.position.y = Math.cos( time ) * 0.75 + 1.25; renderer.render( scene, camera ); stats.update(); } </script> </body> </html>