In the case of PBR materials, such as MeshStandardMaterial and MeshPhysicalMaterial, we have the choice of setting the scene environment map, rather than placing multiple lights.
An environment map will take slightly longer to initially render since it is usually created from an image, or multiple images, that you've downloaded. Where-as lights can be easily created and added to your scene when you set it up.
The problem with adding lights though, is that it uses more processing power needed during the render of each frame.
An environment map will be rendered much faster than using several lights each frame, and also produce a much more realistic effect.
In this example, we can toggle the environment map and lighting on and off.
Note that Environment maps don't cast or receive shadows. You can however implement a light that casts shadows along with using an environment map.
The best quality environment maps are created using HDR images. However, the file sizes of HDR images are quite large compared to JPG or PNG.
So, we can also compress HDR images, using an online tool in case it will benefit your application.
Note
Three r163 added new property scene.environmentIntensity. Affects PBR materials with material.envMap = null.
Changing a material.envMapIntensity will now only work if you have set a fully loaded texture to the material.envMap property first.
Note that after setting material.envMap = someFullyLoadedTexture, it will no longer be affected by changes to scene.environmentIntensity.
Lesson Scripts
./index.html
1 2 3 4 5 6 7 8 9101112
<!DOCTYPE html><htmllang="en"><head><metacharset="utf-8"/><metaname="viewport"content="width=device-width, initial-scale=1.0"/><title>Three.js TypeScript Tutorials by Sean Bradley : https://sbcode.net/threejs</title></head><body><scripttype="module"src="/src/main.ts"></script></body></html>
import'./style.css'import*asTHREEfrom'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'importStatsfrom'three/addons/libs/stats.module.js'import{GUI}from'three/addons/libs/lil-gui.module.min.js'constscene=newTHREE.Scene()constenvironmentTexture=newTHREE.CubeTextureLoader().setPath('https://sbcode.net/img/').load(['px.png','nx.png','py.png','ny.png','pz.png','nz.png'])scene.environment=environmentTexturescene.background=environmentTexture//const hdr = 'https://sbcode.net/img/rustig_koppie_puresky_1k.hdr'// //const hdr = 'https://sbcode.net/img/venice_sunset_1k.hdr'// //const hdr = 'https://sbcode.net/img/spruit_sunrise_1k.hdr'// let environmentTexture: THREE.DataTexture// new RGBELoader().load(hdr, (texture) => {// environmentTexture = texture// environmentTexture.mapping = THREE.EquirectangularReflectionMapping// scene.environment = environmentTexture// scene.background = environmentTexture// scene.environmentIntensity = 1 // added in Three r163// })constdirectionallight=newTHREE.DirectionalLight(0xebfeff,Math.PI)directionallight.position.set(1,0.1,1)directionallight.visible=falsescene.add(directionallight)constambientLight=newTHREE.AmbientLight(0xebfeff,Math.PI/16)ambientLight.visible=falsescene.add(ambientLight)constcamera=newTHREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,100)camera.position.set(-2,0.5,2)constrenderer=newTHREE.WebGLRenderer({antialias:true})renderer.toneMapping=THREE.ACESFilmicToneMappingrenderer.setSize(window.innerWidth,window.innerHeight)document.body.appendChild(renderer.domElement)window.addEventListener('resize',()=>{camera.aspect=window.innerWidth/window.innerHeightcamera.updateProjectionMatrix()renderer.setSize(window.innerWidth,window.innerHeight)})constcontrols=newOrbitControls(camera,renderer.domElement)controls.enableDamping=trueconsttexture=newTHREE.TextureLoader().load('https://sbcode.net/img/grid.png')texture.colorSpace=THREE.SRGBColorSpaceconstmaterial=newTHREE.MeshPhysicalMaterial()material.side=THREE.DoubleSide// material.envMapIntensity = 0.7// material.roughness = 0.17// material.metalness = 0.07// material.clearcoat = 0.43// material.iridescence = 1// material.transmission = 1// material.thickness = 5.12// material.ior = 1.78constplane=newTHREE.Mesh(newTHREE.PlaneGeometry(10,10),material)plane.rotation.x=-Math.PI/2plane.position.y=-1plane.visible=falsescene.add(plane)newGLTFLoader().load('https://sbcode.net/models/suzanne_no_material.glb',(gltf)=>{gltf.scene.traverse((child)=>{;(childasTHREE.Mesh).material=material})scene.add(gltf.scene)})constdata={environment:true,background:true,mapEnabled:false,planeVisible:false}constgui=newGUI()gui.add(data,'environment').onChange(()=>{if(data.environment){scene.environment=environmentTexturedirectionallight.visible=falseambientLight.visible=false}else{scene.environment=nulldirectionallight.visible=trueambientLight.visible=true}})gui.add(scene,'environmentIntensity',0,2,0.01)// new in Three r163. Can be used instead of `renderer.toneMapping` with `renderer.toneMappingExposure`gui.add(renderer,'toneMappingExposure',0,2,0.01)gui.add(data,'background').onChange(()=>{if(data.background){scene.background=environmentTexture}else{scene.background=null}})gui.add(scene,'backgroundBlurriness',0,1,0.01)gui.add(data,'mapEnabled').onChange(()=>{if(data.mapEnabled){material.map=texture}else{material.map=null}material.needsUpdate=true})gui.add(data,'planeVisible').onChange((v)=>{plane.visible=v})constmaterialFolder=gui.addFolder('meshPhysicalMaterial')materialFolder.add(material,'envMapIntensity',0,1.0,0.01).onChange(()=>{// Since r163, `envMap` is no longer copied from `scene.environment`. You will need to manually copy it, if you want to modify `envMapIntensity`if(!material.envMap){material.envMap=scene.environment}})// from meshStandardMaterialmaterialFolder.add(material,'roughness',0,1.0,0.01)// from meshStandardMaterialmaterialFolder.add(material,'metalness',0,1.0,0.01)// from meshStandardMaterialmaterialFolder.add(material,'clearcoat',0,1.0,0.01)materialFolder.add(material,'iridescence',0,1.0,0.01)materialFolder.add(material,'transmission',0,1.0,0.01)materialFolder.add(material,'thickness',0,10.0,0.01)materialFolder.add(material,'ior',1.0,2.333,0.01)materialFolder.close()conststats=newStats()document.body.appendChild(stats.dom)functionanimate(){requestAnimationFrame(animate)controls.update()renderer.render(scene,camera)stats.update()}animate()
HDR Compression
HDR Images are usually very large to download over the internet. We can now use HDR images as a logarithmic gain map encoded with the JPEG algorithm. We can use a third party library to load them.
Example compression results for the venice_sunset.hdr.
If you only need an environment map in your scene, then using the smallest HDRJPG may be good enough. But if you also want to show it as the background, and you want the background to look crisp, then the 4k HDRJPG will be better. But it will take longer to download across the internet.
Some working examples linked on this website will use the HDRJPGLoader in place of the RGBELoader.