Lights manage their own camera frustums when they are set with castShadows=true.
The PointLight and SpotLight will calculate there shadows using a PerspectiveCamera frustum. The defaults are,
Property
Value
fov
90
aspect
1
near
0.5
far
500
The DirectionalLight shadow will be calculated using an OrthographicCamera frustum. This is because light rays from a DirectionalLight are parallel. The defaults are,
Property
Value
left
-5
right
5
top
5
bottom
-5
near
0.5
far
500
When using shadows, there are several extra considerations when creating your scene.
You need to enable the shadowMap property in your renderer.
Your scene meshes will need either or both the castShadow and receiveShadow properties set to true.
E.g.,
box.castShadow=trueground.receiveShadow=true
Note that when a mesh has both its castShadow and receiveShadow properties set to true, it can cause artefacts or black lines to appear on the surface. This can be adjusted using the shadows bias property. Experiment with small amounts such as 0.1, -0.0001, 0.002. The default is 0.
Lesson Scripts
./index.html
1 2 3 4 5 6 7 8 910111213141516
<!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><divclass="label">MeshBasicMaterial</div><divclass="label">MeshNormalMaterial</div><divclass="label">MeshPhongMaterial</div><divclass="label">MeshStandardMaterial</div><scripttype="module"src="/src/main.ts"></script></body></html>
import'./style.css'import*asTHREEfrom'three'import{OrbitControls}from'three/addons/controls/OrbitControls.js'importStatsfrom'three/addons/libs/stats.module.js'import{GUI}from'three/addons/libs/lil-gui.module.min.js'constscene=newTHREE.Scene()scene.add(newTHREE.GridHelper())constcamera=newTHREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,100)camera.position.set(-1,4,2.5)constrenderer=newTHREE.WebGLRenderer({antialias:true})renderer.shadowMap.type=THREE.PCFShadowMap// (default)//renderer.shadowMap.type = THREE.PCFSoftShadowMap//renderer.shadowMap.type = THREE.BasicShadowMap//renderer.shadowMap.type = THREE.VSMShadowMaprenderer.shadowMap.enabled=truerenderer.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=trueconstplane=newTHREE.Mesh(newTHREE.PlaneGeometry(100,100),newTHREE.MeshStandardMaterial({color:0xffffff}))plane.rotation.x=-Math.PI/2plane.receiveShadow=trueplane.castShadow=truescene.add(plane)constdata={color:0x00ff00,lightColor:0xffffff,shadowMapSizeWidth:512,shadowMapSizeHeight:512,}constgeometry=newTHREE.IcosahedronGeometry(1,1)constmeshes=[newTHREE.Mesh(geometry,newTHREE.MeshBasicMaterial({color:data.color})),newTHREE.Mesh(geometry,newTHREE.MeshNormalMaterial({flatShading:true})),newTHREE.Mesh(geometry,newTHREE.MeshPhongMaterial({color:data.color,flatShading:true})),newTHREE.Mesh(geometry,newTHREE.MeshStandardMaterial({color:data.color,flatShading:true})),]meshes[0].position.set(-3,1,0)meshes[1].position.set(-1,1,0)meshes[2].position.set(1,1,0)meshes[3].position.set(3,1,0)meshes[0].castShadow=truemeshes[1].castShadow=truemeshes[2].castShadow=truemeshes[3].castShadow=true//meshes.map((m) => (m.castShadow = true)) // using array mapmeshes.map((m)=>(m.receiveShadow=true))// and instead of using `meshes.map` on two lines in a row, you can use once and share it like,// meshes.map((m) => {// m.castShadow = true// m.receiveShadow = true// })scene.add(...meshes)constgui=newGUI()// #region DirectionalLightconstdirectionalLight=newTHREE.DirectionalLight(data.lightColor,Math.PI)directionalLight.position.set(1,1,1)directionalLight.castShadow=truedirectionalLight.shadow.camera.near=0directionalLight.shadow.camera.far=10directionalLight.shadow.mapSize.width=data.shadowMapSizeWidthdirectionalLight.shadow.mapSize.height=data.shadowMapSizeHeightscene.add(directionalLight)// const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight)constdirectionalLightHelper=newTHREE.CameraHelper(directionalLight.shadow.camera)directionalLightHelper.visible=falsescene.add(directionalLightHelper)constdirectionalLightFolder=gui.addFolder('DirectionalLight')directionalLightFolder.add(directionalLight,'visible')directionalLightFolder.addColor(data,'lightColor').onChange(()=>{directionalLight.color.set(data.lightColor)})directionalLightFolder.add(directionalLight,'intensity',0,Math.PI*10)directionalLightFolder.add(directionalLight.position,'x',-5,5,0.001).onChange(()=>{directionalLightHelper.update()})directionalLightFolder.add(directionalLight.position,'y',-5,5,0.001).onChange(()=>{directionalLightHelper.update()})directionalLightFolder.add(directionalLight.position,'z',-5,5,0.001).onChange(()=>{directionalLightHelper.update()})directionalLightFolder.add(directionalLightHelper,'visible').name('Helper Visible')directionalLightFolder.add(directionalLight.shadow.camera,'left',-10,-1,0.1).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()directionalLightHelper.update()})directionalLightFolder.add(directionalLight.shadow.camera,'right',1,10,0.1).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()directionalLightHelper.update()})directionalLightFolder.add(directionalLight.shadow.camera,'top',1,10,0.1).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()directionalLightHelper.update()})directionalLightFolder.add(directionalLight.shadow.camera,'bottom',-10,-1,0.1).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()directionalLightHelper.update()})directionalLightFolder.add(directionalLight.shadow.camera,'near',0,100).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()directionalLightHelper.update()})directionalLightFolder.add(directionalLight.shadow.camera,'far',0.1,100).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()directionalLightHelper.update()})directionalLightFolder.add(data,'shadowMapSizeWidth',[256,512,1024,2048,4096]).onChange(()=>updateDirectionalLightShadowMapSize())directionalLightFolder.add(data,'shadowMapSizeHeight',[256,512,1024,2048,4096]).onChange(()=>updateDirectionalLightShadowMapSize())directionalLightFolder.add(directionalLight.shadow,'radius',1,10,1).name('radius (PCF | VSM)')// PCFShadowMap or VSMShadowMapdirectionalLightFolder.add(directionalLight.shadow,'blurSamples',1,20,1).name('blurSamples (VSM)')// VSMShadowMap onlydirectionalLightFolder.open()functionupdateDirectionalLightShadowMapSize(){directionalLight.shadow.mapSize.width=data.shadowMapSizeWidthdirectionalLight.shadow.mapSize.height=data.shadowMapSizeHeightdirectionalLight.shadow.map=null}// #endregion// #region PointlightconstpointLight=newTHREE.PointLight(data.lightColor,Math.PI)pointLight.position.set(2,1,0)pointLight.visible=falsepointLight.castShadow=truescene.add(pointLight)constpointLightHelper=newTHREE.PointLightHelper(pointLight)pointLightHelper.visible=falsescene.add(pointLightHelper)constpointLightFolder=gui.addFolder('Pointlight')pointLightFolder.add(pointLight,'visible')pointLightFolder.addColor(data,'lightColor').onChange(()=>{pointLight.color.set(data.lightColor)})pointLightFolder.add(pointLight,'intensity',0,Math.PI*10)pointLightFolder.add(pointLight.position,'x',-10,10)pointLightFolder.add(pointLight.position,'y',-10,10)pointLightFolder.add(pointLight.position,'z',-10,10)pointLightFolder.add(pointLight,'distance',0.01,20)pointLightFolder.add(pointLight,'decay',0,10)pointLightFolder.add(pointLightHelper,'visible').name('Helper Visible')pointLightFolder.add(pointLight.shadow.camera,'near',0.01,100).onChange(()=>{pointLight.shadow.camera.updateProjectionMatrix()pointLightHelper.update()})pointLightFolder.add(pointLight.shadow.camera,'far',0.1,100).onChange(()=>{pointLight.shadow.camera.updateProjectionMatrix()pointLightHelper.update()})pointLightFolder.add(data,'shadowMapSizeWidth',[256,512,1024,2048,4096]).onChange(()=>updatePointLightShadowMapSize())pointLightFolder.add(data,'shadowMapSizeHeight',[256,512,1024,2048,4096]).onChange(()=>updatePointLightShadowMapSize())pointLightFolder.add(pointLight.shadow,'radius',1,10,1).name('radius (PCF | VSM)')// PCFShadowMap or VSMShadowMappointLightFolder.add(pointLight.shadow,'blurSamples',1,20,1).name('blurSamples (VSM)')// VSMShadowMap onlypointLightFolder.close()functionupdatePointLightShadowMapSize(){pointLight.shadow.mapSize.width=data.shadowMapSizeWidthpointLight.shadow.mapSize.height=data.shadowMapSizeHeightpointLight.shadow.map=null}// #endregion// #region SpotlightconstspotLight=newTHREE.SpotLight(data.lightColor,Math.PI)spotLight.position.set(3,2.5,1)spotLight.visible=false//spotLight.target.position.set(5, 0, -5)spotLight.castShadow=truescene.add(spotLight)//const spotLightHelper = new THREE.SpotLightHelper(spotLight)constspotLightHelper=newTHREE.CameraHelper(spotLight.shadow.camera)spotLightHelper.visible=falsescene.add(spotLightHelper)constspotLightFolder=gui.addFolder('Spotlight')spotLightFolder.add(spotLight,'visible')spotLightFolder.addColor(data,'lightColor').onChange(()=>{spotLight.color.set(data.lightColor)})spotLightFolder.add(spotLight,'intensity',0,Math.PI*10)spotLightFolder.add(spotLight.position,'x',-10,10).onChange(()=>{spotLight.shadow.camera.updateProjectionMatrix()spotLightHelper.update()})spotLightFolder.add(spotLight.position,'y',-10,10).onChange(()=>{spotLight.shadow.camera.updateProjectionMatrix()spotLightHelper.update()})spotLightFolder.add(spotLight.position,'z',-10,10).onChange(()=>{spotLight.shadow.camera.updateProjectionMatrix()spotLightHelper.update()})spotLightFolder.add(spotLight,'distance',0.01,100).onChange(()=>{spotLight.shadow.camera.updateProjectionMatrix()spotLightHelper.update()})spotLightFolder.add(spotLight,'decay',0,10).onChange(()=>{spotLight.shadow.camera.updateProjectionMatrix()spotLightHelper.update()})spotLightFolder.add(spotLight,'angle',0,1).onChange(()=>{spotLight.shadow.camera.updateProjectionMatrix()spotLightHelper.update()})spotLightFolder.add(spotLight,'penumbra',0,1,0.001).onChange(()=>{spotLight.shadow.camera.updateProjectionMatrix()spotLightHelper.update()})spotLightFolder.add(spotLightHelper,'visible').name('Helper Visible')spotLightFolder.add(spotLight.shadow.camera,'near',0.01,100).onChange(()=>{spotLight.shadow.camera.updateProjectionMatrix()spotLightHelper.update()})spotLightFolder.add(data,'shadowMapSizeWidth',[256,512,1024,2048,4096]).onChange(()=>updateSpotLightShadowMapSize())spotLightFolder.add(data,'shadowMapSizeHeight',[256,512,1024,2048,4096]).onChange(()=>updateSpotLightShadowMapSize())spotLightFolder.add(spotLight.shadow,'radius',1,10,1).name('radius (PCF | VSM)')// PCFShadowMap or VSMShadowMapspotLightFolder.add(spotLight.shadow,'blurSamples',1,20,1).name('blurSamples (VSM)')// VSMShadowMap onlyspotLightFolder.close()functionupdateSpotLightShadowMapSize(){spotLight.shadow.mapSize.width=data.shadowMapSizeWidthspotLight.shadow.mapSize.height=data.shadowMapSizeHeightspotLight.shadow.map=null}// #endregionconststats=newStats()document.body.appendChild(stats.dom)constlabels=document.querySelectorAll<HTMLDivElement>('.label')letx,yconstv=newTHREE.Vector3()functionanimate(){requestAnimationFrame(animate)controls.update()for(leti=0;i<4;i++){v.copy(meshes[i].position)v.project(camera)x=((1+v.x)/2)*innerWidth-50y=((1-v.y)/2)*innerHeightlabels[i].style.left=x+'px'labels[i].style.top=y+'px'}renderer.render(scene,camera)stats.update()}animate()