Unreal Engine 4: gli strumenti, come funziona, cosa cambia
Riprendiamo l'analisi delle caratteristiche più importanti di Unreal Engine 4, in modo da poter capire come funziona alla base un motore grafico di ultima generazione con tutte le sue funzioni, gli strumenti e le varie opzioni grafiche.
di Rosario Grasso pubblicato il 28 Luglio 2015 nel canale VideogamesEpic
Bilanciare la qualità con le prestazioni
Le prestazioni sono un tema onnipresente nella creazione di videogiochi con grafica processata in tempo reale. Al fine di creare l'illusione che le immagini siano effettivamente in movimento, bisogna garantire un minimo di 15fps ma sappiamo bene che oggi gli utenti non sono soddisfatti se non possono godere almeno di 30fps o 60fps o anche più nel caso del PC.
Unreal Engine 4 mette a disposizione diversi strumenti per ottimizzare le prestazioni. Come prima cosa è ovviamente cruciale disporre di uno strumento che segnali quali elementi grafici stanno consumando più velocemente e intensamente le risorse di elaborazione del sistema. Si tratta di quelli che Epic definisce strumenti di Profiling.
Il rendering degli oggetti a schermo è ovviamente l'area che comporta il maggior consumo di risorse. Aggiungere oggetti, aumentare la risoluzione, introdurre altre luci e gestire materiali con molte proprietà sono tutti elementi che implicano un forte impatto prestazionale. Fortunatamente la grafica 3D dà molto spazio agli sviluppatori nel bilanciamento tra qualità e prestazioni, con Unreal Engine 4 che offre uno stratificato supporto per equilibrare le cose al meglio anche per l'hardware prefissato delle console. Ad esempio, si possono pre-configurare i limiti hardware imposti dalle console modificando il file ConsoleVariables.ini con la sintassi cvarname=value.
Per prima cosa occorre costruire uno scenario di riferimento con qualche elemento di gioco e delle lightmap, in modo da poterlo esportare su configurazioni differenti. Ci sono dei fattori che potrebbero impedire di ottenere dei risultati veramente attendibili: ad esempio, la presenza del Content Browser comporta un consumo, seppure minimo, di risorse. Quindi, è meglio eseguire le misure direttamente in-game, servendosi dei comandi sulle statistiche.
Ue4 calcola le prestazioni in millesimi di secondo, invece che in frame per secondo, perché questo consente di essere più accurati. Nel caso di frame rate limitato da V-Sync, ad esempio, un singolo fotogramma deve essere renderizzato in 33.3ms per ottenere i 30fps fissi, o in 16.6ms per i 60fps. Non bisogna aspettarsi che più sono semplici le scene e automaticamente maggiore sarà il frame rate, semplicemente perché alcune tecniche di rendering sono ottimizzate per garantire fluidità in caso di elevato dettaglio poligonale: è il caso, ad esempio, del rendering differito.
I moderni hardware gestiscono diverse unità contemporaneamente, come l'elaborazione di triangoli/vertex/pixel e la gestione della memoria nel caso della GPU. In molti casi, soprattutto in ambiente DirectX 11, queste unità devono aspettare che il lavoro di altre unità sia stato completato prima di iniziare il proprio lavoro. Per prima cosa, quindi, bisogna individuare qual è elemento che sta vincolando il lavoro delle altre unità. Ottimizzare la cosa sbagliata, infatti, non solo è una perdita di tempo, ma addirittura può comportare un deterioramento delle prestazioni e introdurre bug. Dopo aver migliorato un'area, inoltre, potrebbe diventare evidente un nuovo collo di bottiglia, che richiede un'ulteriore ottimizzazione.
Per prima cosa bisogna stabilire se il frame rate è limitato dalla CPU o dalla GPU e modificare qualche parametro, come la risoluzione, per vedere se ci sono effetti. Nella stat unit possiamo vedere i parametri sul rendering divisi in tre categorie: Game (CPU game thread), Draw (CPU render thread) e GPU (GPU). Nell'esempio qui di seguito si può notare come sia la GPU il fattore di maggiore limitazione per il frame rate.
La GPU, in particolare, dispone di tante unità che lavorano in parallelo, per cui è opportuno andare a verificare come si comportano. Lo si può fare tramite il comando ProfileGPU richiamabile da console o con la Shortcut Ctrl+Shift+,. I dati mostrati da questo strumento sono quasi completamente accurati, ma alcune ottimizzazioni potrebbero portare a una differenziazione fra tali dati e ciò che accade realmente. Per esempio, alcuni driver tendono a ottimizzare i costi dell'utilizzo degli shader.
Lo sviluppatore che vuole fare un buon lavoro dovrebbe andare a spulciare i dati per ogni frame e verificare quali sono le maggiori voci di costo. Per esempio il rendering differito va a richiedere più tempo di elaborazione con l'aumentare dei pixel coinvolti nel rendering e nel caso di associazioni con funzioni di illuminazione, profili IES, ricezione di ombre, luci applicate su aree e modelli di ombreggiatura complessi. Lo sviluppatore può venire a compromessi con tutti i parametri grafici, come la qualità dell'occlusione ambientale o del rendering e il filtraggio delle Shadow Map.
Nella maggior parte dei casi le prestazioni diminuiscono all'aumentare dei pixel, cosa che si può misurare con lo strumento r.SetRes. Tradizionalmente si tratta di un limite imposto dalla bandwidth della memoria o causato da un eccesso di calcoli matematici devoluti alla ALU o, ancora, per via del fatto che specifiche unità, come MRT, risultano saturate.
In alcuni casi diminuire la risoluzione dell'intera immagine non è sufficiente: ad esempio, la risoluzione delle shadow map non è dipendente dalla risoluzione complessiva dell'immagine. È importante verificare anche quale elemento incide di più nel rendering delle shadow map, se è ingrossato dall'elaborazione dei vertici o da quella dei triangoli, il che potrebbe verificarsi in caso si sovraimpiego di mesh, di una non corretta impostazione del LOD, per l'uso di tassellatura. Il rendering delle Shadow Map costa di più se si aumenta il numero di luci coinvolte nella scena, il numero di Cubemap o il numero di oggetti che proiettano ombre, così come la densità dei vertici che li compongono. I singoli oggetti non hanno un costo solo per la loro semplice esistenza nella scena, ma anche per come variano il "gioco" di ombre e luci. Dopo aver controllato le shadow map è opportuno verificare anche le mesh: mesh troppo dense, infatti, impattano sensibilmente sulle prestazioni.
Se cambiare la risoluzione, invece, non comporta benefici sul piano delle prestazioni, allora è possibile che il collo di bottiglia riguardi elementi legati all'elaborazione dei vertici, come i vertex shader o la tassellatura. Bisogna verificare se siano coinvolti troppi vertici andando a usare lo strumento Level of Detail delle mesh, la complessità dei materiali (potrebbero usare texture con mip mapping insufficiente), se sono previsti livelli di tassellatura troppo complessi, se sono presenti troppi attributi per i vertici o se vengono usati vertici a sproposito effettivamente non utili o non evidenti all'occhio del giocatore.
In casi più rari, il problema potrebbe dipendere da un inefficiente uso dei vertex shader, evidentemente sostituito da mesh con un numero di poligoni troppo alto per ottenere lo stesso effetto che invece sarebbe possibile avere con gli shader.
Quanto allo strumento Profiler, si tratta dell'evoluzione dello strumento StatsViewer di Unreal Engine 3 e serve per monitorare le prestazioni e i fattori di consumo di risorse di elaborazione. Per tutti i dettagli sul funzionamento di questo strumento e sulla sua implementazione vi rimandiamo alla documentazione.