Sẽ mở trong thẻ mới
PDL AIAI Worker — trợ lý AI trực 24/7: trả lời khách, xử lý yêu cầu, báo cáo tự động
PDL DigitalBuild. Automate. Grow.
Nhận tư vấn
Lập trình & Thiết kế

three.js nâng cao: tải mô hình glTF nhẹ, chiếu sáng đẹp và chạy mượt trên điện thoại

Khoa Trần MinhĐăng 02.11.2026Cập nhật 12.10.2026≈ … phút đọc
Người ngồi trước ba màn hình cong hiển thị cảnh đồ hoạ 3D nhiều màu, tay đặt trên bàn phím có đèn đỏ

three.js cho người làm web · Phần 2

Cập nhật tháng 10/2026. Mã mẫu viết cho three.js 0.186.1 (r186), bản mới nhất trên npm lúc viết bài. Tên hàm có thể đổi giữa các bản, hãy đối chiếu tài liệu chính thức trước khi dùng.

Bài này dành cho ai: người làm web đã dựng được cảnh three.js đầu tiên và nay phải đưa mô hình 3D thật (sofa, bao bì, máy móc) lên trang sản phẩm.

Bạn sẽ nắm được gì: cách tải glTF/GLB, nén mô hình và ảnh vân, chiếu sáng bằng môi trường, đếm draw call, tải trễ, giải phóng bộ nhớ, kèm checklist trước khi bàn giao.

Thời gian đọc: khoảng 9 phút.

Ở bài three.js cho người làm web: dựng cảnh 3D đầu tiên, khối hộp chỉ nặng vài byte. Mô hình thật xuất thẳng từ Blender có thể nặng hàng chục MB, điện thoại tầm trung tải lâu và nóng máy. Kinh nghiệm của chúng tôi: phần lớn vấn đề hiệu năng nằm ở file mô hình, không ở mã.

Người ngồi trước ba màn hình cong hiển thị cảnh đồ hoạ 3D nhiều màu, tay đặt trên bàn phím có đèn đỏ
Máy làm việc có card đồ hoạ mạnh chạy gì cũng mượt, nên đừng đo hiệu năng 3D trên máy này.

1. Chọn glTF/GLB và tải bằng GLTFLoader

Manual three.js khuyên dùng glTF cho hầu hết trường hợp vì định dạng này sinh ra để giao mô hình cho ứng dụng thời gian thực: gọn khi truyền, nhanh khi nạp. Bản .glb gói tất cả vào một tệp nhị phân, tiện đặt lên WordPress hay CDN.

GLTFLoader là addon, phải import riêng. Nếu dùng import map như bài trước, thêm dòng "three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/" cạnh dòng three. Mô hình đen thui hoặc không thấy đâu? Manual gợi ý thêm đèn, thử phóng to/thu nhỏ 1000 lần và xem tab Network có ảnh vân trỏ sai đường dẫn không.

2. Nén trước khi tải: Draco, meshopt và KTX2

Có hai thứ cần nén: hình học (đỉnh, mặt) và ảnh vân. Công cụ dòng lệnh glTF Transform làm cả hai trong một lệnh:

npm install –global @gltf-transform/cli
gltf-transform inspect sofa.glb
gltf-transform optimize sofa.glb sofa-web.glb –compress meshopt –texture-compress webp –texture-size 1024

inspect liệt kê lưới, vật liệu, dung lượng ảnh vân. optimize mặc định còn gộp lưới để giảm draw call (--join), dùng instancing và giới hạn ảnh vân ở 2048px; trang bán hàng thường chỉ cần 1024px.

Draco

Nén hình học mạnh, hợp mô hình tĩnh nhiều đỉnh; cần tải thêm bộ giải mã WebAssembly.

Meshopt

Nén cả hình học lẫn chuyển động, giải mã nhanh. Là lựa chọn mặc định của lệnh optimize.

KTX2 (Basis)

Ảnh vân giữ dạng nén trong bộ nhớ card đồ hoạ; WebP, AVIF chỉ nhẹ lúc tải.

Muốn xuất KTX2 (--texture-compress ktx2), máy bạn cần cài thêm KTX-Software của Khronos. Phía trình duyệt, GLTFLoader cần được gắn bộ giải mã tương ứng:

import { GLTFLoader } from ‘three/addons/loaders/GLTFLoader.js’;
import { DRACOLoader } from ‘three/addons/loaders/DRACOLoader.js’;
import { KTX2Loader } from ‘three/addons/loaders/KTX2Loader.js’;
import { MeshoptDecoder } from ‘three/addons/libs/meshopt_decoder.module.js’;
const libs = ‘https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/libs/’;
const loader = new GLTFLoader()
.setDRACOLoader(new DRACOLoader().setDecoderPath(libs + ‘draco/gltf/’))
.setKTX2Loader(new KTX2Loader().setTranscoderPath(libs + ‘basis/’).detectSupport(renderer))
.setMeshoptDecoder(MeshoptDecoder);
const gltf = await loader.loadAsync(‘/models/sofa-web.glb’);
scene.add(gltf.scene);
Bàn tay cắm dây cáp vào linh kiện bên trong thùng máy tính để bàn
Dung lượng tải về chỉ là một nửa câu chuyện; nửa còn lại là bộ nhớ card đồ hoạ mà ảnh vân chiếm sau khi giải nén.

3. Ánh sáng môi trường: RoomEnvironment hoặc ảnh HDR

Vật liệu PBR như MeshStandardMaterial trông “nhựa” nếu chỉ có đèn định hướng. Cách rẻ nhất để phản chiếu tự nhiên là gán scene.environment. RoomEnvironment dựng sẵn một căn phòng ảo, không phải tải file nào:

import { RoomEnvironment } from ‘three/addons/environments/RoomEnvironment.js’;
const pmrem = new THREE.PMREMGenerator(renderer);
const room = new RoomEnvironment();
scene.environment = pmrem.fromScene(room).texture;
room.dispose();
pmrem.dispose();

Cần ánh sáng thật của một không gian, dùng ảnh .hdr với HDRLoader (từ r180, RGBELoader cũ đã bị đánh dấu ngừng dùng) và đặt mapping = THREE.EquirectangularReflectionMapping.

4. Hiệu năng trên điện thoại: đếm trước, sửa sau

Bài trước đã dừng vòng lặp khi canvas khuất; bước tiếp theo là nhìn số liệu thay vì đoán. renderer.info.render.calls cho biết số lệnh vẽ trong khung hình vừa rồi, renderer.info.memory cho biết số hình học, ảnh vân đang nằm trong bộ nhớ.

  • Draw call: mỗi lưới với một vật liệu thường tốn ít nhất một lệnh vẽ. 300 con ốc tách rời là 300 lệnh. Gộp lưới tĩnh bằng --join hoặc mergeGeometries trong BufferGeometryUtils; vật thể lặp lại (ghế trong hội trường) dùng InstancedMesh.
  • Tỉ lệ điểm ảnh: trên điện thoại, thử hạ từ 2 xuống 1,5 rồi so bằng mắt.
  • Bóng đổ: thay bóng thời gian thực bằng ảnh bóng mờ dưới chân mô hình.
Bàn tay cầm điện thoại đang mở một trang lưới ảnh, phía sau là máy tính xách tay
Đo trên điện thoại tầm trung thật, mạng 4G, không chỉ trên máy làm việc.

5. Tải trễ: đừng bắt khách tải 3D khi chưa cần

Nếu nạp thư viện và mô hình ngay khi mở trang, chúng tranh băng thông với ảnh chính và kéo chậm LCP (xem lại Core Web Vitals cho chủ doanh nghiệp). Cách làm: hiển thị ảnh tĩnh của sản phẩm trước, chỉ import() mã 3D khi khung sắp cuộn tới hoặc khi khách bấm “Xem 3D”:

const holder = document.querySelector(‘#pdl-3d-khung’);
const watcher = new IntersectionObserver(async ([entry]) => {
if (!entry.isIntersecting) return;
watcher.disconnect();
const { startViewer } = await import(‘/js/xem-3d.js’);
startViewer(holder);
}, { rootMargin: ‘300px’ });
watcher.observe(holder);

Ảnh tĩnh cùng kích thước khung 3D để trang không xô lệch (CLS).

6. Giải phóng bộ nhớ khi đổi hoặc gỡ mô hình

Theo manual “How to dispose of objects”, three.js không tự giải phóng tài nguyên card đồ hoạ; gỡ lưới khỏi cảnh không xoá hình học và vật liệu. Khi đổi mô hình hoặc trang SPA chuyển đi, hãy dọn:

function disposeModel(root) {
root.traverse((obj) => {
if (!obj.isMesh) return;
obj.geometry.dispose();
for (const material of [].concat(obj.material)) {
for (const value of Object.values(material)) {
if (value && value.isTexture) value.dispose();
}
material.dispose();
}
});
root.removeFromParent();
}

Kiểm tra lại bằng renderer.info.memory: đổi mô hình vài lần mà số ảnh vân cứ tăng là đang rò rỉ. Vật liệu, ảnh vân dùng chung thì chỉ dispose khi không còn mô hình nào dùng.

Ví dụ minh hoạ: một xưởng gỗ ở Bình Dương đưa 12 mẫu bàn ghế lên trang sản phẩm. Mỗi file gốc khoảng 25 MB. Sau khi chạy optimize với meshopt và ảnh vân 1024px, giả sử mỗi file còn vài MB; trang hiển thị ảnh tĩnh trước, khách bấm mới tải 3D, đổi mẫu thì dọn mẫu cũ. Hãy đo bằng inspect trước và sau. Muốn khách đặt luôn mẫu vào phòng mình, xem thêm AR thử nội thất trên web.

7. Checklist trước khi bàn giao trang có 3D

  1. Mô hình là .glb, đã chạy inspect và optimize, ghi lại dung lượng trước/sau.
  2. Ảnh vân không quá 1024–2048px; cân nhắc KTX2 nếu mô hình nhiều ảnh vân.
  3. Chỉ gắn bộ giải mã file thật sự cần; ghim phiên bản three.js; có scene.environment, ít đèn và bóng đổ.
  4. Draw call mỗi khung đã đo bằng renderer.info, lưới tĩnh đã gộp.
  5. Ảnh tĩnh hiển thị trước, mã 3D tải trễ, khung giữ đúng kích thước.
  6. Vòng lặp dừng khi canvas khuất; tôn trọng prefers-reduced-motion.
  7. Đổi mô hình có hàm dispose, renderer.info.memory không tăng mãi.
  8. Thử trên điện thoại tầm trung thật, mạng 4G.

8. Thuật ngữ cần biết

  • glTF/GLB: định dạng mở của Khronos để truyền mô hình 3D; GLB là bản một tệp nhị phân. Nguồn: Khronos glTF.
  • Draw call: một lệnh CPU gửi card đồ hoạ để vẽ một nhóm hình; quá nhiều lệnh làm khung hình chậm. Nguồn: three.js manual: Optimizing Lots of Objects.
  • KTX2: định dạng ảnh vân của Khronos, dùng nén Basis để giữ dạng nén trong bộ nhớ card đồ hoạ. Nguồn: Khronos KTX.
  • PMREM: bản đồ môi trường được lọc sẵn theo độ nhám để vật liệu PBR phản chiếu đúng. Nguồn: three.js PMREMGenerator.

9. Tìm hiểu thêm

10. Câu hỏi thường gặp

Nên chọn Draco hay meshopt?

Meshopt giải mã nhanh, nén được cả chuyển động; Draco thường cho file nhỏ hơn với mô hình tĩnh nhiều đỉnh. Xuất cả hai, so dung lượng và thời gian hiển thị trên điện thoại thật rồi chọn.

Mô hình bao nhiêu MB là ổn cho trang sản phẩm?

Không có ngưỡng chính thức. Nguyên tắc của chúng tôi: càng nhỏ càng tốt, tải sau nội dung chính và đo trên mạng 4G; chờ vài giây vẫn chưa thấy mô hình thì giảm ảnh vân hoặc đơn giản hoá lưới (--simplify).

PDL Digital đào tạo đội ngũ làm web của doanh nghiệp từ nền tảng JavaScript đến 3D trên web và ứng dụng AI vào quy trình lập trình. Xem trang đào tạo để chọn khoá phù hợp.

Tác giả
Khoa Trần Minh
Thuộc Ban Phát triển ứng dụng web của PDL, phụ trách website doanh nghiệp, ứng dụng nội bộ và tích hợp dữ liệu. Viết hướng dẫn làm được ngay, kèm ví dụ và lưu ý bảo mật.
Tư vấn 1:1

Bạn đang có bài toán số cần gỡ?

Website · AI · Quảng cáo · 360VR — chuyên gia PDL phản hồi trong giờ làm việc (T2–T7, 8h–18h).
Xem giải pháp
PDL · Tư vấn 1:1

Nhận tư vấn
miễn phí

Để lại thông tin, chuyên viên PDL sẽ liên hệ để hiểu nhu cầu và đề xuất giải pháp phù hợp ngân sách của bạn.

  • Phân tích nhanh hiện trạng website, quảng cáo, kênh bán hàng
  • Đề xuất lộ trình & báo giá minh bạch, rõ từng hạng mục
  • Không ràng buộc: nghe tư vấn xong mới quyết định
Giờ làm việc T2–T7 · 08:00–18:00

Chỉ mất khoảng 30 giây

Bạn cần tư vấn về

🔒 Thông tin được bảo mật, chỉ dùng để PDL liên hệ tư vấn.