Materials & lighting
Physically based materials, environment lighting and shadows.
What it is
Three.js renders with physically based materials: MeshStandardMaterial takes a colour, a roughness (sharp to blurry reflections) and a metalness; MeshPhysicalMaterial adds layers such as clearcoat and transmission (glass). They look right only when there's something to reflect, which is what environment lighting provides. Real-time shadows come from lights with castShadow and meshes that cast or receive them.
When to use it
- Product renders: metal, plastic, glass, paint
- Making simple shapes look premium
- Lighting a scene without baking textures
Metal, paint and glass
Loading 3D scene…
Show code · src/app/three-materials/MaterialsScene.tsx
"use client";
import { Environment, Lightformer, OrbitControls } from "@react-three/drei";
import { useState } from "react";
import { ThreeCanvas } from "@/components/three/ThreeCanvas";
// Physically based rendering (PBR): materials are described by real-world
// properties (how rough, how metallic), and light, including light reflected
// from the surroundings, does the rest. The surroundings here are built from
// <Lightformer> panels instead of an HDR image, so nothing is downloaded.
function Studio() {
return (
<Environment resolution={256}>
<Lightformer form="rect" intensity={3} position={[0, 4, -6]} scale={[10, 3, 1]} />
<Lightformer form="rect" intensity={1.5} position={[-6, 1, 0]} rotation-y={Math.PI / 2} scale={[6, 2, 1]} color="#a89dff" />
<Lightformer form="rect" intensity={1.5} position={[6, 1, 0]} rotation-y={-Math.PI / 2} scale={[6, 2, 1]} color="#ffb0c4" />
<Lightformer form="ring" intensity={2} position={[0, 6, 2]} scale={2} />
</Environment>
);
}
export function MaterialsScene() {
const [roughness, setRoughness] = useState(0.25);
const [metalness, setMetalness] = useState(0.9);
const [color, setColor] = useState("#c9c3ff");
const [environment, setEnvironment] = useState(true);
const slider = (label: string, value: number, set: (v: number) => void) => (
<label className="flex flex-1 basis-55 items-center gap-2 text-sm">
<span className="w-20">{label}</span>
<input className="min-w-0 flex-1" type="range" min={0} max={1} step={0.01} value={value} onChange={(e) => set(Number(e.target.value))} aria-label={label} />
<span className="w-9 tabular-nums">{value.toFixed(2)}</span>
</label>
);
return (
<>
<ThreeCanvas shadows camera={{ position: [0, 1.6, 6.5], fov: 40 }} label="Three spheres: brushed metal, clearcoat paint and glass, under studio lighting">
{() => (
<>
<color attach="background" args={["#14131a"]} />
{environment && <Studio />}
<ambientLight intensity={environment ? 0.1 : 0.4} />
<directionalLight position={[3, 5, 3]} intensity={1.5} castShadow shadow-mapSize={[1024, 1024]} />
{/* 1. Standard: roughness and metalness from the sliders. */}
<mesh position={[-2.2, 0.8, 0]} castShadow>
<sphereGeometry args={[0.8, 64, 64]} />
<meshStandardMaterial color={color} roughness={roughness} metalness={metalness} />
</mesh>
{/* 2. Physical: a clearcoat layer, like car paint over a rough base. */}
<mesh position={[0, 0.8, 0]} castShadow>
<torusKnotGeometry args={[0.5, 0.18, 200, 32]} />
<meshPhysicalMaterial color="#e0577a" roughness={0.6} metalness={0.1} clearcoat={1} clearcoatRoughness={0.05} />
</mesh>
{/* 3. Physical: transmission lets light through, like glass. */}
<mesh position={[2.2, 0.8, 0]} castShadow>
<sphereGeometry args={[0.8, 64, 64]} />
<meshPhysicalMaterial color="#ffffff" roughness={0.05} transmission={1} thickness={1} ior={1.5} />
</mesh>
<mesh rotation-x={-Math.PI / 2} receiveShadow>
<planeGeometry args={[14, 14]} />
<meshStandardMaterial color="#2a2836" roughness={0.9} />
</mesh>
<OrbitControls target={[0, 0.8, 0]} minDistance={4} maxDistance={12} maxPolarAngle={Math.PI / 2.05} />
</>
)}
</ThreeCanvas>
<div className="mb-2 flex flex-wrap items-center gap-x-4 gap-y-2">
{slider("roughness", roughness, setRoughness)}
{slider("metalness", metalness, setMetalness)}
<label className="flex items-center gap-2 text-sm">colour <input type="color" value={color} onChange={(e) => setColor(e.target.value)} /></label>
<label className="flex items-center gap-1.5 text-sm"><input type="checkbox" checked={environment} onChange={(e) => setEnvironment(e.target.checked)} /> environment lighting</label>
</div>
<p className="text-sm text-muted">The sliders and colour apply to the left sphere. Untick environment lighting: metal turns almost black, because metal shows its surroundings, and there are none.</p>
</>
);
}