Listen up. If you're building modern applications, understanding Materials in Three.js 3D WebGL is non-negotiable. This is where simple logic turns into intelligent behavior.
1Materials: The Skin of a 3D Object
Welcome to Materials! If Geometry is the skeleton of an object, the Material is its skin. It defines how the object reacts to light, its color, and its texture.
A mesh always needs both: geometry alone is invisible-shaped data, and a material with no geometry has no surface to apply to. Every material type in Three.js ā Basic, Standard, Normal, and others ā implements the same core job differently: computing what color each visible pixel of the surface should be.
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });3D Scene rendered. Objects: 4, Draw Calls: Optimized.
2MeshBasicMaterial: Flat, Unlit Color
The simplest material is MeshBasicMaterial. As the name implies, it's basic: it doesn't react to light at all. It just renders a flat color, regardless of where lights are positioned or how many exist in the scene.
This makes MeshBasicMaterial useful for UI elements, wireframes, or debug visuals where lighting realism doesn't matter ā and cheap to render, since the GPU skips all lighting calculations entirely for it.
<meshBasicMaterial color="#FF0099" />3D Scene rendered. Objects: 4, Draw Calls: Optimized.
3Why MeshBasicMaterial Needs No Lights
If you use a MeshBasicMaterial, you do NOT need to add lights to your scene to see the object ā it always renders at full brightness regardless of lighting setup, since it ignores light entirely by design.
This is a common beginner debugging trap in reverse: if an object using MeshStandardMaterial appears completely black, the fix is usually adding a light, but swapping temporarily to MeshBasicMaterial is a useful diagnostic step to confirm whether the geometry itself is even correct before troubleshooting lighting.
new THREE.MeshBasicMaterial({ color: 'red' });3D Scene rendered. Objects: 4, Draw Calls: Optimized.
4MeshStandardMaterial: Physically Based Rendering
Let's look at MeshStandardMaterial. This is the most common material in Three.js. It uses PBR (Physically Based Rendering) to react realistically to light, calculating how light bounces off the surface based on real-world lighting math.
Because it responds to actual light sources, MeshStandardMaterial requires at least one light in the scene to be visible ā an object using it in a scene with no lights renders completely black, which is one of the most common early confusion points for newcomers.
<meshStandardMaterial color="#00F0FF" roughness={0.5} metalness={0.5} />3D Scene rendered. Objects: 4, Draw Calls: Optimized.
5Roughness and Metalness Explained
Standard materials have two incredibly important properties: Roughness and Metalness. Roughness controls how blurry reflections are, and metalness controls how much it looks like metal.
These two properties together form the core of PBR's approach to surfaces: rather than manually faking a 'shiny plastic' or 'brushed steel' look with ad-hoc settings, you describe the physical material properties (how rough the microsurface is, whether it's a conductor or dielectric) and the renderer computes realistic lighting response from that.
// Roughness: 0 (smooth like glass) to 1 (rough like brick)
// Metalness: 0 (plastic/wood) to 1 (pure metal)3D Scene rendered. Objects: 4, Draw Calls: Optimized.
6Tuning Roughness and Metalness for Specific Looks
To create a material that looks like a perfectly smooth mirror, set roughness to 0 and metalness to 1 ā zero roughness means reflections stay sharp and undistorted, while full metalness means the surface reflects light like a conductor rather than absorbing and re-emitting it diffusely like plastic.
Common material recipes follow this pattern: polished chrome is roughness 0/metalness 1, matte plastic is roughness 0.7-0.9/metalness 0, and brushed aluminum sits somewhere around roughness 0.4/metalness 1 ā experimenting with both values together, rather than in isolation, is how most PBR materials get tuned in practice.
<meshStandardMaterial roughness={???} metalness={???} />3D Scene rendered. Objects: 4, Draw Calls: Optimized.
7Putting It Together: A Shiny Metallic TorusKnot
Let's see a shiny, metallic TorusKnot using meshStandardMaterial in React Three Fiber! This combines everything so far: a complex geometry, a PBR material with high metalness/low roughness, and actual light sources to react against.
Notice this example includes both an ambientLight (soft, uniform fill light with no direction) and a directionalLight (light from a specific direction, like the sun) ā MeshStandardMaterial needs directional light information specifically to render believable highlights and reflections, which ambient light alone can't provide.
const App = () => {
const meshRef = React.useRef();
useFrame((state, delta) => {
meshRef.current.rotation.y += delta;
});
return (
<Canvas camera={{ position: [0, 0, 5] }}>
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 10]} intensity={2} />
<mesh ref={meshRef}>
<torusKnotGeometry args={[1, 0.3, 128, 16]} />
<meshStandardMaterial
color="#CCFF00"
roughness={0.1}
metalness={0.8}
/>
</mesh>
</Canvas>
);
};
render(<App />);3D Scene rendered. Objects: 4, Draw Calls: Optimized.
8MeshNormalMaterial: A Debugging Tool
There is also MeshNormalMaterial. This material maps the normal vectors of the geometry to RGB colors. It's fantastic for debugging geometry issues, since it needs no lights and directly visualizes the direction each face is pointing.
A normal vector points perpendicular to a surface ā MeshNormalMaterial converts each normal's X/Y/Z direction into an R/G/B color, so faces pointing in different directions render in visibly different colors. This makes inverted faces or broken normals (a common import bug from 3D modeling software) immediately obvious.
<meshNormalMaterial />3D Scene rendered. Objects: 4, Draw Calls: Optimized.
9Reading Normal-Mapped Colors
Check out this sphere using MeshNormalMaterial. The colors change based on which direction the faces are pointing (normals) ā a sphere is a good demonstration surface since its normals point in every possible direction across its curved surface.
As the sphere rotates in this example, you'll notice the color pattern stays fixed relative to world space rather than rotating with the object ā that's because normal-to-color mapping is calculated per-frame from each face's current orientation, not baked into the geometry once.
const App = () => {
const meshRef = React.useRef();
useFrame((state, delta) => {
meshRef.current.rotation.y += delta;
meshRef.current.rotation.x += delta * 0.5;
});
return (
<Canvas camera={{ position: [0, 0, 3] }}>
<mesh ref={meshRef}>
<sphereGeometry args={[1, 32, 32]} />
<meshNormalMaterial />
</mesh>
</Canvas>
);
};
render(<App />);3D Scene rendered. Objects: 4, Draw Calls: Optimized.
10The Universal wireframe Property
One last property: wireframe. Any material can be rendered as a wireframe instead of a solid face. This is great for an '80s synthwave aesthetic, but it's also genuinely useful for visualizing a geometry's underlying triangle structure during development.
Because wireframe is a shared boolean property available on essentially every Three.js material type, you can toggle it on any existing material ā MeshBasicMaterial, MeshStandardMaterial, MeshNormalMaterial ā without switching materials entirely, making it a quick way to inspect mesh topology mid-project.
<meshStandardMaterial wireframe={true} />3D Scene rendered. Objects: 4, Draw Calls: Optimized.
11From Materials to Meshes
Excellent! You now know how to skin your objects using materials and PBR rendering. Next, we combine Geometries and Materials to create Meshes! ā the final step that turns a shape and a surface description into an actual renderable object.
Every material you've learned here ā Basic, Standard, Normal ā attaches to a mesh the exact same way, which means switching an object's visual style is often as simple as swapping which material component you nest inside the mesh tag.
// šØ Materials unlocked!3D Scene rendered. Objects: 4, Draw Calls: Optimized.
12Step-by-Step Breakdown
Welcome to Materials! If Geometry is the skeleton of an object, the Material is its skin. It defines how the object reacts to light, its color, and its texture.
The simplest material is MeshBasicMaterial. As the name implies, it's basic: it doesn't react to light at all. It just renders a flat color.
If you use a MeshBasicMaterial, do you need to add lights to your scene to see the object?
- āYes, always
- āNo, it ignores light
Let's look at MeshStandardMaterial. This is the most common material in Three.js. It uses PBR (Physically Based Rendering) to react realistically to light.
Standard materials have two incredibly important properties: Roughness and Metalness. Roughness controls how blurry reflections are, and metalness controls how much it looks like metal.
To create a material that looks like a perfectly smooth mirror, what should the roughness and metalness values be?
- āRoughness: 1, Metalness: 1
- āRoughness: 0, Metalness: 0
- āRoughness: 0, Metalness: 1
Let's see a shiny, metallic TorusKnot using meshStandardMaterial in React Three Fiber!
There is also MeshNormalMaterial. This material maps the normal vectors of the geometry to RGB colors. It's fantastic for debugging geometry issues.
Check out this sphere using MeshNormalMaterial. The colors change based on which direction the faces are pointing (normals).
One last property: wireframe. Any material can be rendered as a wireframe instead of a solid face. This is great for an '80s synthwave aesthetic.
Which boolean property on a material makes it render as lines instead of solid polygons?
- ālines
- ātransparent
- āwireframe
Excellent! You now know how to skin your objects using materials and PBR rendering. Next, we combine Geometries and Materials to create Meshes!
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Don't Encode Critical State in Material Color Alone
Using metalness/color changes as the only signal for interactive state (like 'selected' vs 'unselected' in a 3D configurator) excludes colorblind users and screen-reader users entirely ā pair material changes with a text or icon indicator reflected in the surrounding DOM.
<div aria-live="polite">{isSelected ? 'Selected: Gold finish' : ''}</div>SEO Implications
- 1
Material Properties Are Runtime WebGL State, Not Indexable Content
Roughness, metalness, and color values only exist as GPU shader uniforms once rendered ā none of it is visible to a search crawler, so this page's SEO value comes from explaining PBR concepts in text, not from any specific material configuration.
Best Practices
Reuse Material Instances Across Meshes That Share the Same Appearance
Like geometries, materials can be shared across multiple meshes safely ā creating a new MeshStandardMaterial for every object that should look identical wastes memory and GPU state changes unnecessarily.
Prefer MeshBasicMaterial for UI/Debug Elements That Don't Need Lighting
Reaching for MeshStandardMaterial by default even for elements where lighting realism doesn't matter (like debug gizmos or flat UI panels) adds unnecessary lighting computation. Use MeshBasicMaterial when PBR realism isn't the goal.
Frequent Bugs
Using MeshStandardMaterial (or any lit material) in a scene with no lights added, resulting in a completely black, invisible-looking object.
Lit materials like MeshStandardMaterial require at least one light source in the scene to be visible at all. If an object renders solid black, check for a missing ambientLight or directionalLight before assuming the geometry or material itself is broken.
Real-World Examples
Material Swapping in a Car Configurator
Automotive configurator sites let users toggle between paint finishes (matte, metallic, pearlescent) on the same car model by swapping only the MeshStandardMaterial's roughness and metalness values on the body mesh ā the geometry never changes, only the material properties driving how light reflects off it.
const paintMaterial = new THREE.MeshStandardMaterial({
color: selectedColor,
roughness: finish === 'matte' ? 0.8 : 0.2,
metalness: finish === 'metallic' ? 1 : 0.1
});