Three.js Advanced Materials — PBR, Textures, Environment Maps
In this tutorial, you will learn about Three.js Advanced Materials. We cover key concepts, practical examples, and best practices to help you master this topic.
Three.js advanced materials use physically-based rendering with texture maps to create realistic surfaces through diffuse, normal, roughness, metalness, and environment map inputs.
What You'll Learn
By the end of this guide, you will understand PBR material properties, load and apply multiple texture maps, use environment maps for reflections, configure MeshPhysicalMaterial for realistic surfaces, and optimize material performance.
Why Advanced Materials Matter
Basic materials with solid colors look flat and fake. Real-world surfaces have scratches, bumps, fingerprints, and varying reflectivity. Physically-based rendering (PBR) mimics real light behavior. In Durga Antivirus Pro, the 3D threat dashboard uses PBR materials to render network nodes as glassy orbs with metallic connections — giving security analysts an intuitive, realistic view of network topology.
flowchart LR
A[Base Color Map] --> E[PBR Material]
B[Normal Map] --> E
C[Roughness Map] --> E
D[Metalness Map] --> E
F[Ambient Occlusion] --> E
G[Environment Map] --> E
E --> H[Final Rendered Surface]
Texture Maps — The Building Blocks
A texture is an image applied to a surface. Different texture channels control different visual properties.
var loader = new THREE.TextureLoader();
var material = new THREE.MeshStandardMaterial({
map: loader.load('diffuse.jpg'),
normalMap: loader.load('normal.jpg'),
roughnessMap: loader.load('roughness.jpg'),
metalnessMap: loader.load('metalness.jpg'),
aoMap: loader.load('ao.jpg'),
aoMapIntensity: 1.0,
roughness: 0.5,
metalness: 0.8
});
Expected output: A mesh that appears to have surface detail, bumps, scratches, and realistic lighting response based on the texture maps.
Texture Map Reference
| Map Type | Color Channel | What It Does |
|---|---|---|
| map | RGB | Base color of the surface |
| normalMap | RGB | Simulates 3D surface bumps without geometry |
| roughnessMap | Grayscale | White = rough, black = smooth |
| metalnessMap | Grayscale | White = metal, black = non-metal |
| aoMap | Grayscale | Darkens crevices for depth |
| displacementMap | Grayscale | Actually moves vertices (needs subdivisions) |
Environment Maps — Realistic Reflections
An environment map captures the surrounding scene as a 360-degree image that reflects off metallic or glossy surfaces.
var envMap = loader.load('equirectangular.jpg');
envMap.mapping = THREE.EquirectangularReflectionMapping;
var material = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
metalness: 1.0,
roughness: 0.1,
envMap: envMap,
envMapIntensity: 1.5
});
Expected output: A chrome-like sphere that reflects the surrounding environment with sharp, glossy reflections.
HDR Environment Maps
For better results, use HDR (high dynamic range) environment maps:
import { RGBELoader } from "three/addons/loaders/RGBELoader.js";
new RGBELoader().load('environment.hdr', function(texture) {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
scene.background = texture;
});
MeshPhysicalMaterial — The Gold Standard
MeshPhysicalMaterial extends MeshStandardMaterial with additional properties for realistic rendering.
var material = new THREE.MeshPhysicalMaterial({
color: 0x4488ff,
metalness: 0.0,
roughness: 0.05,
clearcoat: 1.0,
clearcoatRoughness: 0.1,
clearcoatNormalMap: normalMap,
reflectivity: 0.5,
ior: 1.5,
transmission: 0.9,
thickness: 1.5
});
Expected output: A glass-like material with clear coat finish, refraction, and transparency.
| Property | Effect | Use Case |
|---|---|---|
| clearcoat | Adds a clear glossy layer | Car paint |
| ior | Index of refraction | Glass, water, diamond |
| transmission | Light passes through | Windows, bottles |
| thickness | How thick the glass is | Realistic refraction |
| sheen | Fabric/fuzz appearance | Velvet, carpets |
Repeating and Wrapping Textures
var texture = loader.load('brick.jpg');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(4, 4);
texture.offset.set(0.5, 0);
texture.rotation = Math.PI / 4;
texture.anisotropy = 4;
Expected output: A brick texture repeated 4 times in each direction, offset right, rotated 45 degrees, with anisotropic filtering for sharp viewing from angles.
Why anisotropy: Without it, textures viewed from a shallow angle become blurry. Anisotropic filtering samples more texels for those angles, keeping the texture sharp.
Common Mistakes
1. Using TextureLoader Before Textures Are Loaded
The TextureLoader returns immediately with an empty texture. The image loads asynchronously. Check with texture.image or use callbacks.
2. Missing UV Coordinates for aoMap and displacementMap
Not all geometries have a second UV set. Use geometry.setAttribute('uv2', geometry.attributes.uv.clone()) to duplicate UVs for aoMap.
3. Normal Map Without Tangent Space
Standard geometries include tangents for normal mapping. Custom BufferGeometry may need computeTangents().
4. HDR Environment Map Without Tone Mapping
HDR textures have values above 1.0. Without tone mapping, colors clip. Enable renderer.toneMapping = THREE.ACESFilmicToneMapping.
5. Texture Memory Overload
4K textures on many objects exhaust GPU memory. Use 1K-2K textures for most objects and mipmapping (enabled by default).
6. Forgetting to Set Texture Mapping Mode
Plain textures apply as equirectangular. For cube maps, set texture.mapping = THREE.CubeReflectionMapping.
Practice Questions
Q1: What is the difference between MeshStandardMaterial and MeshPhysicalMaterial? A: MeshPhysicalMaterial adds clearcoat, transmission, sheen, ior, and more advanced optical properties on top of MeshStandardMaterial's PBR.
Q2: What does a roughness map control? A: It controls how rough each pixel appears. White pixels are rough (diffuse Reflection), black pixels are smooth (specular reflection).
Q3: Why do you need tone mapping for HDR environments? A: HDR images contain values beyond the 0-1 range. Tone mapping compresses these into the displayable range while preserving detail in bright and dark areas.
Q4: What is anisotropy in texture filtering? A: It controls how many texel samples are taken when a surface is viewed at an angle, preventing blurriness. Values are powers of 2 (1, 2, 4, 8, 16).
Q5: How do you create a transparent glass material?
A: Use MeshPhysicalMaterial with transmission: 0.9, thickness: 1.5, roughness: 0, ior: 1.5, and transparent: true.
Challenge: Create a scene with three spheres side by side — one matte ceramic, one metallic chrome, and one glass — each with appropriate PBR settings and environment map reflections.
FAQ
Try It Yourself — PBR Material Explorer
Build a complete HTML page with a rotating sphere that lets you switch between material presets (ceramic, chrome, glass, rubber) and adjust roughness, metalness, and envMap intensity with sliders.
<!DOCTYPE html>
<html>
<head>
<title>PBR Material Explorer</title>
<style>
body { margin: 0; overflow: hidden; font-family: sans-serif; }
#ui { position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.8); color: white; padding: 15px; border-radius: 8px; width: 220px; }
#ui label { display: block; margin: 6px 0; font-size: 13px; }
#ui input[type="range"] { width: 100%; }
#ui select { width: 100%; padding: 4px; margin: 4px 0; }
#presets { display: flex; gap: 4px; margin: 8px 0; }
#presets button { flex: 1; padding: 4px; font-size: 11px; cursor: pointer; }
</style>
</head>
<body>
<div id="ui">
<strong>PBR Explorer</strong>
<label>Roughness <span id="roughVal">0.2</span></label>
<input type="range" id="roughness" min="0" max="1" step="0.01" value="0.2">
<label>Metalness <span id="metalVal">0.0</span></label>
<input type="range" id="metalness" min="0" max="1" step="0.01" value="0">
<label>Env Intensity <span id="envVal">1.0</span></label>
<input type="range" id="envIntensity" min="0" max="3" step="0.05" value="1">
<div id="presets">
<button onclick="setPreset(0.05, 0, 'Ceramic')">Ceramic</button>
<button onclick="setPreset(0.1, 1, 'Chrome')">Chrome</button>
<button onclick="setPreset(0, 0.9, 'Glass')">Glass</button>
<button onclick="setPreset(0.9, 0, 'Rubber')">Rubber</button>
</div>
<div id="presetLabel" style="font-size: 12px; color: #aaa;">Custom</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(5, 3, 8);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
document.body.appendChild(renderer.domElement);
var controls = new OrbitControls(camera, renderer.domElement);
var ambient = new THREE.AmbientLight(0x222244, 0.5);
scene.add(ambient);
var hemi = new THREE.HemisphereLight(0x88aaff, 0x442200, 0.8);
scene.add(hemi);
var material = new THREE.MeshPhysicalMaterial({
color: 0x88bbff,
roughness: 0.2,
metalness: 0,
clearcoat: 0,
envMapIntensity: 1.0
});
var sphere = new THREE.Mesh(new THREE.SphereGeometry(2, 64, 64), material);
sphere.position.y = 1;
scene.add(sphere);
var ground = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.MeshStandardMaterial({ color: 0x222233, roughness: 0.9, metalness: 0 })
);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -1;
scene.add(ground);
document.getElementById("roughness").addEventListener("input", function() {
material.roughness = parseFloat(this.value);
document.getElementById("roughVal").textContent = this.value;
document.getElementById("presetLabel").textContent = "Custom";
});
document.getElementById("metalness").addEventListener("input", function() {
material.metalness = parseFloat(this.value);
document.getElementById("metalVal").textContent = this.value;
document.getElementById("presetLabel").textContent = "Custom";
});
document.getElementById("envIntensity").addEventListener("input", function() {
material.envMapIntensity = parseFloat(this.value);
document.getElementById("envVal").textContent = this.value;
});
window.setPreset = function(rough, metal, name) {
material.roughness = rough;
material.metalness = metal;
document.getElementById("roughness").value = rough;
document.getElementById("metalness").value = metal;
document.getElementById("roughVal").textContent = rough;
document.getElementById("metalVal").textContent = metal;
document.getElementById("presetLabel").textContent = name;
};
function animate() {
requestAnimationFrame(animate);
sphere.rotation.y += 0.005;
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener("resize", function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
What's Next
Explore GLSL shaders for complete control over rendering.
Shaders GLSL — Write vertex and fragment shaders. Shader Material — Using ShaderMaterial for custom effects.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro