[Unity Series 4] Tối ưu hiệu năng Ứng dụng game Unity - Tối ưu CPU (tiếp)

Trong phần này, mình sẽ tiếp tục bài viết Tối ưu CPU, bao gồm các phương pháp để giảm số SetPass Call và các phương pháp tối ưu Skinned Meshes.

Giảm số lượng object được render Để giảm số lượng object được render, cách đơn giản nhất để thực hiện là giảm số lượng object được nhìn thấy trong Scene. Vì vậy, đầu tiên các bạn nên duyệt toàn bộ những object của mình, xác định những object nào không thật sự quá cần thiết, ẩn nó đi sẽ làm hiệu năng của ứng dụng bạn được cải thiện. Nhiều khi bạn sẽ phải đánh đổi những hiệu ứng đẹp mắt, hay những cảnh giả lập rất thực tế để cải thiện tốc độ CPU. Ví dụ, trong Scene của bạn có nhiều object cây cối để tạo cảm giác một khu rừng, bạn có thể cần suy tính xem mình có nên giảm bớt những object này để cải thiện hiệu suất hay không. Một cách cũng vô cùng đơn giản nữa, mà nhiều khi các bạn không để ý đến, là thuộc tính Far Clip Plane của Camera. Thuộc tính này xác định khoảng cách xa nhất mà Camera của bạn có thể nhìn thấy. Những object nằm ngoài khoảng cách này sẽ không được Render. Nhiều khi bạn không cần phải render những vật thể ở xa, việc giảm khoảng cách Far Clip Plane là một phương pháp rất hữu dụng. Có một mẹo nhỏ là bạn có thể sử dụng hiệu ứng sương mù (Fog), để tạo cảm giác người chơi không thể nhìn xa.

Để mở thuộc tính Far Clip Plane, bạn cần chọn object Camera mà bạn đang sử dụng trong Scene. Trong mục Clipping Plane, thay đổi giá trị Far.

topic3_3

Một cách phức tạp hơn để tuỳ chỉnh khoảng cách nhìn thấy của Camera, đó là thuộc tính Layer Cull Distances của Camera. Thuộc tính này cho phép bạn thay đổi khoảng cách nhìn thấy cho các object theo từng Layer. Do vậy, bạn có thể tạo thêm nhiều Layer để tuỳ chỉnh khoảng cách nhìn thấy của Camera cho mỗi object.

topic3_4

Cách này rất hữu ích nếu chúng ta có rất nhiều những object trang trí nhỏ, chúng ta có thể set khoảng cách nhìn thấy của nó nhỏ hơn nhiều so với khoảng cách nhìn thấy của Terrain mà ta sử dụng. Cách sử dụng layerCullDistance:

public float[] layerCullDistances;

Khi gán giá trị layerCullDistance, bạn cần gán cho nó một mảng giá trị float32. Giá trị 0 trong Cull Distances chính là giá trị Far Clip Plane.

Ví dụ:
using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour
{
    void Start()
    {
        Camera camera = GetComponent();
        float[] distances = new float[32];
        distances[10] = 15;
        camera.layerCullDistances = distances;
    }
}

Occlusion Culling: Đây là một kĩ thuật rất phổ biến và rất mạnh của Unity. Nó cho phép vô hiệu hoá việc rendering các đối tượng bị các đối tượng khác che đi. Ví dụ như trong cảnh của bạn có một toà nhà trước mặt, việc sử dụng Occlusion Culling sẽ vô hiệu hoá các object đằng sau toà nhà, mà bạn không thể nhìn thấy do bị toà nhà che mất. Tuy nhiên, không phải lúc nào Occlusion Culling cũng hữu dụng, nó có thể làm tăng thêm phí tổn cho CPU. Ví dụ như trong các Scene có các vật thể lớn như ngọn núi, toà nhà lớn hay những bức tường làm che chắn, nhiều vật thể đằng sau sẽ cho chúng ta thấy sức mạnh của kĩ thuật này. 01_good_vs_bad_occluder

Hướng dẫn sử dụng cơ bản Occlusion Culling:

Cách đơn giản nhất là chọn tất cả các object trong Scene mà bạn thấy cần là một phần của Occlusion và đổi tag static trong cửa sổ Inspector là Occluder Static và Occludee Static.

topic3_5

Occludee Static được sử dụng cho các object trong suốt hoặc mờ và những object nhỏ không có khả năng che khuất những object khác. Khi bạn sử dụng LOD Groups, chỉ có LOD0 có thể sử dụng Occluder.

Sau đó, mở cửa sổ Occlusion Culling, chọn mục Bake và ấn Bake.

topic3-6

Bạn có thể thay đổi các giá trị trong mục Bake. Tuy nhiên mình thấy giá trị mặc định trong mục này áp dụng khá tốt trong hầu hết các trường hợp.

Giảm số lần mỗi object được Render Realtime lighting, shadows và reflections là những kĩ thuật tạo ánh sáng trong Scene, làm cho game của chúng ta trở nên thực hơn rất nhiều. Tuy nhiên việc sử dụng những kĩ thuật này có thể dẫn đến việc các object được render nhiều lần, điều này có thể ảnh hưởng đến hiệu năng của hệ thống. Rendering Path là một thuật ngữ cho việc cài đặt thứ tự tính toán khi draw scene và sự khác biệt chính giữa các Rendering Path là cách mà chúng xử lý realtime lights, shadows và reflections. Deferred Rendering là sự lựa chọn tốt hơn nếu game của bạn hướng tới phần cứng mạnh và sử dụng nhiều realtime light, shadows và reflections. Forward Rendering có thể sẽ phù hợp hơn với các thiết bị phần cứng yếu (smartphone).

Bạn có thể thay đổi Rendering Path bằng cách vào: Edit > Project Settings > Graphics > Rendering Path.

topic3_7

Bất kể chúng ta chọn Rendering Path nào, việc sử dụng realtime lighting, shadow và reflection đều có thể ảnh hưởng đến hiệu năng của ứng dụng. Và điều quan trọng nhất là phải hiểu cách tối ưu 3 kĩ thuật này. Tuy nhiên, việc tối ưu những kĩ thuật này rất phức tạp nên mình sẽ giới thiệu chúng ở một phần khác của Series.

Kết hợp dữ liệu từ các object được render Mình sẽ giới thiệu một vài kĩ thuật khác nhau sử dụng trong việc tối ưu Batching: Static Batching là một kĩ thuật cho phép Unity kết hợp (batching) các object cố định (static) vào trong các Mesh lớn sau đó render chúng. Dynamic Batching là một kĩ thuật cho phép kết hợp các object, nhóm nhiều đỉnh tương tự nhau của chúng trên CPU và draw chúng trong một lần Draw Call. 2 kĩ thuật này rất hữu ích trong việc giúp CPU giảm đi nhiều tác vụ Batching. Tuy nhiên nó cũng có một số nhược điểm: Static Batching tạo phí tổn bộ nhớ memory và storage, trong khi đó Dynamic Batching sẽ tạo phí tổn trên CPU.

Để sử dụng Static Batching, bạn cần vào cửa sổ Inspector của những object tĩnh trong Scene của bạn, chọn Static.

topic3_8

Sau đó vào Edit-> Project Settings > Player > Other Settings > tích chọn Static Batching và Dynamic Batching (tuỳ thuộc vào bạn muốn sử dụng Batching nào).

Lưu ý: do Batching các Object động sẽ có phí tổn với từng đỉnh (Vertex) trên CPU, nên việc sử dụng kĩ thuật Dynamic Batching chưa thực sự hiệu quả. Bạn sẽ cần thử nghiệm kĩ thuật này trong ứng dụng để xác định xem nó có thật sự hiệu quả hay không. Đối với Batch, mình sẽ giới thiệu cho các bạn một tool rất hữu ích trên Assets Store ở các phần sau của Series. GPU instancing là một công nghệ cho phép một lượng lớn các object giống hệt nhau có thể được Batch một cách hiệu quả. Ví dụ đơn giản như scene của bạn có nhiều những đồng xu giống hệt nhau, bạn có thể batch chúng lại dễ dàng thông qua GPU instancing. Tuy rằng có những hạn chế trong việc sử dụng nó cũng như nó không được hỗ trợ trên tất cả các Platform, tuy nhiên nếu trong Scene của bạn có nhiều object giống nhau, có lẽ kĩ thuật này có thể giúp bạn nâng cao FPS ứng dụng của mình.

Để sử dụng kĩ thuật GPU instancing, bạn chọn Material sử dụng cho object của bạn > chọn Enable GPU Instancing.

topic3_10

Texture atlasing là một kĩ thuật cho phép kết hợp nhiều texture với nhau thành một texture lớn. Nó thường được sử dụng trong game 2D và UI, nhưng cũng có thể sử dụng trong game 3D. Khi chúng ta sử dụng kĩ thuật này, chúng ta có thể đảm bảo rằng các object có thể dùng chung một textures và do vậy những object này có thể batching. Unity có sẵn một Texture atlasing tool cho game 2D, được gọi là Sprite Packer.

Ở mặc định, Sprite Packer sẽ bị ẩn đi, tuy nhiên bạn bật chức năng này bằng cách vào Edit > Project Settings > Editor > Sprite Packer.

topic3_11

Sau đó vào Windows > Sprite Packer để mở cửa sổ chức năng này.

SpritePackerMain

Skinned meshes

Skinned Mesh Renderer là một component của Unity được sử dụng khi chúng ta tạo chuyển động mesh bằng cách sử dụng một kĩ thuật được gọi là bone animation. Các tác vụ liên quan tới việc Rendering Skinned Mesh sẽ được thực hiện trên main thread hoặc trên các worker threads, nó phụ thuộc vào cài đặt ứng dụng và phần cứng thiết bị. Nếu khi bạn kiểm tra thấy Rendering Skinned Mesh là nguyên nhân gây ra CPU bound, bạn có thể thử nghiệm vài phương pháp dưới đây để cải thiện hiệu suất: Đầu tiên, bạn hãy duyệt toàn bộ những object có thành phần Skinned Mesh Renderer. Sau đó hãy xem xét liệu có cần phải sử dụng chúng không? Như mình đã nói bên trên, Skinned Mesh Renderer thường được sử dụng khi tạo animation trong object. Nếu object không cần animation, bạn có thể sử dụng MeshRenderer thay vì Skinned Mesh Renderer, điều này sẽ làm tăng hiệu suất của hệ thống. Khi import model vào trong Unity, nếu bạn không chọn import animations trong phần Import Settings của model, model sẽ sử dụng MeshRenderer thay vì Skinned Mesh Renderer. Bạn cũng chỉ nên sử dụng duy nhất một Skinned Mesh Renderer cho mỗi đối tượng. Unity sẽ tối ưu hoá animation chỉ khi bạn sử dụng kết hợp một Animation component và một Skinned Mesh Renderer component. Tại Component Skinned Mesh Renderer, bạn có thể tuỳ chỉnh thành phần Quality. Thành phần Quality cho phép bạn xác định số lượng bone tối đa được sử dụng mỗi vertex khi skinning. Và tất nhiên, số lượng bone càng thấp thì hiệu năng sẽ càng cao, tuy nhiên sẽ phải đánh đổi hiệu ứng animation không được chất lượng bằng số lượng bone lớn. Giá trị Auto xác định bạn sẽ sử dụng giá trị Blend Weights trong Quality Settings.

topic3_13

Ở trong một vài platform, công việc skinning có thể được thực hiện bằng GPU thay gì CPU. Điều này rất hữu ích khi ứng dụng của bạn đang gặp CPU bound và GPU của bạn còn khá nhiều tài nguyên để sử dụng.

Để thực hiện kĩ thuật này, bạn có thể bật GPU skinning tại: Edit > Project Settings > Other Settings > GPU skinning.

topic3_12-1

KẾT LUẬN

Trong quá trình Render Frame, CPU sẽ phải thực hiện rất nhiều tác vụ. Điều này dẫn đến việc CPU hầu như là vấn đề thường xuyên gặp nhất trong các ứng dụng. Sau khi xác nhận ứng dụng của bạn bị CPU bound, hiểu rõ quá trình, xem xét vấn đề nằm ở tác vụ nào để rồi đó tìm ra các phương pháp tối ưu phù hợp cho ứng dụng. Trong phần tiếp theo, mình sẽ giới thiệu các phương pháp để tối ưu hiệu năng khi ứng dụng của bạn gặp GPU Bound.

Bài viết liên quan
scrolltop