jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Three.js from Zero to Senior · Part 5 — Textures & PBR Materials

Dress your surfaces: UV mapping, the PBR map set (albedo, normal, roughness, metalness, AO), color management done right, and environment maps that make metal reflect — with a live material you can build one map at a time.

4 MIN READ Updated JUL 12, 2026

Một MeshStandardMaterial màu đặc chỉ đi được tới đó. Bề mặt thật — gạch, thép chải, da trầy — mang chi tiết ở mức nhỏ hơn nhiều so với tam giác của geometry. Texture vẽ chi tiết đó lên một cách rẻ, và PBR map điều khiển cách từng pixel phản ứng với ánh sáng.

Dựng material từng map một trong demo — bật/tắt base color, normal, roughness, và toggle environment để thấy phản chiếu xuất hiện.

Mở demo toàn màn hình

Nạp một texture

Texture là một ảnh bọc lên geometry. Nạp bằng TextureLoader (bất đồng bộ — ảnh tới qua mạng):

const loader = new THREE.TextureLoader();
const albedo = loader.load('/textures/brick_color.jpg');

// CRITICAL: a color texture must be tagged as sRGB, or it looks washed out.
albedo.colorSpace = THREE.SRGBColorSpace;

const material = new THREE.MeshStandardMaterial({ map: albedo });

Lặp và lệch nằm trên texture, không phải material:

albedo.wrapS = albedo.wrapT = THREE.RepeatWrapping;
albedo.repeat.set(4, 4); // tile 4×4 across the UVs

Ánh xạ UV — ảnh phẳng bọc hình 3D thế nào

Mỗi đỉnh mang một toạ độ 2D (u, v) trong khoảng 0–1, nói “điểm này của mesh ứng với điểm này của texture.” Geometry dựng sẵn đã có UV hợp lý; model import mang UV được tạo trong Blender hay Maya. Khi texture trông bị kéo hoặc lộ đường nối, hãy nghi UV — không phải texture.

Bộ map PBR

Render dựa trên vật lý mô tả bề mặt bằng một bộ texture, mỗi cái điều khiển một thuộc tính. Hiếm khi dùng hết, nhưng nên biết từng cái.

  • map (albedo / màu nền) — màu thô, không nướng sẵn ánh sáng. Gắn SRGBColorSpace.
  • normalMap — giả gồ ghề bằng cách bóp méo normal từng pixel, để ánh sáng phản ứng như có hình mà thực ra không có. Cách rẻ nhất để thêm chi tiết. Không gian màu Linear (nó là dữ liệu, không phải màu).
  • roughnessMap — thang xám; trắng = matte, đen = bóng. Cho một bề mặt trộn vùng bóng và vùng mòn.
  • metalnessMap — thang xám; trắng = kim loại, đen = phi kim.
  • aoMap (che khuất môi trường) — làm tối các khe mà ánh sáng môi trường không tới. Cần bộ UV thứ hai (uv2).
  • displacementMap — thật sự dời đỉnh (cần geometry dày); trái với normal map giả.
const mat = new THREE.MeshStandardMaterial({
  map: albedo,            // sRGB
  normalMap: normal,      // linear
  roughnessMap: rough,    // linear
  metalnessMap: metal,    // linear
  metalness: 1,           // multiplied with the map
  roughness: 1,
});

Lưu ý giá trị metalness/roughness được nhân với map của chúng. Nếu bạn cấp roughnessMap, để roughness: 1 để map hiện ra.

Quản lý màu — lỗi khiến mọi thứ trông “sai sai”

Three.js hiện đại render trong không gian linear và xuất ra sRGB. Hai quy tắc giữ màu đúng:

// 1. LDR color textures (albedo/emissive PNG/JPEG) → sRGB.
// Data maps (normal, roughness, metalness) và HDR/EXR environment giữ linear.
albedo.colorSpace = THREE.SRGBColorSpace;

// 2. Add tone mapping for a filmic, non-clipped look.
renderer.toneMapping = THREE.ACESFilmicToneMapping;

Bỏ metadata color-space của color map thì texture trông sai; lỡ gắn normal/data map là sRGB thì ánh sáng sai một cách tinh vi. Environment LDR và HDR không cùng rule: JPEG/PNG chứa màu hiển thị thường là sRGB, còn HDR/EXR lighting data nằm trong linear-sRGB.

Environment map — phản chiếu và ánh sáng có chi phí thấp hơn nhiều đèn

Một quả cầu kim loại không có environment chỉ là quả bóng tối — chẳng có gì để phản chiếu. Environment map là ảnh 360° của khung cảnh; material PBR lấy mẫu từ nó để phản chiếu chiếu sáng dựa trên ảnh. Toggle nó trong demo — lớp bóng như gương xuất hiện mà không cần thêm nhiều đèn realtime. PMREM vẫn tốn thời gian preprocess và texture memory, nên cache/reuse kết quả.

Cách gọn, không cần ship file HDR, là RoomEnvironment + PMREMGenerator (cái demo dùng):

import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';

const pmrem = new THREE.PMREMGenerator(renderer);
const envMap = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;

material.envMap = envMap;            // per-material
// or light the WHOLE scene at once:
scene.environment = envMap;

Đặt scene.environment chiếu sáng mọi material PBR trong scene — nâng cấp “chân thực tức thì” lớn nhất bạn có thể làm.

Lời cảnh báo của sư phụ

  • Texture nhợt nhạt? Bạn quên colorSpace = SRGBColorSpace cho color map.
  • roughnessMap bị lờ? Để roughness: 1 để map không bị nhân về 0.
  • Kim loại đen? Không có gì để phản chiếu — đặt scene.environment.
  • aoMap không ăn? Cần bộ UV thứ hai.
  • Texture bị kéo? Đổ lỗi cho UV, không phải ảnh.

Luyện tập, không thì coi như chưa học

  1. Từng map một: trong demo, bắt đầu chỉ base color, rồi thêm normal, rồi roughness — gọi tên thứ mỗi cái đổi.
  2. Làm cái gương: metalness 1, roughness 0, bật environment. Rồi tăng roughness lên 0.5 — xem phản chiếu nhoè.
  3. Lặp nó: tăng slider tiling và xem ô cờ lặp — đó là repeat.set() trên texture.

Phần tiếp theo

Giờ bạn tạo được bề mặt đáng tin từ đầu. Nhưng không ai dựng nhân vật từng đỉnh bằng code — asset thật đến từ Blender dưới dạng file glTF, thường kèm animation nướng sẵn. Ở Phần 6 ta nạp model glTF (kèm nén Draco), rồi phát clip của chúng qua AnimationMixer — và bạn sẽ điều khiển một mixer trực tiếp crossfade giữa các animation.