Environment Maps
Environment maps light a scene from a panoramic texture. PMREMs, HDR, and the few lines that make MeshStandardMaterial look amazing.
Three.js — environment maps
EXAMPLE
import * as THREE from 'three';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// ===== Scene baseline =====
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 100);
camera.position.set(3, 2, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.outputColorSpace = THREE.SRGBColorSpace;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// ===== HDR environment map =====
new RGBELoader().load('/studio.hdr', (texture) => {
const pmrem = new THREE.PMREMGenerator(renderer);
pmrem.compileEquirectangularShader();
const envMap = pmrem.fromEquirectangular(texture).texture;
scene.environment = envMap; // lights every PBR material
scene.background = envMap; // (optional) also as backdrop
texture.dispose();
pmrem.dispose();
});
// PMREM = PreFiltered Mipmapped Radiance Environment Map
// It pre-blurs the env map at each mip level so rough surfaces sample faster.
// ===== Add a PBR object =====
const ball = new THREE.Mesh(
new THREE.SphereGeometry(1, 64, 64),
new THREE.MeshStandardMaterial({ color: 0xffffff, metalness: 1, roughness: 0.1 })
);
scene.add(ball);
// MeshStandardMaterial reads scene.environment automatically.
// ===== Animation =====
renderer.setAnimationLoop(() => {
controls.update();
renderer.render(scene, camera);
});
// ===== Cubemap alternative =====
import { CubeTextureLoader } from 'three';
const cubeTex = new CubeTextureLoader().load([
'px.jpg','nx.jpg','py.jpg','ny.jpg','pz.jpg','nz.jpg',
]);
scene.environment = cubeTex;
scene.background = cubeTex;
// 6 PNG / JPG faces. Cheaper than HDR; less dynamic range.
// ===== Where to find HDRs =====
// - polyhaven.com (free, generous license, 1k/2k/4k presets)
// - hdri-skies.com
// - Your own panoramic shots from a 360 camera
// ===== Production tips =====
// - 1k or 2k HDRs are usually enough; 4k+ bloats memory
// - Cache PMREM textures across scene transitions
// - Lower toneMappingExposure if metallic surfaces blow out
// - Set the same texture as background OR a gradient background; do not leave it black if PBR materials need a reference
// ===== Patterns to internalise =====
// - HDR + PMREM + ACESFilmicToneMapping = baseline 'modern' look
// - outputColorSpace = SRGBColorSpace once; let the renderer do the rest
// - One env per scene; swap on level transitions
// - Dispose PMREMGenerator + textures when changing scenes
// ===== Pitfalls =====
// - Forgetting toneMapping + exposure -> washed out or burnt
// - Loading HDR without PMREM -> blocky reflections on rough surfaces
// - 4k+ HDR on mobile -> texture memory blows up
// - Background a different image to environment -> obvious 'fake' shading
Why it matters
Drop an HDR + PMREMGenerator into a scene and MeshStandardMaterial looks like a render farm shot. The pipeline (HDR -> PMREM -> scene.environment -> ACESFilmicToneMapping -> SRGBColorSpace) is short, learnable, and the difference between flat and photoreal.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
new RGBELoader().load('/env.hdr', hdr => {
hdr.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = hdr;
});
Try it Yourself »
Discussion
Loading…