Soft Shadows in Three.js
Advanced three.js soft shadow techniques with PCF shadow mapping, dynamic shadow softness control, and real-time shadow quality comparison.
What this code does
- Three.js Soft Shadows: PCF (Percentage Closer Filtering) creates naturally soft shadow edges for realistic lighting.
- Shadow Softness Comparison: toggle between basic hard shadows and advanced soft shadow techniques in real-time.
- Light Jittering: simulates area light softness by randomly moving point light source for enhanced shadow quality.
- WebGL Shadow Mapping: orthographic shadow camera setup with configurable frustum and resolution controls.
- Shadow Bias Control: prevents shadow acne artifacts on surfaces with adjustable bias parameters.
- Interactive Shadow Tuning: real-time switching between shadow techniques with softness and quality controls.
JavaScript (plain)
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(70, width / height, 0.1, 200)
camera.position.set(2, 2, 5)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
renderer.setSize(width, height)
document.querySelector('#app').appendChild(renderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
controls.target.set(0, 0.5, 0)
controls.update()
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(12, 12),
new THREE.MeshStandardMaterial({ color: 0x101010 })
)
plane.rotation.x = -Math.PI / 2
plane.receiveShadow = true
scene.add(plane)
const box = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.5 })
)
box.position.set(0, 0.5, 0)
box.castShadow = true
scene.add(box)
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(3, 5, 2)
light.castShadow = true
scene.add(light)
light.shadow.mapSize.set(1024, 1024)
light.shadow.bias = -0.0005
const c = light.shadow.camera
c.near = 0.5
c.far = 20
c.left = -4
c.right = 4
c.top = 4
c.bottom = -4
const params = {
softness: 1.0,
technique: 'PCF Soft',
shadowMapSize: 1024,
penumbraSize: 3.0,
contactHardening: true
}
function applyTechnique() {
switch (params.technique) {
case 'Basic':
renderer.shadowMap.type = THREE.BasicShadowMap
break
case 'PCF':
renderer.shadowMap.type = THREE.PCFShadowMap
break
case 'PCF Soft':
renderer.shadowMap.type = THREE.PCFSoftShadowMap
break
case 'VSM':
renderer.shadowMap.type = THREE.VSMShadowMap
break
}
light.shadow.mapSize.setScalar(params.shadowMapSize)
light.shadow.radius = params.contactHardening ? params.penumbraSize : 1
}
applyTechnique()
function animate() {
requestAnimationFrame(animate)
const time = Date.now() * 0.001
const s = params.softness * 0.005
light.position.x += (Math.random() - 0.5) * s
light.position.z += (Math.random() - 0.5) * s
controls.update()
renderer.render(scene, camera)
}
animate()