Tối Ưu Hóa Bộ Nhớ Cho Cảnh Lớn Trong Unity3D - nói dối e blog

Tối Ưu Hóa Bộ Nhớ Cho Cảnh Lớn Trong Unity3D

Dự án MMORPG của công ty chúng tôi gần đây đã gặp phải vấn đề nghiêm trọng về giới hạn bộ nhớ. Trong cuộc họp ngày hôm qua, chúng tôi đã thảo luận về hướng giải quyết. Tôi xin chia sẻ lại phương án cải tiến đã đề xuất thông qua bài viết này.

Vấn đề cụ thể như sau: Trên các thiết bị di động và máy tính bảng hiện nay, hệ điều hành chỉ dành cho ứng dụng khoảng 500MB bộ nhớ khả dụng. Khác với môi trường PC, thiết bị di động sử dụng cơ chế phân vùng hoán đổi (swap partition) để xử lý các nhu cầu bộ nhớ đột biến tạm thời. Tuy nhiên, thiết bị phải đảm bảo hoạt động của các dịch vụ hệ thống (các tiến trình nền ưu tiên cao), do đó khi xảy ra các tác vụ bất ngờ như nhận cuộc gọi, nhận thông báo đẩy… hệ thống có thể chiếm dụng thêm bộ nhớ, dẫn đến việc buộc dừng ứng dụng đang chạy để giải phóng tài nguyên.

Theo kết quả kiểm thử thực tế, để chạy ổn định trên các smartphone cao cấp hiện nay, game cần kiểm soát đỉnh tiêu thụ bộ nhớ dưới 400MB, lý tưởng là 350MB. Con số này thấp hơn rất nhiều so với tiêu chuẩn của game PC cách đây 10 năm. Trong khi đó, dự án của chúng tôi là một MMORPG theo phong cách chân thực với các cảnh game quy mô lớn.

Unity3D được thiết kế trước thời kỳ bùng nổ smartphone, không lường trước được việc sẽ được sử dụng rộng rãi cho game di động. Do đó, nền tảng này không tối ưu cho môi trường bộ nhớ hạn chế, dẫn đến việc sử dụng tài nguyên thô sơ - đây là điểm bị chỉ trích nhiều nhất khi phát triển MMORPG bằng Unity.

Đối với game di động, việc thiết kế engine chuyên dụng cần đặc biệt chú trọng hai yếu tố: kiểm soát nghiêm ngặt bộ nhớ và tiêu thụ năng lượng. Ví dụ trong engine ejoy2d do chúng tôi phát triển, các tham chiếu tài nguyên được tối ưu bằng con trỏ ngắn 32-bit thay vì 64-bit thông thường, tiết kiệm 4 byte mỗi con trỏ. Ma trận biến đổi sử dụng 6 số nguyên định điểm 32-bit, các ma trận giống nhau trong tài nguyên sẽ chia sẻ cùng một dữ liệu. Tài nguyên được lưu trữ liên tục để giảm phân mảnh bộ nhớ… Đây đều là những điểm mà Unity chưa đầu tư kỹ. Trên PC, việc tiết kiệm vài chục MB không đáng kể, nhưng trên di động đó có thể là ranh giới giữa sống và chết.

(P/s: Vấn đề tiêu thụ năng lượng cũng rất thú vị. Trên PC, bạn có thể tận dụng đa luồng để đạt FPS cao bất chấp CPU nóng lên, nhưng trên di động dù 8 nhân đã trở thành tiêu chuẩn, việc này vẫn không nên làm. Vì cùng một khối lượng công việc, việc chia nhỏ cho nhiều luồng luôn làm tăng tổng công việc (ít nhất là chi phí đồng bộ hóa giữa các luồng), dẫn đến tiêu thụ năng lượng cao hơn. Người chơi sẽ không muốn một game phải cắm sạc vẫn nóng ran đến mức tự tắt máy.)

Trong các bài kiểm tra gần đây, game của chúng tôi tiêu thụ khoảng 360MB bộ nhớ trong điều kiện xấu nhất - gần chạm ngưỡng đỏ. Trong đó, cảnh game chiếm 120MB. Điều thú vị là chỉ khoảng 50MB trong số đó dùng cho texture và mô hình, còn lại 70MB dùng cho cấu trúc cảnh. Điều này cho thấy việc yêu cầu họa sĩ tái sử dụng mô hình hoa cỏ không giải quyết được vấn đề. Nói cách khác, vấn đề không nằm ở số lượng texture mà ở số lượng đối tượng được đặt trong cảnh - điều trái ngược với kinh nghiệm làm game PC trước đây.

Chính vì vậy, đa số MMORPG di động hiện nay theo phong cách hoạt hình fantasy là có lý do. Phong cách này cho phép sử dụng tỷ lệ phi thực tế, các đối tượng to hơn để lấp đầy cảnh với số lượng ít hơn. Trong khi đó, phong cách chân thực đòi hỏi chi tiết phong phú, phải dùng nhiều đối tượng nhỏ để lấp đầy tầm nhìn. Trên PC, việc giảm số lượng mô hình và texture là đủ, nhưng trên di động thì không.

Chúng tôi đã nhận thức được vấn đề này từ đầu và áp dụng giải pháp kỹ thuật cho cảnh lớn: Chia cảnh thành các “vùng bao” (bounding box), khi người chơi ra xa vùng nào thì giải phóng tài nguyên trong vùng đó. Đồng thời, thiết kế mỹ thuật đảm bảo cảnh chi tiết nhưng không quá trống trải. Tuy nhiên, giải pháp này vẫn chưa đủ, tôi đã đề xuất cải tiến như sau:

  1. Mở rộng và chồng lấp vùng bao: Cho phép các vùng bao mở rộng hơn, có thể chồng lấp nhau, dùng nhiều vùng bao để định nghĩa một khu vực. Mỗi đối tượng chỉ thuộc duy nhất một vùng, dù vị trí có nằm trong nhiều vùng bao.

  2. Phân loại đối tượng thành “ngoại quan” và “chi tiết”: Ví dụ, tường thành là đối tượng ngoại quan, các vật thể trong thành là chi tiết. Cây lớn trong rừng là ngoại quan, hoa cỏ dưới đất là chi tiết. Hầu hết đối tượng mặc định là chi tiết, chỉ những đối tượng cần nhìn từ xa mới được đánh dấu là ngoại quan. Trước đây chúng tôi đã phân tầng tầm nhìn để loại bỏ rendering, nhưng lần này sẽ dùng cho quản lý bộ nhớ bằng cách đóng gói riêng biệt để giải phóng linh hoạt.

  3. Chiến lược tải/unload thông minh: Khi người chơi ở trong một vùng, phải tải đầy đủ cả ngoại quan và chi tiết. Khi tiếp cận vùng mới, chỉ cần tải ngoại quan và giải phóng chi tiết của vùng xa. Điều này giúp giảm tải bộ nhớ hiệu quả hơn.

Giải pháp mở rộng vùng bao và cho phép chồng lấp giúp cải thiện thời điểm tải dữ liệu. Ví dụ, khi đứng ngoài tường thành, dù gần nhưng không cần tải chi tiết bên trong. Chúng tôi sẽ thêm vùng đệm nhỏ ở cổng thành, khi người chơi bước vào vùng đệm, quá trình tải chi tiết bên trong sẽ bắt đầu. Việc cho phép vùng chồng lấp và phân vùng duy nhất giúp kiểm soát thời điểm tải các đối tượng chi tiết bên ngoài cổng thành, đảm bảo chúng đã được tải sẵn khi người chơi còn ở ngoài.

Giải pháp này không chỉ tối ưu bộ nhớ mà còn cải thiện trải nghiệm người dùng, giảm độ trễ khi di chuyển giữa các khu vực. Đây là bước tiến quan trọng trong hành trình tối ưu hóa game di động có quy mô lớn.

0%