LỜI MỞ ĐẦU
Trong bài viết trước, tôi đã giới thiệu cho anh em một vài mẹo tối ưu game về mặt Scripting. Bài viết này tôi sẽ cùng anh em nghiên cứu và tìm hiểu cách để tối ưu những asset được import vào game. Vì chủng loại asset là rất nhiều nên tôi sẽ chỉ tập trung vào 1 thành phần phổ biến là Audio. Thành phần phổ biến tương đương với nó là Texture, tôi sẽ đề cập ở bài viết sau
Với từng loại asset, tôi sẽ tìm hiểu xem Unity lưu trữ, load và thao tác với các loại asset import vào như nào trong các thời điểm như buildtime và runtime. Khi đã hiểu kĩ về nguyên lí hoạt động, anh em sẽ tránh được những hành động có thể gây ra bottlenecks.
Bài viết được dịch, tổng hợp từ nhiều nguồn khác nhau nên sẽ khá dài, anh em nào lười đọc có thể kéo xuống phần tldr (too long didn’t read) ở cuối bài, ở đó tôi sẽ tóm tắt lại các setting cần thiết.
TỐI ƯU AUDIO ASSET TRONG UNITY
Unity là một Game Engine, người sử dụng có thể tạo ra các game từ quy mô nhỏ nhỏ chỉ yêu cầu một nhạc nền và một vài hiệu ứng, cho đến các game nhập vai cỡ lớn yêu cầu hàng triệu đoạn hội thoại, nhạc nền và hiệu ứng. Audio file không chỉ đóng góp một phần đáng kể vào dung lượng của file sau khi build mà còn có thể tiêu tốn một lượng tài nguyên CPU và Memory kha khá khi chạy runtime.
Việc của tôi và anh em (các developers) là phải cân bằng việc tối ưu giữa hiệu năng và chất lượng âm thanh trong game khi đến tay người dùng, phải làm sao cho âm thanh phải vừa hay, vừa nhẹ về mặt dung lượng.
Bottleneck (nghẽn cổ chai) ở audio có thể phát sinh từ nhiều nguyên nhân như sau: sử dụng chuẩn nén tốn nhiều tài nguyên, nhiều thao tác xử lí đến âm thanh phức tạp, nhiều âm thanh cùng xử lí trong 1 thời điểm, phương thức truy xuất dữ liệu đến bộ nhớ không hiệu quả, tốc độ truy cập,… ⇒ ảnh hưởng đến cpu và memory.
Các nội dung tiếp theo đây có thể giúp anh em tránh được lỗi bottleneck bằng cách áp dụng đúng tùy chỉnh theo từng trường hợp.
1 – Import audio files
Khi import một file audio vào Unity, các Import setting sẽ được hiển thị trên Inspector như hình dưới đây:
Tôi sẽ đi lần lượt từ trên xuống dưới ý nghĩa của các tùy chọn này:
a – Force to Mono
Đầu tiên tôi phải làm rõ một số khái niệm khái niệm: âm thanh Mono hay còn gọi là âm thanh 1 kênh, được phát từ 1 nguồn âm có vị trí cố định; ngược lại với nó, âm thanh Stereo là âm thanh được phát từ nhiều nguồn, có sự phân biệt giữa 2 kênh trái và phải.
Khi bật tùy chọn này lên, đầu tiên, hệ thống sẽ check xem âm đầu vào đang là dạng nào. Nếu âm đầu vào là dạng Mono thì giữ nguyên, nếu âm đầu vào ở dạng Stereo thì dữ liệu âm thanh ở cả 2 kênh sẽ hợp làm 1, từ đó tiết kiệm 50% dung lượng lưu trữ của file âm thanh này trên bộ nhớ.
Những trường hợp nên bật tùy chọn này: nếu game không cần thiết phải cho người chơi định hướng bằng âm thanh (ví dụ các game cần sự định hướng âm thanh là các game có sự ảnh hưởng từ âm thanh → môi trường → gameplay như các game bắn súng, chiến thuật,.. Người chơi có thể dựa vào âm thanh để biết được kẻ địch đang đến từ nơi nào). Những âm thanh không cần định hướng đa kênh, không ảnh hưởng đến gameplay có thể kể đến: nhạc nền, hiệu ứng UI,….
b – Normalize
Tùy chọn này chỉ cho phép tương tác khi bật tùy chọn Force To Mono bên trên. Khi các kênh âm từ nguồn Stereo được gộp về thành nguồn Mono, âm lượng có khả bị tăng lên ít nhiều. Tùy chọn này sẽ điều chỉnh mức âm lượng mặc định về bằng với mức âm thanh từ nguồn Stereo.
c – Ambisonic
Tùy chọn này sẽ lưu trữ âm thanh ở dạng trường âm rộng và có thể xoay vị trí nguồn âm dựa trên hướng của người nghe. Tùy chọn này chỉ nên chọn khi tạo các video 360 độ hoặc các game/ứng dụng tương tác ảo Cross Reality như Virtual Reality, Mixed Reality,….
2 – Loading audio files
Các file âm thanh ban đầu được đóng gói dưới dạng nhị phân đi kèm ứng dụng và nằm trong bộ nhớ của thiết bị, hoặc 1 số trường hợp được download và lưu vào thiết bị, hoặc được streaming trực tiếp từ internet. Loading audio data là quá trình load các file âm thanh đã được nén vào RAM, sau đó bộ giải mã của thiết bị sẽ convert những dữ liệu đó thành các tín hiệu âm thanh để phát qua bộ phận phát âm thanh như loa ngoài, headphone,…
Cách mà file âm thanh được load sẽ phụ thuộc vào các tùy chọn dưới đây:
- Preload Audio Data: quyết định file audio sẽ được load ngay trong lúc scene được khởi tạo hoặc sau đó một khoảng thời gian
- Khi quá trình load audio được xảy ra, tùy chọn Load In Background sẽ quyết định xem hoạt động này có block hoạt động của main thread hay là sẽ đợi đến khi main thread chạy xong mới bắt đầu tải.
- Tùy chọn Load Type sẽ chia audio file thành các định dạng được quy định sẵn, nó quyết định loại dữ liệu nào được nạp vào bộ nhớ và bao nhiêu dữ liệu được sử dụng tại 1 thời điểm.
a – Preload audio data & Load in background
Hai phần này tôi để chung do chúng có sự liên quan mật thiết đến nhau, kết hợp 2 tùy chọn này sẽ ra 4 trường hợp bên dưới:
- Chỉ bật Preload audio data: Scene sẽ không được play cho tới khi tất cả audio file đã load xong. Tùy chọn này áp dụng với những âm thanh play ngay khi scene được load xong, ví dụ như tiếng bước chân, UI, hoặc bất cứ âm thanh nào cần đồng bộ với hình ảnh xuất hiện sớm nhất trong scene. Nếu bỏ tùy chọn này, audio sẽ chỉ load khi có lệnh Play hoặc LoadAudioData
- Bật cả Preload audio data và Load in background: audio file sẽ được load trong khi scene được load, nhưng khả năng play của scene sẽ không bị delay bởi quá trình load âm thanh. Với tùy chọn này, scene sẽ được play nhanh hơn và không cần phải chờ đợi file âm thanh được load xong hay chưa. Ví dụ như âm thanh kẻ địch chết, end game thắng thua, vật phẩm, và những âm thanh cần đồng bộ với hình ảnh và xuất hiện ở giai đoạn sau của game.
- Chỉ bật Load in Background: audio file sẽ được load khi được gọi và play ngay khi load xong. Tùy thuộc vào kích cỡ của audio file, nó có thể gây ra 1 khoảng delay giữa lúc lệnh được gọi và lúc âm thanh bắt đầu play. File vẫn sẽ được load, nên vấn đề delay này chỉ gặp ở lần gọi đầu tiên. Chỉ nên chọn tùy chọn này với những âm thanh không cần đồng bộ với hình ảnh: ví dụ như ambient sound. Vì chúng chỉ là yếu tố phụ và không ảnh hưởng đến nhịp của gameplay, nên delay 1 chút cũng không có vấn đề gì.
- Tắt cả hai: Nếu lựa chọn tùy chọn này, khi audio file được gọi, nó sẽ sử dụng tài nguyên của mainthread để load. Nếu file có kích thước quá lớn sẽ block xử lí của mainthread, hậu quả là gây ra super ultra lag game. Tùy chọn này chỉ khuyến khích sử dụng với những audio file có kích thước rất nhỏ và phải sure rằng chỉ play từng âm thanh một chứ không phải 1 loạt âm thanh khác cùng play tại 1 thời điểm.
b – Load Type
Khác với 2 tùy chọn trên quyết định audio file sẽ được load khi nào, Load Type setting quyết định audio file sẽ được load như thế nào.
Decompress on load:
Tùy chọn này lấy file đã được nén storage và giải nén vào RAM khi lần đầu tiên được load. Đây là phương thức cơ bản để load audio file ở hầu hết các trường hợp. Vì cần giải nén file, nên sẽ tốn 1 chút thời gian khi loading, nhưng sẽ giảm tài nguyên xử lí khi audio file đã được play, các lần sau khi gọi âm thanh này sẽ không phải load và giải nén lại nữa do file audio đã được cache vào RAM. Tùy chọn phù hợp với file âm thanh cỡ nhỏ và trung bình.
Compressed in memory:
Tùy chọn này sẽ copy file đã được nén từ storage vào RAM khi được load. Nó sẽ chỉ giải nén audio trong khi play âm thanh lúc runtime. Tiêu tốn tài nguyên CPU để tăng tốc độ xử lí và giảm lượng RAM. Do đó, tùy chọn này phù hợp các file âm thanh lớn, hoặc khi hệ thống đang bị bottleneck tại RAM, lúc đó anh em có thể dùng tùy chọn này để chia tải cho CPU.
Streaming:
Tùy chọn này (hay còn gọi là bộ đệm) sẽ load, giải mã, và play file lúc runtime bằng cách chia file âm thanh ra thành từng đoạn nhỏ và xử lí lần lượt từng đoạn đó. Phương pháp này sử dụng một lượng nhỏ RAM nhưng lại sử dụng một lượng lớn CPU.
Vì mỗi đoạn âm thanh nhỏ đều cần 1 buffer riêng, nên setting này sẽ tạo ra nhiều reference đến những đoạn âm thanh đó, dẫn đến việc nhiều bản sao của audio file đó tồn tại trong RAM cần phải xử lí riêng biệt, nên sẽ sử dụng lượng lớn tài nguyên từ CPU. Do đó, tùy chọn này chỉ hợp với những âm thanh riêng lẻ, không mang tính chất trùng lặp với những âm thanh khác: ví dụ như background music hoặc ambient sound.
Hình minh họa cho việc chia tải xử lí giữa CPU và RAM theo các phương thức Load.
Tổng kết lại, với trường hợp mặc định cơ bản, Preload Audio Data bật, Load In Background tắt, Load Type là Decompress On Load, sẽ mất thời gian loading, tuy nhiên đảm bảo rằng mọi âm thanh về sau sẽ được load ngay lập tức khi cần. Không cần thêm thời gian delay khi cần play một audio nào đó, audio sẽ play ngay tại thời điểm gọi hàm Play().
3 – Encoding format and quality level
Các audio file import vào unity có thể ở nhiều định dạng khác nhau, nhưng thực ra, bộ giải mã có sẵn sẽ convert chúng thành các định dạng khác nhau tùy nền tảng. Standalone application và các platform non-mobile sẽ được convert về Ogg Vorbis, các mobile platform sẽ dùng MP3
Số liệu thống kê hiển thị trên inspector window trong vùng Compression Format option cho thấy sự hiệu quả của các phép nén về mặt dung lượng.
Ví dụ trong hình:
Dung lượng file gốc: 8.1MB
Dung lượng file sau khi import và sử dụng chuẩn nén Vorbis: 294.8 KB
Tỉ lệ file sau/trước khi nén: 3.57%
Unity support 4 định dạng encoding phổ biến như sau:
Vorbis: Chất lượng âm thanh có thể tùy chỉnh từ thấp → cao, tốn nhiều tài nguyên CPU để giải nén. Chỉ thích hợp cho nhạc nền và những âm thanh có độ dài trung bình
PCM: File không nén, chất lượng âm thanh cao, chỉ nên dùng cho sound effect ngắn
ADPCM: Chất lượng thấp, trong quá trình nén có thể gây ra 1 số nhiễu (noise), phù hợp với những âm thanh ngắn, hỗn loạn ;)) như tiếng nổ, tiếng va chạm,…
MP3: Tương tự như Vorbis nhưng khác nền tảng (Vorbis là non-mobile platform, MP3 là mobile platform)
4 – Resample to lower frequencies
File âm thanh khi import vào game có thể đến từ nhiều nguồn khác nhau như có người làm trong team, outsource, hoặc đi mua asset từ store về. Các file này nếu không có quy định chuẩn ngay từ đầu sẽ phát sinh ra rất nhiều định dạng và chất lượng khác nhau. Ở bước này, anh em có thể chuẩn hóa lại chất lượng của file đó theo nhu cầu.
Giảm tần số mẫu của file âm thanh xuống sẽ làm giảm dung lượng, đồng thời cả chất lượng của âm đó. Tùy theo nhu cầu của dự án, anh em có thể đặt mức này ở mức vừa đủ, ví dụ những âm thanh đơn như tiếng click UI, đóng mở popup, không yêu cầu chất lượng cao có thể giảm rate này xuống.
5 – Minimize active audiosource count
Trong unity, với mỗi nguồn phát âm thanh sẽ tiêu tốn 1 lượng tài nguyên CPU, có thể tiết kiệm tài nguyên bằng cách tắt bớt đi những nguồn phát không sử dụng đến. Có một cách tiếp cận cho phương pháp này là giới hạn số lượng audio clip được chạy trong 1 thời điểm. Để làm điều này, cần tạo 1 đối tượng là AudioManager, là nguồn trung gian phát âm thanh trong game mỗi khi có event hay request phát âm thanh nào đó. Manager này sẽ giới hạn con số tối đa các nguồn phát tại cùng 1 thời điểm.
Ví dụ: Trong màn gameplay có 10 loại quân đang tham chiến với nhau, tôi quy định về giới hạn cho Manager như sau: chỉ có 1 nhạc nền, 1 ambient sound, các sound effect cùng loại không bao giờ vượt quá x (ví dụ 3 hay 10 melee troop cùng tấn công, thì số lượng âm thanh tấn công tối đa 3). Nếu Manager nhận request phát âm thanh đã đạt giới hạn thì sẽ reject request đó. Số x kia sẽ người phụ trách config âm thanh trong game kiểm soát. Nếu x quá nhỏ sẽ gây ra sự mất đồng bộ về mặt hình ảnh: ví dụ như quân lính chém nhau mà không có âm thanh; nếu x quá lớn sẽ gây ra hiện tượng cộng hưởng âm overlapse khiến âm lượng to đột biến như muốn đấm vào tai người nghe.
Ví dụ về AudioManager được sử dụng trong dự án HOE
Gameplay Unit: các loại quân lính, request âm thanh di chuyển, chém nhau, bắn, chết,..
UI Unit: Các popup, request âm thanh nút bấm, bật, tắt popup
Background Unit: Request các âm thanh nền trong game khi chuyển scene: loading, lobby, gameplay
Các unit kể trên mỗi khi cần play âm thanh gì đó, sẽ không tự play mà request đến một đối tượng SoundManager để play âm thanh. Đối tượng Sound Manager này sẽ lưu reference đến các loại âm thanh, số lượng âm thanh nhiều nhất trong 1 thời điểm theo từng loại âm thanh (để người làm âm thanh có thể tùy chỉnh được).
Tldr:
- Nhạc nền: Vorbis, chất lượng kéo xuống mức vừa phải, Compressed In Memory hoặc Streaming. Nếu game của anh em nhỏ nhẹ, ít thứ trong RAM thì Compressed In Memory để tăng hiệu suất, còn nếu game đã tốn RAM cho nhiều thứ khác thì chọn Streaming, chấp nhận giảm hiệu suất một chút nhưng đủ RAM cho tác vụ khác.
- Tiếng động ngắn và thường sử dụng: CPM hoặc ADCPM, Decompress On Load. CPM cho các âm thanh không được nhiễu, ADCPM cho âm thanh cho phép nhiễu
- Tiếng động dài và ít khi sử dụng: Vorbis, Compressed In Memory. Vì ít khi sử dụng nên tốn CPU giải nén Vorbis không phải vấn đề lớn.
- Trường hợp còn lại (ngắn và ít dùng /dài và dùng nhiều): ADCPM, Compressed In Memory. Lựa chọn này để cân bằng giữa RAM và CPU.
- Giới hạn số lượng nguồn âm thanh được phát trong 1 thời điểm của gêm.
Cảm ơn anh em nào đã kiên nhẫn đọc đến đây :D, một số thông tin, tips&tricks tôi sẽ để link bên dưới, hẹn gặp anh em vào bài tiếp theo liên quan đến Texture.
Nguồn tham khảo:
Ebook Unity Game Optimization 2nd Edition
Ebook Unity Game Optimization 3rd Edition
https://docs.unity3d.com/Manual/class-AudioClip.html
https://sudonull.com/post/1649-Optimize-game-performance-with-Unity-sound-import-options
https://cldn.dev/guides/Unity-Audio-Best-Practices/
https://gamedevbeginner.com/10-unity-audio-tips-that-you-wont-find-in-the-tutorials/
https://gamedevbeginner.com/unity-audio-optimisation-tips/
https://www.habrador.com/tutorials/unity-optimization/
http://www.theappguruz.com/blog/optimize-game-sounds-in-unity