Hiểu Draw Calls Là Gì Trong Game Engine Và Cách Tối Ưu

Làm video game thường nghe phải giữ số lượng polycount thấp nhất, có định mức “budget” tối đa và tối thiểu của mỗi dự án dành cho các model 3D. Mục tiêu việc này nhằm để tối ưu được tốc độ khung hình trên giây (frame budget) và giữ ổn định trãi nghiệm của người chơi, hoặc nếu là làm cinematic thì coi không giật mình :p.

Thắt lưng buột bụng mọi thứ, cũng chỉ vì tiết kiệm bộ nhớ, nhưng thực ra một trong các lý do lớn của tác động lên việc phải kiểm soát bộ nhớ là vì DRAW CALLS.

Hồi Xửa Hồi Xưa...

Thời video game lúc tôi còn bé tí là Mario và chơi trên máy Nitendo gắn băng, tay cầm có 2 cái nút màu đỏ. Tôi không sinh ra ở thế hệ mà những video game trước đó, hoàn toàn là những đồ họa vector.

Các hình ảnh sẽ được giải thuật toán học vẽ lên màn hình đơn sắc, các hình ảnh cơ bản có cả vector và pixel sơ khai bằng tín hiệu điện tử tạo thành video game.

Sau này có những phương thức để thực hiện rasterize vector đồ họa sang pixel là mở ra thời kỳ màu.

Nếu thích timeline của video games ở đây.

Pixel Art Game

Những video game thời kỳ này sẽ luôn có số lượng bộ nhớ vô cùng hạn chế, chứa trên ram, trên những thiết bị lưu trữ riêng. Những pixel này mang mã màu thường tối đa chỉ có từ 0~254 (tổng 255 màu, như ảnh GIF). 

Gọi các hình ảnh này theo từng mảng sẽ là sprite.

Hình ảnh tôi vẽ pixel art tham dự cuộc thi video games trên di động năm 2004 do Samsung tổ chức cùng nhóm bạn học chung.
Thường sẽ vẽ riêng lẽ các spirte ngay cả animation, khi tổng hợp các sprite sẽ có map level - màn chơi, khung cảnh, nhân vật hoạt diễn. Như hình ảnh tổng hợp thiết kế màn chơi của mobile game Sơn Tinh Thủy Tinh mà tụi tôi đã làm cho cuộc thi game di động Samsung 2014.

Các kỹ thuật xếp chồng này gọi là Texture Atlas, bây giờ làm 3D model các bạn quá quen thuộc với texture, một tấm ảnh chứa hình ảnh tương ứng với nhiều UV island của một vật thể 3D. Hoặc phức tạp hơn cho phim ảnh là multiple texture image cho chỉ một phần nào đó của một khối hình 3D. 

Trong video games trước đây, hay trên điện thoại di động giới hạn bộ nhớ, gọi kỹ thuật vừa thấy là texture atlas, “giúp tiết kiệm bộ nhớ”, cụ thể là giúp tối ưu số lần gọi một cụm đồ họa vẽ lên màn hình. Hình trên cho thấy sự tương đồng của sắp xếp hình ảnh sprite của trò Zombie Plant vừa vặn vào chỉ một tấm hình cũng tương tự như tọa độ UV được ghi nhận sẵn trong phần mềm 3D làm texture con robot bên phải. 

Draw Calls

Từ đây bộ nhớ sẽ được kêu ra và vẽ thì gọi là Draw Call. Những tác động như tấm ảnh nén chưa, dài rộng có quá khổ không, màu sắc có vượt khỏi hạn chế của thiết bị hiển thị ? Những việc đó là định mức cho việc làm phát triển game vì Draw Calls.

draw calls nằm trong pipeline từ CPU điều khiển GPU render hình ảnh và hiển thị ra màn hình

Rất nhiều game vì để tối đa hóa được tốc độ, phải nhét để tài nguyên vừa vặn hết vào một tấm hình hoặc dùng trăm phương nghìn kế để tối ưu tốc độ ổn định khung hình. Cũng chỉ là nhằm để tiết kiệm số lần Draw Calls, một lần gọi là một lần xử lý và chờ, tấm hình quá lớn thì chờ lâu, hàng chục tấm như vậy thì nhân lên cho số lược chờ, và sẽ ảnh hưởng trực tiếp đến độ mượt của video games khi xử lý frame per seconds.

Một tác giả giải thích về draw call chi tiết, kỹ thuật, về đồ họa nói chung

Ngay cả ray tracing cũng tốn chi phí draw call. Hình trên là biểu đồ cho thấy lượt sử dụng draw call mỗi frame hình khi thực hiện ray tracing (1 sample cho mỗi pixel, 1024 x 1024 resolution) dựa theo cấp độ dò tia sâu cỡ nào. Trong hình Depth (cấp độ dò tia sâu cỡ nào) = 3. Đây là kết quả nghiên cứu Nvidia RTX từ paper trên Research Gate. Click vào hình dẫn đến link.

Vậy trả lời đơn giản một object có 4 material là 4 draw call, tất cả đều được tính vào, từ ánh sáng, bóng đổ…

Draw Calls là động thái để vẽ tương ứng số lượng object có trong một scene lên màn hình, số lượt gọi và khối lượng dữ liệu sẽ ảnh hưởng đến frame per second.

Game Engine Hiện Đại

Trở lại với hiện tại, phần cứng hiện đại không có nghĩa là không tối ưu định mức cần có ở mỗi dự án. Lấy trường hợp Unreal Engine, mỗi một cái cây được assign vật liệu riêng biệt, hiển thị đôi ba cái cây thì không vấn đề gì nhưng nếu nguyên cái khu vườn thượng uyển hay cả cánh rừng thì frame rate sẽ drop.

Để kiểm tra điều này có thể xem cách trong Unreal Engine hiển thị Draw Call với mục tiêu debug

Draw call cũng dễ dàng bị ảnh hưởng nếu như sử dụng các material layer/blend quá nhiều lớp, phức tạp. Gì cũng có giới hạn của nó, phương án layer material là phù hợp đối ưu nhưng cũng là con dao hai lưỡi nếu nó trở nên thành cái container để engine phải xử lý phức tạp.

Có nhiều giải pháp cho việc xử lý draw call, ví dụ như gom nhiều object lại thành một mesh để sử dụng cùng một texture, như gom 2 3 cái cây lại thành một group.

Hoặc có thể phần cứng đang dùng Unreal Engine chưa phù hợp, có thể nâng cấp lên RTX GPU cho nhiều bộ nhớ và khả năng xử lý đồng loạt khủng hơn, dĩ nhiên đó là làm phim thì ok, nếu làm video game phát hành thì không phải người dùng khách hàng của cái game đều có khả năng sắm Ray Tracing ON.

Video trên hướng dẫn tối ưu draw call cho Mesh được vẽ lên màn hình thông qua plugin trên Marketplace. 

Phải hiểu rằng, một cái blueprint component cũng cost một lượt draw call. Project mở ra empty cũng tốn draw call vì lúc đó các graphics engine có sẵn vẫn chuẩn bị sẵn.

Tài liệu của Epic Games cũng có giải pháp cho xử lý kiểm soát draw calls trên CPU, thông qua công cụ CPU profiling. https://docs.unrealengine.com/4.26/en-US/TestingAndOptimization/PerformanceAndProfiling/CPU/

If you are CPU bound in the render thread, it is likely because of too many draw calls. This is a common problem and artists often have to combine draw calls to reduce the cost for that (e.g. combine multiple walls into one mesh)

The ProfileGPU command allows you to quickly identify the GPU cost of the various passes, sometimes down to the draw calls.

Chuẩn Bị 3D Model Đưa Vào Unreal Engine Từ Blender

Video dưới đây sẽ cho thấy import một cái model là chiếc siêu xe vào từ Blender vào Unreal Engine, thì vấn đề ở đây không phải là polycount mà là nên group các mesh có cùng một vật liệu lại với nhau sẽ giúp cho ổn định khung hình ngon lành.

Không phải lúc nào cũng là polycount đến mức cái xe low poly hay phải dùng GPU quá dữ dội, việc GPU và CPU draw calls là luôn sync với nhau. Những gì GPU hiển thị là do CPU xử lý những gì tải sẵn vào trong bộ nhớ và chờ để hiển thị, cái này gọi là back face culling sync với GPU.

mô hình dữ liệu truyền đi từ input tới CPU xử lý các dữ liệu lưu đang có trên engine và tiếp tục sync với GPU để render ra màn hình

Tin tốt lành là tôi tìm được cái video tiếng Việt cho các bạn chuẩn bị model để import vào Unreal Engine, dành cho các bạn còn hơi khó xử với tiếng nước ngoài

Nghẽn Cổ Chai - Bottle Neck

Frame sẽ drop nếu CPU hay GPU không đồng bộ, một trong hai thiết bị phải chờ để thiết bị còn lại xử lý xong (nghẽn cổ chai – bottleneck), xảy ra do CPU quá yếu so với GPU hay ngược lại là GPU quá kém so với khả năng của CPU.

Nếu do CPU, gọi đó là CPU bound, là mọi thứ chuyển động từ vị trí đến hiển thị. Có thể là Physics, AI, skeletal animations, projectiles, …

Nếu GPU bound thì có top 3 thứ nên xem xét drawcalls, dynamic shadows và translucency.

Khi tối ưu thì cũng cần biết đang bị cái gì để tối ưu chứ đôi khi làm thật nhiều xong không giải quyết được gì. Dùng công cụ profiling để thẩm tra. (Có một công cụ tích hợp vô Unreal Engine là RenderDoc, tải về miễn phí)

Ngoài ra thì nên kiểm tra shader material node, một cái mớ loằn ngoằn Material trông rất phức tạp và có vẻ sẽ thể hiện realistic như trong phim ảnh render offline là không cân xứng, vì Unreal Engine hoạt động theo phương án số lệnh gọi. Phải tối thiểu số lệnh gọi yêu cầu xử lý ở các đầu node xử lý pixel, tức giảm tính phức tạp của material lại.

một map vertex có thể giúp tạo ra các hiệu ứng tương tự destruction thế này, real-time ngay trong Unreal Engine

Giảm dung lượng của kiểu hiệu ứng Vertex Shader base animation, thường giúp thay đổi vị trí của vertices (rất hay dùng để áp dụng animation ví dụ như trường hợp để làm destruction real-time cái xe ở đây), để giảm cái này thì phải giảm sự phức tạp của poly mesh (một cái lưới 4 quad trong phần mềm 3D, qua tới Unreal Engine là toàn bộ polycount gấp đôi vì lưới 3 triangle), từng ấy vertices chắn chắn sẽ ảnh hưởng định mức của frame per seconds.

Định Mức Polycount

Trong Unreal Engine có chức năng tự động build LOD, cái này không cần build bằng tay, chỉ cần ban đầu có định mức polycount phù hợp là ổn.

Vậy, về số polycount thì tối ưu bao nhiêu là vừa ? đây là thông tin chung chung nhưng tối thiểu cũng có cái để canh.

Với PC và các máy console:
  • 2000~3000 là lý tưởng
  • 5000 bắt đầu có vấn đề
  • 10000 chắc chắn có chuyện
Với di động và VR 
  • Tối đa thì vài trăm, hạn hữu có thể ép cố gắng lên nghìn nhưng drop frame rate là thấy rõ.

Vẽ Lố - Overdraw

Hiện tượng này xảy ra do vấn đề nghẽn cổ chai phát sinh, lý do là khi được thả ra chạy thì lúc đó nhiều lệnh draw calls đã được gọi, và đồng thời rất nhiều draw calls sẽ gọi tới object vẽ đi vẽ lại cùng một pixel.Điều này đồng nghĩa GPU tiếp tục lag vì thời gian vẽ cho các thứ khác ít đi. Giảm hiệu quả khung hình, không thể ổn định độ mượt.Thường thì cũng ăn đòn do Alpha, transparent kiểu như khói, sương, băng giá, gương, .. đó là khi mà các pixel đè nhau ra vẽ – mà điều này là không tránh khỏi với các kiểu vật liệu trong suốt – transparent.

Overdraw cũng chính là tác nhân làm cho vật liệu kiểu translucent (kiểu như có alpha đục lỗ như lá, cỏ) sẽ luôn “xa xỉ” hơn so với vật liệu đục (opaque), đặc biệt là khi chúng xếp chồng nhau trong material stack. (nên dùng mask thay cho transparent để đỡ tốn).

Về mấy cái khói lữa, trước đây dùng sprite thì trong bộ particle CASCADE có tool particle cut out để trim cái phần dư khi các hiệu ứng chưa hoàn toàn fill vào hình sprite.

Khi visualize thường thấy màu tím hiển thị sáng lên. Giải pháp trong video.

Overshading

Điều này xảy ra khi engine cố gắng vẽ một pixel tạo bởi một tam giác quá bé (tí tẹo), khi xử lý render GPU sẽ xử lý theo giải thuật 2×2 (quad), để có thể tính được mip maps, vậy nên đó là một cái tam giác chỉ có một pixel, nhưng GPU vẫn vẽ ra thêm 3 pixel nữa cho đủ 4. (This is the way !!!!)

Đó là lý do mà một cái model được import vào game engine phải hết sức chăm chút và cân đo đong đếm, nhất là topology, như hình dưới cho thấy kiểu các hình cầu, hình trụ mà có phần tam giác tụ lại là không dùng được, phải xử lý dùng kiểu quad (coi thêm cách xử lý dựng hình đúng topology) hoặc ngay cả có cho sản xuất phim cinematic render offline thì việc dựng hình cho sản xuất cũng là tối quan trọng, cần skill thực thụ.

không để các tam giác bé tẹo ở các chóp

Hoặc dùng cái tool này cho Maya miễn phí, lẹ. Link: https://www.artstation.com/marketplace/p/Xmkz1/maya-quadprimitive-beta

Channel Packing

Vốn dĩ tôi có sở thích cá nhân là rất thích làm phim hoạt hình và game trên mobile, nên khi tiếp xúc với Unreal Engine mục tiêu của tôi là làm các thứ tương tác, sau đó có thể packing và triển khai trên năng lực của smartphone. 

Mặc dù smartphone bây giờ (2021) thì hơn hẳn cái máy tính hồi thuở bọn tôi thi cuộc thi Samsung mobile game 2004 rất nhiều, chưa kể hồi trước viết bằng Java, khổ trăm điều, tôi kém giải thuật nhất bọn, rành nhất là đồ họa thôi, vậy mà cả bọn giỏi code và giải thuật trong nhóm lui cui mãi cũng rất khó hiệu chỉnh được tối ưu cho cái điện thoại chạy cái game Sơn Tinh Thủy Tinh mượt.

Sau này rất nhiều năm khi tôi vô tình có đầu tư cho một nhóm nhỏ viết game trên mobile ngoài công ty tôi đang kinh doanh, thì tôi có giúp đỡ trong nhóm về mặt kỹ thuật trong một chập cuối tuần. Tôi biết tới việc để tối ưu được băng thông lượt đọc file trên di động, còn có thể pack các channel shader của một ảnh texture vào trong chính nó.

Nếu mở trong photoshop phần channel, mỗi map sẽ được bỏ vào một channel của file texture này

Mỗi ảnh RGB sẽ có 4 channel từ Red Green Blue và Alpha (RGBA), các shader như Red cho Roughness, Green cho Metalness, Blue cho Ambient Occlusion, Alpha sẽ là “transparent” đây là tôi nói luôn cho việc tối ưu shader bây giờ, shader PBR, chứ xưa là không có mấy shader này. Việc này giúp giảm được số lượng hình texture – giảm được lưu lượng băng thông, kết hợp với bộ nén, ví dụ như Unreal Engine thì có các bộ nén DX1. Thường thì kênh màu xanh lá cây Green sẽ tốn dữ liệu nhất.

Ở thời điểm này thì pack channel rất phổ biến và các pháp sư của Unreal Engine thường xuyên tìm cách làm tốt nhất, các bạn có thể tìm đọc ở đây về Packing Channel, tôi để cái video mới 2021 cho chắc ăn, vì nhiều bạn mò theo kiến thức cũ mặc dù nó nền tảng hơn nhưng ngại là sẽ không có kết quả ăn ngay

Năm 2019 tôi thấy có vài topic phản ảnh về việc, mặc dù giảm băng thông texture nhưng khả năng Draw Calls sẽ tăng, về việc cân đối này phải nghiên cứu thêm, tôi hiện đang bận làm tạoc tác mày mò hiệu ứng FX với Houdini nên không có thời gian với Unreal Engine cho năm nay. 

Unity Mô Tả Draw Calls

Với tài liệu của Unity cho thấy chi tiết https://docs.unity3d.com/Manual/DrawCallBatching.html

Thú vị của game engine bây giờ là có thể xem cả hai tài liệu để cùng xử lý cho một vấn đề chung của đồ họa nếu nó không quá đặc thù riêng của game engine.

Với Unity có 2 giải pháp xử lý Draw Calls là static và dynamic, với giải pháp thông minh là dynamic, Unity có khả năng tự batch nhóm các GameObject thành một lượt gọi lên vẽ draw call nếu nó cùng vật liệu Material và vài điều kiện khác. Việc này là tự động. (và cũng có nhưng nhị, tuy nhiên này kia, vào tài liệu đọc để giải quyết).

Các phương án tối ưu draw calls trong Unity cũng tương tự như ở trên đã chia sẻ cho Unreal Engine.

Trong hình là hướng dẫn tối ưu draw call cho unity game chạy thiệt nhanh. Click vô dẫn tới trang hướng dẫn.

Unreal Engine 5

Nanite xuất hiện. Giải pháp giúp vẽ tỉ tỉ tam giác mesh lên màn hình mà không giật lag. Một giải pháp vô tiền khoán hậu với Unreal Engine 5. Hiện tại chưa xử lý được với cây cỏ và các kiểu cây cỏ chuyển động, nhưng chắc cái này chỉ là vấn đề thời gian. Tôi không phải chuyên gia, vụ này tôi chịu, 

Epic Games cho thấy, giờ đây frame per second (frame budget – định mức frame) không còn bị giới hạn bởi polycount, draw call và khả năng tối đa sử dụng bộ nhớ lưu mesh.

Tuy nhiên, phần note của Unreal Engine 5 doc cũng nói rõ là vẫn bị giới hạn vì những điều kiện khác, tại đây.

… there are practical limits that still remain. For example, instance counts, triangles per mesh, material complexity, output resolution, and performance should be carefully measured for any combination of content and hardware.

Giải thích Nanite thiệt nhanh dưới đây, 2 phút.

Post Author: Vu Pham