Đưa Trung Địa vào cuộc sống bằng WebGL cho thiết bị di động
Trước đây, việc mang lại trải nghiệm tương tác, dựa trên nền tảng web và chứa nhiều nội dung đa phương tiện cho thiết bị di động và máy tính bảng là một thách thức. Các hạn chế chính là hiệu suất, khả năng cung cấp API, các giới hạn trong âm thanh HTML5 trên thiết bị và việc không phát video nội tuyến liền mạch.
Đầu năm nay, chúng tôi đã bắt đầu một dự án với các bạn bè ở Google và Warner Bros. để tạo trải nghiệm web ưu tiên thiết bị di động cho bộ phim Hobbit mới, The Hobbit: The Desolation of Smaug (Người Hobbit: Sự tàn phá của Smaug). Việc xây dựng một Thử nghiệm Chrome dành cho thiết bị di động có nhiều nội dung đa phương tiện là một nhiệm vụ thực sự đầy cảm hứng và thử thách.
Trải nghiệm này được tối ưu hoá cho Chrome dành cho Android trên các thiết bị Nexus mới, nơi chúng tôi hiện có quyền truy cập vào WebGL và Web Audio. Tuy nhiên, bạn cũng có thể trải nghiệm phần lớn tính năng này trên các thiết bị và trình duyệt không phải WebGL nhờ tính năng kết hợp tăng tốc phần cứng và ảnh động CSS.
Toàn bộ trải nghiệm này dựa trên bản đồ Trung địa, các địa điểm và nhân vật trong phim Hobbit. Nhờ sử dụng WebGL, chúng tôi có thể làm nổi bật và khám phá thế giới phong phú của bộ ba phim Hobbit, đồng thời cho phép người dùng kiểm soát trải nghiệm.
Những thách thức của WebGL trên thiết bị di động
Trước tiên, thuật ngữ "thiết bị di động" rất rộng. Thông số kỹ thuật của các thiết bị rất khác nhau. Vì vậy, với tư cách là nhà phát triển, bạn cần quyết định xem mình có muốn hỗ trợ nhiều thiết bị hơn với trải nghiệm ít phức tạp hơn hay không, hoặc như chúng tôi đã làm trong trường hợp này, hãy giới hạn các thiết bị được hỗ trợ ở những thiết bị có thể hiển thị thế giới 3D chân thực hơn. Đối với "Hành trình qua Trung địa", chúng tôi tập trung vào các thiết bị Nexus và 5 điện thoại thông minh Android phổ biến.
Trong thử nghiệm này, chúng tôi đã sử dụng three.js như đã làm cho một số dự án WebGL trước đây. Chúng tôi bắt đầu triển khai bằng cách xây dựng phiên bản ban đầu của trò chơi Trollshaw có thể chạy tốt trên máy tính bảng Nexus 10. Sau một số thử nghiệm ban đầu trên thiết bị, chúng tôi đã có một danh sách các biện pháp tối ưu hoá tương tự như những gì chúng tôi thường sử dụng cho máy tính xách tay có thông số kỹ thuật thấp:
- Sử dụng mô hình có số mặt đa giác thấp
- Sử dụng hoạ tiết có độ phân giải thấp
- Giảm số lượng lệnh gọi vẽ càng nhiều càng tốt bằng cách hợp nhất hình học
- Đơn giản hoá chất liệu và ánh sáng
- Xoá hiệu ứng hậu kỳ và tắt tính năng khử răng cưa
- Tối ưu hoá hiệu suất JavaScript
- Kết xuất canvas WebGL ở một nửa kích thước và mở rộng tỷ lệ bằng CSS
Sau khi áp dụng các biện pháp tối ưu hoá này cho phiên bản thô đầu tiên của trò chơi, chúng tôi đã có tốc độ khung hình ổn định ở mức 30 khung hình/giây mà chúng tôi hài lòng. Tại thời điểm đó, mục tiêu của chúng tôi là cải thiện hình ảnh mà không ảnh hưởng tiêu cực đến tốc độ khung hình. Chúng tôi đã thử nhiều thủ thuật: một số thủ thuật thực sự có tác động đến hiệu suất; một số thủ thuật khác không có tác động lớn như chúng tôi mong đợi.
Sử dụng mô hình có số mặt đa giác thấp
Hãy bắt đầu với các mô hình. Việc sử dụng mô hình có số đa giác thấp chắc chắn sẽ giúp giảm thời gian tải xuống cũng như thời gian khởi chạy cảnh. Chúng tôi nhận thấy rằng có thể tăng độ phức tạp khá nhiều mà không ảnh hưởng nhiều đến hiệu suất. Các mô hình troll mà chúng tôi sử dụng trong trò chơi này có khoảng 5.000 khuôn mặt và cảnh có khoảng 40.000 khuôn mặt và hoạt động tốt.

Đối với một vị trí khác (chưa được phát hành) trong trải nghiệm, chúng tôi nhận thấy việc giảm số đa giác có tác động nhiều hơn đến hiệu suất. Trong trường hợp đó, chúng tôi đã tải các đối tượng có ít đa giác hơn cho thiết bị di động so với các đối tượng chúng tôi đã tải cho máy tính. Việc tạo nhiều bộ mô hình 3D đòi hỏi thêm một số công việc và không phải lúc nào cũng cần thiết. Điều này thực sự phụ thuộc vào độ phức tạp của các mô hình ban đầu.
Khi làm việc trên các cảnh lớn có nhiều đối tượng, chúng tôi đã cố gắng có chiến lược về cách chia hình học. Điều này cho phép chúng tôi nhanh chóng bật và tắt các lưới ít quan trọng hơn để tìm chế độ cài đặt phù hợp với tất cả thiết bị di động. Sau đó, chúng ta có thể chọn hợp nhất hình học trong JavaScript trong thời gian chạy để tối ưu hoá động hoặc hợp nhất hình học trong giai đoạn tiền sản xuất để lưu các yêu cầu.
Sử dụng hoạ tiết có độ phân giải thấp
Để giảm thời gian tải trên thiết bị di động, chúng tôi đã chọn tải các hoạ tiết khác nhau có kích thước bằng một nửa kích thước của hoạ tiết trên máy tính. Hóa ra tất cả thiết bị đều có thể xử lý kích thước kết cấu lên đến 2048x2048px và hầu hết đều có thể xử lý 4096x4096px. Việc tra cứu hoạ tiết trên từng hoạ tiết riêng lẻ có vẻ không phải là vấn đề sau khi các hoạ tiết đó được tải lên GPU. Tổng kích thước của hoạ tiết phải vừa với bộ nhớ GPU để tránh việc liên tục tải lên và tải xuống hoạ tiết, nhưng đây có thể không phải là vấn đề lớn đối với hầu hết trải nghiệm trên web. Tuy nhiên, bạn cần kết hợp các hoạ tiết vào càng ít trang sprite càng tốt để giảm số lượng lệnh gọi vẽ – đây là yếu tố có tác động lớn đến hiệu suất trên thiết bị di động.

(kích thước ban đầu 512x512px)
Đơn giản hoá chất liệu và ánh sáng
Lựa chọn chất liệu cũng có thể ảnh hưởng rất lớn đến hiệu suất và bạn phải quản lý chất liệu một cách khôn ngoan trên thiết bị di động. Chúng tôi đã sử dụng MeshLambertMaterial
(tính toán ánh sáng trên mỗi đỉnh) trong three.js thay vì MeshPhongMaterial
(tính toán ánh sáng trên mỗi texel) để tối ưu hoá hiệu suất. Về cơ bản, chúng tôi cố gắng sử dụng chương trình đổ bóng đơn giản với ít phép tính chiếu sáng nhất có thể.
Để xem các chất liệu bạn sử dụng ảnh hưởng như thế nào đến hiệu suất của cảnh, bạn có thể ghi đè các chất liệu của cảnh bằng MeshBasicMaterial
. Điều này sẽ giúp bạn so sánh hiệu quả.
scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});
Tối ưu hoá hiệu suất JavaScript
Khi xây dựng trò chơi cho thiết bị di động, GPU không phải lúc nào cũng là rào cản lớn nhất. CPU tốn nhiều thời gian, đặc biệt là các hiệu ứng vật lý và ảnh động khung xương. Một mẹo đôi khi hữu ích, tuỳ thuộc vào mô phỏng, là chỉ chạy các phép tính tốn kém này trên mỗi khung hình khác. Bạn cũng có thể sử dụng các kỹ thuật tối ưu hoá JavaScript hiện có khi nói đến việc gộp đối tượng, thu thập rác và tạo đối tượng.
Việc cập nhật các đối tượng được phân bổ trước trong vòng lặp thay vì tạo các đối tượng mới là một bước quan trọng để tránh tình trạng "giật" khi thu gom rác trong trò chơi.
Ví dụ: hãy xem xét mã như sau:
var currentPos = new THREE.Vector3();
function gameLoop() {
currentPos = new THREE.Vector3(0+offsetX,100,0);
}
Phiên bản cải tiến của vòng lặp này giúp tránh việc tạo các đối tượng mới phải được thu gom rác:
var originPos = new THREE.Vector3(0,100,0);
var currentPos = new THREE.Vector3();
function gameLoop() {
currentPos.copy(originPos).x += offsetX;
//or
currentPos.set(originPos.x+offsetX,originPos.y,originPos.z);
}
Trong khả năng có thể, trình xử lý sự kiện chỉ nên cập nhật các thuộc tính và để vòng lặp kết xuất requestAnimationFrame
xử lý việc cập nhật giai đoạn.
Một mẹo khác là tối ưu hoá và/hoặc tính toán trước các thao tác chiếu tia. Ví dụ: nếu cần đính kèm một đối tượng vào lưới trong quá trình di chuyển theo đường dẫn tĩnh, bạn có thể "ghi lại" các vị trí trong một vòng lặp, sau đó đọc từ dữ liệu này thay vì chiếu tia vào lưới. Hoặc như chúng ta làm trong trải nghiệm Rivendell, hãy chiếu tia để tìm các hoạt động tương tác bằng chuột với một lưới ẩn có số mặt đa giác thấp hơn và đơn giản hơn. Việc tìm kiếm các va chạm trên lưới có nhiều đỉnh rất chậm và bạn nên tránh thực hiện trong vòng lặp trò chơi nói chung.
Kết xuất canvas WebGL ở một nửa kích thước và mở rộng tỷ lệ bằng CSS
Kích thước của canvas WebGL có thể là tham số hiệu quả nhất mà bạn có thể điều chỉnh để tối ưu hoá hiệu suất. Bạn càng sử dụng canvas lớn để vẽ cảnh 3D, thì càng phải vẽ nhiều pixel trên mỗi khung hình. Tất nhiên, điều này ảnh hưởng đến hiệu suất.Nexus 10 với màn hình có mật độ điểm ảnh 2560x1600 phải đẩy số pixel lên gấp 4 lần so với máy tính bảng có mật độ điểm ảnh thấp. Để tối ưu hoá điều này cho thiết bị di động, chúng ta sử dụng một mẹo trong đó đặt canvas thành một nửa kích thước (50%) rồi mở rộng canvas lên kích thước dự kiến (100%) bằng các phép biến đổi 3D CSS được tăng tốc phần cứng. Nhược điểm của việc này là hình ảnh bị pixel hoá, trong đó các đường kẻ mảnh có thể trở thành vấn đề, nhưng trên màn hình có độ phân giải cao, hiệu ứng này không quá tệ. Chắc chắn bạn sẽ hài lòng với hiệu suất cao hơn.

Đối tượng dưới dạng thành phần
Để có thể tạo mê cung lớn của lâu đài Dol Guldur và thung lũng Rivendell không bao giờ kết thúc, chúng tôi đã tạo một bộ mô hình 3D khối xây dựng để sử dụng lại. Việc sử dụng lại các đối tượng giúp chúng ta đảm bảo rằng các đối tượng được tạo bản sao và tải lên khi bắt đầu trải nghiệm, chứ không phải ở giữa trải nghiệm.

Trong Rivendell, chúng tôi có một số phần đất liền mà chúng tôi liên tục định vị lại theo chiều sâu Z khi hành trình của người dùng diễn ra. Khi người dùng đi qua các phần, các phần này sẽ được định vị lại ở khoảng cách xa.
Đối với lâu đài Dol Guldur, chúng tôi muốn mê cung được tạo lại cho mỗi trò chơi. Để làm việc này, chúng ta đã tạo một tập lệnh giúp tạo lại mê cung.
Việc hợp nhất toàn bộ cấu trúc thành một lưới lớn ngay từ đầu sẽ dẫn đến một cảnh rất lớn và hiệu suất kém. Để giải quyết vấn đề này, chúng tôi quyết định ẩn và hiển thị các khối xây dựng tuỳ thuộc vào việc chúng có trong chế độ xem hay không. Ngay từ đầu, chúng tôi đã có ý định sử dụng tập lệnh raycaster 2D, nhưng cuối cùng chúng tôi đã sử dụng công cụ loại bỏ hình frustum tích hợp sẵn trong three.js. Chúng ta sử dụng lại tập lệnh raycaster để phóng to "nguy hiểm" mà người chơi đang đối mặt.
Việc quan trọng tiếp theo cần xử lý là tương tác của người dùng. Trên máy tính, bạn có phương thức nhập bằng chuột và bàn phím; trên thiết bị di động, người dùng tương tác bằng thao tác chạm, vuốt, chụm, hướng thiết bị, v.v.
Sử dụng tính năng tương tác cảm ứng trong trải nghiệm web dành cho thiết bị di động
Việc thêm tính năng hỗ trợ cảm ứng không hề khó. Có nhiều bài viết hay để đọc về chủ đề này. Tuy nhiên, có một số điều nhỏ có thể khiến việc này phức tạp hơn.
Bạn có thể sử dụng cả thao tác chạm và chuột. Chromebook Pixel và các máy tính xách tay hỗ trợ cảm ứng khác đều hỗ trợ cả chuột và cảm ứng. Một sai lầm phổ biến là kiểm tra xem thiết bị có hỗ trợ cảm ứng hay không, sau đó chỉ thêm trình nghe sự kiện chạm và không thêm trình nghe sự kiện chuột nào.
Không cập nhật quá trình kết xuất trong trình nghe sự kiện. Thay vào đó, hãy lưu các sự kiện chạm vào biến và phản ứng với các sự kiện đó trong vòng lặp kết xuất requestAnimationFrame. Điều này giúp cải thiện hiệu suất và cũng hợp nhất các sự kiện xung đột. Hãy đảm bảo rằng bạn sử dụng lại các đối tượng thay vì tạo các đối tượng mới trong trình nghe sự kiện.
Hãy nhớ rằng đây là tính năng cảm ứng đa điểm: event.touches là một mảng gồm tất cả các lần chạm. Trong một số trường hợp, bạn nên xem xét event.targetTouches hoặc event.changedTouches và chỉ phản ứng với các thao tác chạm mà bạn quan tâm. Để phân tách thao tác nhấn với thao tác vuốt, chúng ta sử dụng độ trễ trước khi kiểm tra xem thao tác chạm có di chuyển (vuốt) hay không hoặc vẫn ở nguyên vị trí (nhấn). Để nhận được thao tác chụm, chúng ta đo khoảng cách giữa hai lần chạm ban đầu và cách khoảng cách đó thay đổi theo thời gian.
Trong thế giới 3D, bạn phải quyết định cách máy ảnh phản ứng với thao tác chuột so với thao tác vuốt. Một cách phổ biến để thêm chuyển động của máy ảnh là theo chuyển động của chuột. Bạn có thể thực hiện việc này bằng cách điều khiển trực tiếp bằng vị trí con chuột hoặc bằng chuyển động delta (thay đổi vị trí). Bạn không phải lúc nào cũng muốn hành vi trên thiết bị di động giống như trên trình duyệt dành cho máy tính. Chúng tôi đã thử nghiệm rộng rãi để quyết định xem phiên bản nào phù hợp.
Khi xử lý màn hình nhỏ hơn và màn hình cảm ứng, bạn sẽ thấy rằng ngón tay của người dùng và đồ hoạ tương tác trên giao diện người dùng thường cản trở nội dung bạn muốn hiển thị. Đây là điều chúng ta thường làm khi thiết kế ứng dụng gốc nhưng chưa thực sự phải suy nghĩ trước đây với trải nghiệm web. Đây là một thách thức thực sự đối với các nhà thiết kế và nhà thiết kế trải nghiệm người dùng.
Tóm tắt
Kinh nghiệm tổng thể của chúng tôi từ dự án này là WebGL trên thiết bị di động hoạt động rất tốt, đặc biệt là trên các thiết bị mới hơn, cao cấp hơn. Khi nói đến hiệu suất, có vẻ như số lượng đa giác và kích thước hoạ tiết chủ yếu ảnh hưởng đến thời gian tải xuống và khởi chạy, đồng thời chất liệu, chương trình đổ bóng và kích thước của canvas WebGL là những phần quan trọng nhất để tối ưu hoá hiệu suất trên thiết bị di động. Tuy nhiên, hiệu suất là tổng của các phần ảnh hưởng đến hiệu suất, vì vậy, mọi việc bạn có thể làm để tối ưu hoá đều có tác động.
Việc nhắm đến thiết bị di động cũng có nghĩa là bạn phải làm quen với việc suy nghĩ về các hoạt động tương tác bằng cách chạm, và điều này không chỉ liên quan đến kích thước pixel mà còn liên quan đến kích thước thực tế của màn hình. Trong một số trường hợp, chúng tôi phải di chuyển máy ảnh 3D lại gần hơn để thực sự xem được điều gì đang diễn ra.
Thử nghiệm đã ra mắt và đó là một hành trình tuyệt vời. Hy vọng bạn sẽ thích!
Bạn muốn dùng thử không? Hãy bắt đầu Hành trình đến Trung Địa của riêng bạn.