Trải nghiệm của người Hobbit

Làm cho Trung Địa trở nên sống động với WebGL dành cho thiết bị di động

Daniel Isaksson
Daniel Isaksson

Trước đây, việc mang lại những trải nghiệm tương tác, dựa trên web, nhiều đ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 sử dụng API, những hạn chế trong âm thanh HTML5 trên thiết bị và thiếu khả năng phát video nội tuyến liền mạch.

Đầu năm nay, chúng tôi đã bắt đầu dự án với những người bạn từ Google và Vault Bros. để tạo trải nghiệm web ưu tiên cho thiết bị di động cho bộ phim mới về Hobbit: Người hoblo: Sự tàn phá của coroutine. Việc xây dựng Thử nghiệm Chrome dành cho thiết bị di động nhiều đa phương tiện đã là một nhiệm vụ thực sự truyền cảm hứng và đầy 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, phần lớn trải nghiệm cũng có thể truy cập được trên các thiết bị và trình duyệt không phải WebGL nhờ việc kết hợp tăng tốc phần cứng và ảnh động CSS.

Toàn bộ trải nghiệm được dựa trên bản đồ Trung địa cũng như các địa điểm và nhân vật trong các bộ phim về người Hobbit. Việc sử dụng WebGL giúp chúng tôi kịch tính hóa và khám phá thế giới phong phú của bộ ba người 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

Thứ nhất, thuật ngữ "thiết bị di động" rất rộng. Các thông số kỹ thuật của các thiết bị khác nhau rất nhiều. Vì vậy, là nhà phát triển, bạn cần quyết định xem bạn muốn hỗ trợ thêm nhiều thiết bị có trải nghiệm ít phức tạp hơn hay như chúng tôi đã làm trong trường hợp nà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 thực tế 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à năm điện thoại thông minh Android phổ biến.

Trong thử nghiệm, chúng tôi đã sử dụng three.js như chúng tôi đã thực hiện 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 Trollhaw sẽ 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 tối ưu hóa giống 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 đa giác thấp
  • Sử dụng hoạ tiết có độ phân giải thấp
  • Giảm số lượng hàm gọi vẽ nhiều nhất có thể 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 bài đăng 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à tăng tỷ lệ bằng CSS

Sau khi áp dụng những tính năng tối ưu hoá này cho phiên bản thô đầu tiên của trò chơi, chúng tôi đã đạt được tốc độ khung hình ổn định là 30 khung hình/giây mà chúng tôi rất hài lòng. Khi đó, 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 hoá ra thực sự có tác động đến hiệu suất; một số thủ thuật không có tác động lớn như chúng tôi mong đợi.

Sử dụng mô hình đ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 đ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 cần thiết để khởi tạo cảnh. Chúng tôi nhận thấy rằng mình có thể tăng độ phức tạp lên khá nhiều mà không ảnh hưởng nhiều đến hiệu suất. Các mô hình troll chúng tôi sử dụng trong trò chơi này có khoảng 5 nghìn khuôn mặt và cảnh có khoảng 40 nghìn khuôn mặt và hoạt động tốt.

Một trong những quỷ khổng lồ ở rừng Trollhaw
Một trong những loài quỷ khổng lồ ở rừng Trollhaw

Đố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 đa giác bị tác động nhiều hơn đến hiệu suất. Trong trường hợp đó, chúng ta đã tải các đối tượng đa giác thấp hơn cho thiết bị di động so với các đối tượng chúng ta đã tải cho máy tính. Việc tạo các mô hình 3D khác nhau sẽ đòi hỏi bạn thực hiện thêm một số thao tác và không phải lúc nào cũng bắt buộc. Điều này thực sự phụ thuộc vào độ phức tạp của các mô hình khi bắt đầu.

Khi thực hiện những cảnh lớn có nhiều vật thể, chúng tôi cố gắng lập chiến lược về cách phân chia hình học. Nhờ đó, chúng tôi có thể nhanh chóng bật và tắt các lưới ít quan trọng hơn, cũng như tìm được chế độ cài đặt phù hợp với mọi thiết bị di động. Sau đó, chúng tôi có thể chọn hợp nhất hình 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 trước khi 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 bằng một nửa kích thước của hoạ tiết trên máy tính. Hoá ra tất cả thiết bị đều có thể xử lý kích thước hoạ tiết tối đa 2048x2048px và hầu hết các thiết bị đề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 thành vấn đề sau khi chúng đượ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 các hoạ tiết liên tục được tải lên và tải xuống, nhưng đây có thể không phải là vấn đề lớn đối với hầu hết các trải nghiệm web. Tuy nhiên, việc kết hợp các hoạ tiết vào càng ít sprite càng tốt là yếu tố quan trọng để giảm số lượng các hàm gọi vẽ. Đây là vấn đề có tác động lớn đến hiệu suất trên thiết bị di động.

Hoạ tiết của một trong những quỷ lùn Trollhaw
Hoạ tiết của một trong những loài quỷ khổng lồ ở rừng Trollhaw
(kích thước ban đầu 512x512px)

Đơn giản hoá chất liệu và ánh sáng

Việc lựa chọn chất liệu cũng có thể ảnh hưởng lớn đến hiệu suất và phải được quản lý một cách khôn ngoan trên thiết bị di động. Việc sử dụng MeshLambertMaterial (tính toán ánh sáng cho mỗi đỉnh) trong Three.js thay vì MeshPhongMaterial (tính toán ánh sáng trên mỗi texel) là một thứ chúng tôi sử dụng để tối ưu hóa hiệu suất. Về cơ bản, chúng tôi cố gắng sử dụng các chương trình đổ bóng đơn giản với ít phép tính ánh sáng nhất có thể.

Để xem các chất liệu mà bạn sử dụng ảnh hưởng như thế nào đến hiệu suất của một cảnh, bạn có thể ghi đè các chất liệu của cảnh bằng MeshBasicMaterial . Nhờ đó, bạn sẽ có kết quả so sánh chính xác.

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 dành cho thiết bị di động, GPU không phải lúc nào cũng là trở ngại lớn nhất. Dành rất nhiều thời gian cho CPU, đặc biệt là về hình ảnh vật lý và hoạt ảnh xương. Một mẹo hữu ích đôi khi, tuỳ thuộc vào quá trình mô phỏng, là chỉ chạy các phép tính tốn kém này đối với 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 có sẵn khi nói đến việc gộp đối tượng, thu gom 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 đối tượng mới là một bước quan trọng để tránh tình trạng thu thập rác "bị giật" trong quá trình 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 tạo các đối tượng mới phải thu thập 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);
}

Trình xử lý sự kiện nhiều nhất có thể chỉ nên cập nhật các thuộc tính và cho phép 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 hoạt động truyền tia. Ví dụ: nếu cần gắn một đối tượng vào lưới khi chuyển động theo đường tĩnh, bạn có thể "ghi lại" vị trí trong một vòng lặp, sau đó đọc từ dữ liệu này thay vì truyền tia vào lưới. Hoặc như chúng ta làm trong trải nghiệm Rivendell, phương pháp truyền tia để tìm các tương tác chuột với lưới vô hình ít đa giác đơn giản hơn. Việc tìm kiếm va chạm trên lưới đa năng cao rất chậm nên cần tránh 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à tă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 hóa hiệu suất. Bạn sử dụng canvas càng lớn để vẽ cảnh 3D, thì càng phải vẽ nhiều pixel trên mỗi khung hình. Điều này tất nhiên ảnh hưởng đến hiệu suất.Nexus 10 với màn hình 2560x1600 pixel mật độ cao phải đẩy số lượng pixel gấp 4 lần so với một máy tính bảng mật độ thấp. Để tối ưu hoá việc này cho thiết bị di động, chúng tôi sử dụng một mẹo để đặt canvas thành một nửa kích thước (50%) và sau đó mở rộng khung hình lên kích thước dự kiến (100%) bằng cách chuyển đổi CSS 3D được tăng tốc phần cứng. Nhược điểm của phương pháp này là hình ảnh bị phân thành điểm ảnh, trong đó các đường kẻ mỏng có thể trở thành vấn đề nhưng trên màn hình có độ phân giải cao thì hiệu ứng này không phải là tệ. Hoàn toàn xứng đáng với hiệu suất tăng thêm.

Cùng một cảnh mà không có canvas được điều chỉnh theo tỷ lệ trên Nexus 10 (16FPS) và được điều chỉnh thành 50% (33FPS)
Cùng một cảnh mà không phải chia tỷ lệ canvas trên Nexus 10 (16FPS) và được điều chỉnh theo tỷ lệ 50% (33FPS).

Đối tượng đóng vai trò là thành phần

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

Các khối xây dựng vật thể 3D được sử dụng trong mê cung của Dol Guldur.
Các khối xếp hình vật thể 3D được sử dụng trong mê cung Dol Guldur.

Trong Rivendell, chúng tôi có một số mục cơ bả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, những vị trí này sẽ được định vị lại ở phía xa.

Đối với lâu đài Dol Guldur, chúng tôi muốn mê cung được tái tạo cho mỗi trò chơi. Để làm việc này, chúng tôi đã tạo một tập lệnh tạo lại mê cung.

Việc hợp nhất toàn bộ cấu trúc vào một lưới lớn ngay từ đầu 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 dựng tuỳ thuộc vào việc các khối này có hiển thị hay không. Ngay từ đầu, chúng tôi đã có ý tưởng về việc sử dụng tập lệnh raycaster 2D nhưng cuối cùng, chúng tôi đã sử dụng phương pháp chọn lọc frustrum tích hợp ba.js. Chúng tôi sử dụng lại tập lệnh raycaster để phóng to vào "mối nguy hiểm" mà người chơi đang gặp phải.

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, 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ương tác chạm 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 phải là một việc khó khăn. Bạn có thể đọc các bài viết hay về chủ đề này. Tuy nhiên, có một số yếu tố nhỏ có thể khiến quy trình phức tạp hơn.

Bạn có thể sử dụng cả chuột và cảm ứng. Chromebook Pixel và các máy tính xách tay hỗ trợ cảm ứng khác có hỗ trợ cả chuột và cảm ứng. Một lỗi thường gặp là kiểm tra xem thiết bị đã bật chế độ chạm hay chưa rồi chỉ thêm trình nghe sự kiện chạm và không thêm trình nghe sự kiện chạm.

Không cập nhật chế độ hiển thị trong trình nghe sự kiện. Thay vào đó, hãy lưu các sự kiện chạm vào các 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 có thể kết hợp 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 đối tượng mới trong trình nghe sự kiện.

Hãy nhớ rằng tính năng này là nhiều thao tác: event.Touches là một dãy gồm tất cả các thao tác chạm. Trong một số trường hợp, bạn nên xem xét event.targetTouches hoặc event.replaceTouches và chỉ phản ứng với các thao tác chạm mà bạn quan tâm. Để tách riêng thao tác nhấn với thao tác vuốt, chúng ta sử dụng thời gian chờ trước khi kiểm tra xem thao tác chạm đã di chuyển (vuốt) hay không (nhấn). Để chụm, chúng tôi đo khoảng cách giữa hai lần chạm ban đầu và khoảng cách đó thay đổi như thế nào 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 bằng chuột so với thao tác vuốt. Một cách phổ biến để thêm chuyển động của camera 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 cách sử dụng vị trí chuột hoặc bằng chuyển động delta (thay đổi vị trí). Không phải lúc nào bạn cũng muốn hoạt động giống nhau trên thiết bị di động với trình duyệt dành cho máy tính. Chúng tôi đã thử nghiệm kỹ lưỡng để quyết định xem cách nào phù hợp với từng phiên bản.

Khi xử lý các màn hình và màn hình cảm ứng nhỏ hơn, 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 không thể hiện những gì bạn muốn hiển thị. Đây là điều chúng tôi quen thuộc khi thiết kế ứng dụng gốc nhưng chưa thực sự phải suy nghĩ về trải nghiệm web trước đây. Đâ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

Trải nghiệm chung 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 và cao cấp. 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. Ngoài ra, 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 hóa cho hiệu suất trên thiết bị di động. Tuy nhiên, dữ liệu này là tổng hợp của các phần ảnh hưởng đến hiệu suất, nên bạn có thể làm mọi việc để tối ưu hoá số lượng.

Việc nhắm mục tiêu đế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 tương tác chạm và điều đó không chỉ quan tâm đến kích thước pixel mà còn là kích thước vật lý của màn hình. Trong một số trường hợp, chúng tôi phải di chuyển camera 3D lại gần hơn để thực sự thấy điều gì đang diễn ra.

Thử nghiệm đã được bắt đầu 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ử? Bắt đầu Hành trình đến Trung Địa của riêng bạn.