Skip to content

Materials

A Three.js guide for coding agents. Also covers MeshBasicMaterial, MeshNormalMaterial, MeshMatcapMaterial, MeshDepthMaterial, MeshLambertMaterial, MeshPhongMaterial, and 23 more.

Show all 29 aliases

MeshBasicMaterial, MeshNormalMaterial, MeshMatcapMaterial, MeshDepthMaterial, MeshLambertMaterial, MeshPhongMaterial, MeshToonMaterial, MeshStandardMaterial, MeshPhysicalMaterial, matcap, metalness roughness, aoMap ambient occlusion, displacementMap, normalMap normalScale, alphaMap transparent, material opacity, side DoubleSide, flatShading, RGBELoader hdr, clearcoat, sheen, iridescence, transmission ior, toon shading gradientMap, which material needs lights, screen black after changing material, how do I make glass in three js, how do I pick a material, material performance cost

Core Philosophy

  • A material decides the color of every visible pixel of a geometry via a shader. Three.js ships pre-made shaders as material classes so you rarely write GLSL for standard looks
  • One material instance can be shared across many meshes. Tweak the instance once, every mesh updates
  • Materials sit on a performance ladder: Basic, Normal, and Matcap are cheap and light-free, Lambert is the cheapest lit material, then Phong, then Standard, with Physical the most expensive. Pick the cheapest one that achieves the look, especially on mobile
  • Properties can be passed in the constructor object or set on the instance afterward, both are equivalent
const material = new THREE.MeshBasicMaterial({ map: doorColorTexture })
// same as
const material = new THREE.MeshBasicMaterial()
material.map = doorColorTexture

The material catalog at a glance

  • MeshBasicMaterial: flat color or texture, ignores lights entirely
  • MeshNormalMaterial: colors by normal direction relative to the camera, great for debugging normals and stylized looks
  • MeshMatcapMaterial: fakes lighting by sampling a sphere-render texture, looks lit with zero lights, very performant
  • MeshDepthMaterial: white near the camera near, black near far, used internally for depth passes and shadows
  • MeshLambertMaterial: cheapest light-reactive material, but shows banding artifacts on curved surfaces
  • MeshPhongMaterial: Lambert plus specular highlights (shininess, specular), fewer artifacts, slightly more expensive
  • MeshToonMaterial: cartoon-style stepped shading, controlled by a gradientMap
  • MeshStandardMaterial: PBR with metalness and roughness, the realistic default
  • MeshPhysicalMaterial: Standard plus clearcoat, sheen, iridescence, transmission. The most expensive material in Three.js
  • PointsMaterial: for particles. ShaderMaterial / RawShaderMaterial: custom GLSL. Covered elsewhere

Gotcha: Lambert, Phong, Toon, Standard, and Physical all require lights (or an environment map). Swapping to one of them with no lights gives a black screen and no console error. Add an AmbientLight and a PointLight, or an environment map, before assuming the material is broken.

Universal properties (shown on MeshBasicMaterial, apply broadly)

  • map: applies a texture to the surface
  • color: uniform tint, must be a THREE.Color when set on the instance. Accepts '#ff0000', '#f00', 'red', 'rgb(255, 0, 0)', 0xff0000. Combined with map it tints the texture
  • wireframe: true: shows the triangles as 1px lines regardless of distance, the quickest way to see how subdivided a geometry really is
  • opacity: does nothing until transparent = true tells Three.js the material supports transparency
material.transparent = true
material.opacity = 0.5
  • alphaMap: grayscale texture controlling per-pixel transparency, also requires transparent = true
  • side: THREE.FrontSide (default), THREE.BackSide, or THREE.DoubleSide. DoubleSide is needed to see a plane from behind, but it doubles the faces to render, avoid it when the back is never visible

MeshNormalMaterial

  • Renders each pixel by its normal's orientation relative to the camera: the purple-blue look. Normals are the per-vertex outward directions used for lighting, reflection, and refraction
  • The color stays the same as you orbit because it is camera-relative
  • Adds flatShading: true, which stops interpolating normals between vertices so faces render flat and faceted
  • Legitimately usable as a final look, not just for debugging

MeshMatcapMaterial

  • Supply a texture of a pre-lit sphere via matcap; the material samples it by normal direction relative to the camera. Result: convincing lighting with no lights in the scene, at near-Basic cost
const material = new THREE.MeshMatcapMaterial()
material.matcap = matcapTexture
  • Matcap textures are sRGB: set matcapTexture.colorSpace = THREE.SRGBColorSpace like a color map
  • Limitation: the "lighting" is baked into the texture, so it never responds to camera orbit or scene lights
  • Sources: the nidorx/matcaps GitHub list (check licenses), render your own sphere in a 3D package, or build one at matcap-studio.vercel.app

Lambert, Phong, Toon

  • Lambert: fastest lit material. Fine for performance-critical scenes, but inspect curved geometry for visible banding patterns
  • Phong: fewer artifacts plus specular reflection. shininess raises the highlight tightness, specular colors it
material.shininess = 100
material.specular = new THREE.Color(0x1188ff)
  • Toon: two-tone cartoon shading by default. Add more bands with a tiny gradient texture on gradientMap

The Toon gradientMap filtering trap

A 3x1 (or 5x1) pixel gradient texture gets blended by default filtering, which smooths the steps and silently kills the cartoon effect. Force nearest sampling and drop mipmaps:

gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
gradientTexture.generateMipmaps = false
material.gradientMap = gradientTexture

MeshStandardMaterial (the PBR default)

  • Physically based rendering with realistic light response. "Standard" because PBR parameters transfer across engines: the same metalness and roughness should look similar everywhere
  • The two core dials, both 0 to 1:
material.metalness = 0.45
material.roughness = 0.65
  • Wire them to a debug UI (lil-gui) immediately, these values are tuned by eye:
gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)

The full map set

material.map = doorColorTexture                    // sRGB color
material.aoMap = doorAmbientOcclusionTexture       // fake crevice shadows
material.aoMapIntensity = 1
material.displacementMap = doorHeightTexture       // moves vertices
material.displacementScale = 0.1
material.metalnessMap = doorMetalnessTexture
material.roughnessMap = doorRoughnessTexture
material.normalMap = doorNormalTexture             // fake detail, no vertices moved
material.normalScale.set(0.5, 0.5)                 // a Vector2, not a number
material.transparent = true
material.alphaMap = doorAlphaTexture

Gotchas, each one a classic:

  • aoMap only affects AmbientLight, HemisphereLight, and environment-map lighting. Under a lone PointLight it appears to do nothing
  • displacementMap looks terrible until the geometry has enough subdivisions (e.g. PlaneGeometry(1, 1, 100, 100), SphereGeometry(0.5, 64, 64)) and until displacementScale is tamed down from its default of 1
  • metalnessMap / roughnessMap multiply with the scalar values. Leaving metalness = 0.45 while a metalnessMap is set skews every texel. Set both metalness and roughness to 1 so the maps alone drive the result
  • normalScale is a Vector2: material.normalScale.set(0.5, 0.5), not = 0.5

Environment maps

An environment map is an image of the scene's surroundings, used for reflection, refraction, and as a light source in itself:

import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'

const rgbeLoader = new RGBELoader()
rgbeLoader.load('./textures/environmentMap/2k.hdr', (environmentMap) => {
    environmentMap.mapping = THREE.EquirectangularReflectionMapping
    scene.background = environmentMap
    scene.environment = environmentMap
})
  • .hdr files load via RGBELoader, not TextureLoader, and the result arrives in a callback (second parameter), unlike TextureLoader's immediate return
  • mapping = THREE.EquirectangularReflectionMapping must be set before use
  • scene.environment lights every PBR mesh in the scene; scene.background just displays it. Set either or both
  • An environment map alone can replace explicit lights entirely: delete the AmbientLight and PointLight and the scene still reads as lit
  • Works with Lambert and Phong too, not just Standard/Physical
  • Low roughness plus high metalness is the combination that shows reflections clearly when testing

MeshPhysicalMaterial

Inherits everything from MeshStandardMaterial and adds four effects. Cost warning: this is the worst-performing material in Three.js, do not blanket a scene with it on mobile.

Clearcoat

Simulates a thin varnish or glass layer with its own reflectivity on top of the base material:

material.clearcoat = 1
material.clearcoatRoughness = 0

Sheen

Highlights the surface at grazing angles, the fabric and velvet look, reads as soft:

material.sheen = 1
material.sheenRoughness = 0.25
material.sheenColor.set(1, 1, 1)

Iridescence

Color-shifting artifacts like a fuel puddle or soap bubble, most visible at narrow viewing angles:

material.iridescence = 1
material.iridescenceIOR = 1                      // ranges 1 to 2.333
material.iridescenceThicknessRange = [100, 800]

Transmission

Light passes through and refracts, real glass rather than mere opacity fading:

material.transmission = 1
material.ior = 1.5        // index of refraction: glass 1.5, water 1.333, diamond 2.417
material.thickness = 0.5  // fixed value, actual geometry thickness is ignored
  • Transmission deforms the image behind the object, which transparent + opacity never does
  • For a clean glass look, strip all maps and set metalness = 0 and roughness = 0; raise roughness for frosted glass
  • IOR values for real substances are on the Wikipedia list of refractive indices

Choosing a material

  • No lights, flat texture or color: Basic
  • Stylized normal-rainbow or debugging: Normal
  • Looks lit, costs nothing, static lighting is fine: Matcap
  • Lit and cheap, artifacts acceptable: Lambert
  • Lit with specular highlight, still cheap: Phong
  • Cartoon: Toon with a nearest-filtered gradientMap
  • Realistic PBR: Standard (the default choice for realism)
  • Glass, varnish, fabric, soap-bubble effects: Physical, sparingly

Common mistakes

  • Switching to Lambert/Phong/Toon/Standard/Physical and getting a black screen: these need lights or an environment map, and there is no error
  • Setting opacity or alphaMap without transparent = true, so nothing changes
  • Using THREE.DoubleSide everywhere: it renders both faces even when one is never seen
  • Forgetting colorSpace = THREE.SRGBColorSpace on map and matcap textures, giving washed-out color
  • Toon material losing its stepped look because the tiny gradientMap is linearly filtered: set min and mag filters to NearestFilter and disable mipmaps
  • Leaving scalar metalness/roughness below 1 while also using metalnessMap/roughnessMap, which multiplies and distorts the maps
  • Applying a displacementMap to a two-triangle plane or low-poly sphere and calling the map broken: add subdivisions and lower displacementScale
  • Setting normalScale = 0.5 instead of normalScale.set(0.5, 0.5): it is a Vector2
  • Loading an .hdr environment map with TextureLoader instead of RGBELoader, or forgetting EquirectangularReflectionMapping
  • Expecting aoMap to react to a PointLight: it only affects ambient-style light and environment maps
  • Assigning material.color = '#ff0000' as a string on an existing instance instead of new THREE.Color('#ff0000')
  • Using MeshPhysicalMaterial on many large on-screen objects and losing the frame rate, especially on mobile

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-three-js-guide({ topic: "materials" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems