Environment Maps

Environment Maps and Image-Based Lighting

Image-Based Lighting's image-based lighting is one assignment: every standard and physical material uses scene.environment. PMREMGenerator prefilters it into mipmaps, one blur level per roughness. The chapter's environment is the RoomEnvironment add-on, a procedural lit room with nothing to download:

bookshelf.js: addEnvironment(), image-based lighting without an image fileJavaScript
export function addEnvironment(renderer, scene, intensity = 1, PMREM = THREE.PMREMGenerator) {
  const pmrem = new PMREM(renderer);            // Section 5.13 passes WebGPU's generator
  scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;
  scene.environmentIntensity = intensity;
  pmrem.dispose();
}

For a photographed environment, load an .hdr with HDRLoader (RGBELoader is deprecated since r180) or .exr with EXRLoader, set mapping = THREE.EquirectangularReflectionMapping, and assign it to scene.environment and, to see it, scene.background (blur it with backgroundBlurriness). scene.environmentIntensity dims it.

Image-based lighting from a PMREM-filtered RoomEnvironment, switched on and offHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f5f1e8; font: 13px system-ui, sans-serif; color: #263238; }
  canvas { display: block; max-width: 100%; }
  .ui { padding: 6px 8px; }
</style>
<script type="importmap">
{ "imports": {
  "three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/" } }
</script>
<div class="ui">
  <label><input type="checkbox" id="env" checked> scene.environment</label>
  <label><input type="checkbox" id="bg"> show it as the background</label>
  environmentIntensity <input type="range" id="k" min="0" max="2" step="0.1" value="1">
</div>
<script type="module">
import * as THREE from 'three';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(600, 260);
renderer.toneMapping = THREE.NeutralToneMapping;
document.body.prepend(renderer.domElement);
const scene = new THREE.Scene();
const plain = new THREE.Color('#fbf8f2');
scene.background = plain;
const camera = new THREE.PerspectiveCamera(35, 600 / 260, 0.1, 50);
camera.position.set(0, 0.5, 3.6);
camera.lookAt(0, 0, 0);

// PMREMGenerator prefilters the environment: one blur level per roughness
const pmrem = new THREE.PMREMGenerator(renderer);
const envMap = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;
pmrem.dispose();
scene.environment = envMap;                   // every standard and physical material uses it

const chrome = new THREE.Mesh(new THREE.SphereGeometry(0.45, 64, 32),
  new THREE.MeshStandardMaterial({ color: '#ffffff', metalness: 1, roughness: 0.05 }));
chrome.position.x = -1.1;
const brushed = new THREE.Mesh(new THREE.TorusKnotGeometry(0.3, 0.1, 128, 24),
  new THREE.MeshStandardMaterial({ color: '#e09a10', metalness: 1, roughness: 0.35 }));
const book = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.8, 0.14),
  new THREE.MeshPhysicalMaterial({ color: '#1f5f8b', roughness: 0.6, clearcoat: 1, clearcoatRoughness: 0.1 }));
book.position.x = 1.1;
scene.add(chrome, brushed, book);

const $ = (id) => document.getElementById(id);
function update() {
  scene.environment = $('env').checked ? envMap : null;   // no lights at all: black without it
  scene.background = $('bg').checked ? envMap : plain;
  scene.backgroundBlurriness = 0.3;
  scene.environmentIntensity = +$('k').value;
}
['env', 'bg', 'k'].forEach((id) => $(id).addEventListener('input', update));
update();

renderer.setAnimationLoop((time) => {
  brushed.rotation.y = book.rotation.y = time / 2000;
  renderer.render(scene, camera);
});
</script>