iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…