This programming guide is only available in Vietnamese. Switch the language toggle to Vietnamese to read the full article.

Khi lập trình đồ họa WebGL thời gian thực, mục tiêu quan trọng nhất là duy trì độ mượt mà ổn định ở tốc độ 60 khung hình trên giây (FPS) để mang lại trải nghiệm tốt nhất. Dù GPU có khả năng xử lý song song hàng triệu luồng cực mạnh, hiệu năng ứng dụng vẫn thường xuyên bị giật lag (sụt giảm FPS) do gặp phải các nút thắt cổ chai giao tiếp và quản lý tài nguyên kém tối ưu. Bài học cuối cùng này sẽ hướng dẫn bạn cách tối ưu hóa toàn diện ứng dụng WebGL của mình.

1. Nút thắt cổ chai CPU-to-GPU (CPU-GPU Bottleneck)

Hiệu năng WebGL bị giới hạn bởi tốc độ truyền tải thông điệp từ JavaScript (CPU) sang driver đồ họa của trình duyệt (GPU). Mỗi khi bạn gọi một lệnh vẽ (`gl.drawArrays` hoặc `gl.drawElements`) hoặc một lệnh thay đổi trạng thái (như cấu hình lại texture, chuyển đổi shader program), CPU phải gửi lệnh điều khiển này qua API đồ họa đắt đỏ.

Nếu cảnh 3D của bạn chứa 1000 cái cây, và bạn dùng vòng lặp gọi 1000 lệnh vẽ đơn lẻ, CPU sẽ bị quá tải (CPU bound) và ứng dụng sẽ bị giật lag, mặc dù GPU vẫn đang rất rảnh rỗi. Do đó, nguyên tắc tối ưu cốt lõi là: Giảm thiểu tối đa số lệnh vẽ (draw calls) và gộp dữ liệu lại gửi đi một lần.

2. Các kỹ thuật tối ưu hóa trạng thái WebGL

  • Texture Atlases (Bản đồ gộp): Thay vì nạp 10 file hình ảnh texture nhỏ lẻ và phải thay đổi liên kết texture liên tục (`gl.bindTexture`), hãy ghép tất cả 10 ảnh đó vào 1 bức ảnh lớn duy nhất. Khi vẽ các vật thể, ta chỉ cần bind texture atlas này 1 lần duy nhất và thay đổi tọa độ UV tương ứng để trỏ đến vùng ảnh nhỏ.
  • Minimize State Changes (Giảm thiểu đổi shader/buffers): Sắp xếp thứ tự vẽ các vật thể theo nhóm sử dụng chung shader program và texture. Việc bind lại shader program (`gl.useProgram`) liên tục là một trong những tác vụ đắt đỏ nhất.
  • Sử dụng VAO: Như đã học ở Bài 6, VAO giúp giảm tải giao tiếp thiết lập attribute layout đỉnh đáng kể.

3. Vẽ hàng loạt bằng Instanced Rendering

Instanced Rendering là vị cứu tinh của hiệu năng khi ta cần vẽ hàng ngàn vật thể giống nhau về dạng hình học nhưng khác nhau về vị trí hoặc màu sắc (ví dụ: các lá cây trong rừng, các hạt bụi bay, tinh vân tinh thể).

Thay vì chạy vòng lặp và gọi hàng ngàn draw calls đơn lẻ, Instanced Rendering cho phép ta gọi vẽ toàn bộ chỉ với 1 lệnh vẽ duy nhất:

// WebGL 2: Vẽ 500 thực thể lập phương cùng lúc
gl.drawElementsInstanced(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, 500);

Để mỗi thực thể lập phương vẽ ra ở các tọa độ khác nhau, chúng ta gán thêm một VBO chứa tọa độ dịch chuyển (offset) của từng thực thể, và sử dụng hàm cấu hình phân tách thuộc tính (attribute divisor):

gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.enableVertexAttribArray(offsetLoc);
gl.vertexAttribPointer(offsetLoc, 3, gl.FLOAT, false, 0, 0);

// Thiết lập divisor = 1: 
// GPU sẽ chuyển sang đọc giá trị offset tiếp theo cho mỗi INSTANCE mới,
// thay vì đọc cho mỗi VERTEX mới.
gl.vertexAttribDivisor(offsetLoc, 1); 
🎮 Demo tương tác: 500 Cubes Instanced Rendering Benchmark
Lệnh vẽ phát đi (Draw Calls): 1
Tốc độ kết xuất (FPS): 60

4. Câu hỏi trắc nghiệm ôn tập

Trắc nghiệm 1: Nguyên nhân CPU Bottleneck

Tại sao việc chạy một vòng lặp lớn trong JavaScript để vẽ từng đối tượng 3D riêng lẻ lại gây sụt giảm khung hình (FPS) mạnh mẽ?

Trắc nghiệm 2: Ý nghĩa của vertexAttribDivisor

Khi cấu hình Instanced Rendering, lệnh gl.vertexAttribDivisor(attributeLocation, 1) có vai trò gì?

Trắc nghiệm 3: Instanced Rendering giảm thiểu chi phí gì

Khi vẽ 10.000 vật thể giống nhau, Instanced Rendering mang lại lợi ích hiệu năng cốt lõi nhờ giảm thiểu điều gì?

Mã nguồn đầy đủ setup Instanced rendering bằng WebGL 2:

Tải về mã nguồn Instanced Rendering

Related Articles

Bài viết liên quan trong series

Lesson 18: Introduction to WebGPU — Beyond WebGL Bài 18: Giới Thiệu WebGPU — Tương Lai Sau WebGL Lesson 16: Procedural Graphics & Raymarching Bài 16: Đồ Họa Thủ Tục (Procedural) & SDFs/Raymarching Back to WebGL Course Overview Quay lại Lộ trình WebGL Series