Qual è il punto di aggiornamento rendering indipendente in un loop di gioco?


74

Ci sono dozzine di articoli, libri e discussioni là fuori sui circuiti di gioco. Tuttavia, mi capita spesso di imbattermi in qualcosa del genere:

while(running)
{
    processInput();
    while(isTimeForUpdate)
    {
        update();
    }
    render();
}

Ciò che sostanzialmente mi infastidisce di questo approccio è il rendering "indipendente dall'aggiornamento", ad esempio il rendering di un frame quando non vi è alcun cambiamento. Quindi la mia domanda è: perché questo approccio viene spesso insegnato?


6
Personalmente, ho trovato gameprogrammingpatterns.com/game-loop.html una spiegazione utile
Niels

12
Non tutte le modifiche al rendering si riflettono nello stato del gioco. E ho il sospetto che tu fraintenda il punto di quel pezzo di codice: ti consente di aggiornare più volte per rendering, non più volte per aggiornamento.
Luaan,

13
Si noti che il codice legge while (isTimeForUpdate), non if (isTimeForUpdate). L'obiettivo principale non è render()quando non c'è stato un update(), ma update()ripetutamente tra una render()s e l'altra. Indipendentemente da ciò, entrambe le situazioni hanno usi validi. Il primo sarebbe valido se lo stato può cambiare al di fuori della propria updatefunzione, ad esempio, modificare ciò che viene reso in base allo stato implicito come l'ora corrente. Quest'ultimo è valido perché dà al tuo motore fisico la possibilità di fare molti piccoli aggiornamenti precisi, che ad esempio riduce la possibilità di "deformare" attraverso gli ostacoli.
Thierry,

Una domanda più logica sarebbe "qual è il punto del loop di gioco di rendering dipendente
dall'aggiornamento

1
Ogni quanto tempo hai un aggiornamento che non fa nulla? Non aggiorni nemmeno animazioni di sfondo o orologi su schermo?
pjc50,

Risposte:


113

C'è una lunga storia di come siamo arrivati ​​a questa convenzione comune, con molte sfide affascinanti lungo la strada, quindi cercherò di motivarlo in più fasi:

1. Problema: i dispositivi funzionano a velocità diverse

Hai mai provato a giocare a un vecchio gioco DOS su un PC moderno, e funziona in modo incredibilmente veloce - solo una sfocatura?

Molti vecchi giochi avevano un ciclo di aggiornamento molto ingenuo: raccoglievano input, aggiornavano lo stato del gioco e eseguivano il rendering alla velocità consentita dall'hardware, senza tenere conto di quanto tempo fosse trascorso. Ciò significa che non appena l'hardware cambia, il gameplay cambia.

In genere desideriamo che i nostri giocatori abbiano un'esperienza e un'esperienza di gioco coerenti su una vasta gamma di dispositivi (purché soddisfino alcune specifiche minime) sia che utilizzino il telefono dell'anno scorso o il modello più recente, un desktop di gioco di fascia alta o un laptop di livello medio.

In particolare, per i giochi competitivi (multiplayer o classifiche) non vogliamo che i giocatori che corrono su un determinato dispositivo abbiano un vantaggio sugli altri perché possono correre più velocemente o avere più tempo per reagire.

La soluzione infallibile qui è bloccare la velocità con cui eseguiamo gli aggiornamenti dello stato di gioco. In questo modo possiamo garantire che i risultati saranno sempre gli stessi.

2. Quindi, perché non bloccare semplicemente il framerate (ad es. Usando VSync) ed eseguire comunque gli aggiornamenti e il rendering dello stato di gioco in lockstep?

Questo può funzionare, ma non è sempre appetibile per il pubblico. C'è stato molto tempo quando correre a 30 fps è stato considerato il gold standard per i giochi. Ora, i giocatori si aspettano abitualmente 60 fps come barra minima, specialmente nei giochi d'azione multiplayer, e alcuni titoli più vecchi ora sembrano notevolmente mossi poiché le nostre aspettative sono cambiate. C'è anche un gruppo vocale di giocatori PC in particolare che si oppongono a blocchi di framerate. Hanno pagato molto per il loro hardware all'avanguardia e vogliono essere in grado di utilizzare quel muscolo di calcolo per il rendering più fluido e con la massima fedeltà di cui è capace.

Nella realtà virtuale in particolare, il framerate è il re e lo standard continua a salire. All'inizio della recente rinascita della realtà virtuale, i giochi spesso correvano intorno ai 60 fps. Ora 90 è più standard e harware come il PSVR sta iniziando a supportare 120. Questo potrebbe continuare a salire ancora. Quindi, se un gioco VR limita il suo framerate a ciò che è fattibile e accettato oggi, è probabile che rimanga indietro mentre l'hardware e le aspettative si sviluppano ulteriormente.

(Di norma, diffidare quando viene detto "i giocatori non possono percepire nulla più velocemente di XXX" poiché di solito si basa su un particolare tipo di "percezione", come riconoscere un frame in sequenza. La percezione della continuità del movimento è generalmente molto più sensibile. )

L'ultimo problema qui è che un gioco che usa un framerate bloccato deve anche essere conservativo: se ti capita di cogliere un momento del gioco in cui stai aggiornando e mostrando un numero insolitamente alto di oggetti, non vuoi perderti il ​​frame scadenza e causare una balbuzie o un intoppo evidente. Pertanto, è necessario impostare budget per i contenuti abbastanza bassi da lasciare spazio o investire in funzioni di regolazione dinamica della qualità più complicate per evitare di agganciare l'intera esperienza di gioco alle prestazioni peggiori sull'hardware con specifiche minime.

Ciò può essere particolarmente problematico se i problemi di prestazioni si manifestano in una fase avanzata dello sviluppo, quando tutti i sistemi esistenti sono costruiti e messi a punto assumendo un framerate di rendering a blocchi che ora non si può sempre colpire. Il disaccoppiamento delle percentuali di aggiornamento e rendering offre una maggiore flessibilità per gestire la variabilità delle prestazioni.

3. L'aggiornamento a un timestep fisso non presenta gli stessi problemi di (2)?

Penso che questo sia il frutto della domanda originale: se disaccoppiamo i nostri aggiornamenti e talvolta eseguiamo il rendering di due fotogrammi senza aggiornamenti dello stato del gioco, allora non è lo stesso del rendering lockstep con un framerate inferiore, dal momento che non ci sono cambiamenti visibili su lo schermo?

Esistono diversi modi in cui i giochi utilizzano il disaccoppiamento di questi aggiornamenti con buoni risultati:

a) La velocità di aggiornamento può essere più veloce del framerate renderizzato

Come tyjkenn nota in un'altra risposta, la fisica in particolare viene spesso calpestata a una frequenza più elevata rispetto al rendering, il che aiuta a ridurre al minimo gli errori di integrazione e a fornire collisioni più accurate. Quindi, invece di avere 0 o 1 aggiornamenti tra i frame renderizzati potresti avere 5 o 10 o 50.

Ora il rendering del giocatore a 120 fps può ottenere 2 aggiornamenti per fotogramma, mentre il giocatore con rendering hardware con specifiche inferiori a 30 fps ottiene 8 aggiornamenti per fotogramma ed entrambi i loro giochi girano alla stessa velocità di gioco-tick-per-tempo reale al secondo. L'hardware migliore lo rende più fluido, ma non altera radicalmente il funzionamento del gameplay.

Qui c'è il rischio che, se la frequenza di aggiornamento non corrisponde al framerate, è possibile ottenere una "frequenza di battimento" tra i due . Per esempio. la maggior parte dei frame ha abbastanza tempo per 4 aggiornamenti dello stato del gioco e un po 'di avanzi, quindi ogni tanto abbiamo abbastanza risparmi per fare 5 aggiornamenti in un frame, facendo un piccolo salto o balbettare nel movimento. Questo può essere risolto da ...

b) Interpolare (o estrapolare) lo stato del gioco tra gli aggiornamenti

Qui lasceremo spesso vivere lo stato del gioco in un timestep fisso in futuro e memorizzeremo abbastanza informazioni dai 2 stati più recenti che possiamo rendere un punto arbitrario tra di loro. Quindi quando siamo pronti a mostrare un nuovo fotogramma sullo schermo, ci mescoliamo al momento appropriato solo a scopo di visualizzazione (cioè non modifichiamo lo stato di gioco sottostante qui)

Se fatto bene, questo rende il movimento morbido, e aiuta anche a mascherare alcune fluttuazioni nel framerate, purché non scendiamo troppo in basso.

c) Aggiunta di fluidità ai cambiamenti dello stato non di gioco

Anche senza interpolare lo stato di gioco, possiamo comunque ottenere delle vittorie fluide.

Cambiamenti puramente visivi come l'animazione dei personaggi, i sistemi di particelle o VFX e gli elementi dell'interfaccia utente come HUD, spesso si aggiornano separatamente dal timestep fisso dello stato di gioco. Questo significa che se stiamo controllando il nostro stato di gioco più volte per fotogramma, non stiamo pagando il costo con ogni tick - solo sul passaggio di rendering finale. Al contrario, ridimensioniamo la velocità di riproduzione di questi effetti in modo che corrisponda alla lunghezza del fotogramma, in modo che vengano riprodotti in modo uniforme come consente il framerate di rendering, senza influire sulla velocità o sull'equità del gioco, come discusso in (1).

Anche il movimento della telecamera può fare questo - specialmente in VR, a volte mostriamo lo stesso fotogramma più di una volta ma lo riproiettiamo per tener conto del movimento della testa del giocatore tra , in modo da poter migliorare la latenza e il comfort percepiti, anche se possiamo rendere nativamente tutto così velocemente. Alcuni sistemi di streaming di gioco (in cui il gioco è in esecuzione su un server e il lettore esegue solo un thin client) utilizzano anche una versione di questo.

4. Perché non usare quello stile (c) per tutto? Se funziona per l'animazione e l'interfaccia utente, non possiamo semplicemente ridimensionare gli aggiornamenti dello stato di gioco in modo che corrispondano al framerate corrente?

Sì * questo è possibile, ma no non è semplice.

Questa risposta è già un po 'lunga, quindi non entrerò in tutti i dettagli cruenti, solo un breve riassunto:

  • Moltiplicare per le deltaTimeopere per adattarsi agli aggiornamenti di lunghezza variabile per il cambiamento lineare (ad es. Movimento a velocità costante, conto alla rovescia di un timer o avanzamento lungo una sequenza temporale di animazione)

  • Sfortunatamente, molti aspetti dei giochi non sono lineari . Anche qualcosa di semplice come la gravità richiede tecniche di integrazione più sofisticate o sottofasi a risoluzione più elevata per evitare risultati divergenti in framerate diverse. L'input e il controllo del giocatore è esso stesso un'enorme fonte di non linearità.

  • In particolare, i risultati del rilevamento discreto delle collisioni e della risoluzione dipendono dalla velocità di aggiornamento, causando errori di tunneling e jitter se i frame diventano troppo lunghi. Quindi un framerate variabile ci costringe a utilizzare metodi di rilevazione di collisione continua più complessi / costosi su più dei nostri contenuti, o tollerare la variabilità nella nostra fisica. Anche il rilevamento continuo delle collisioni incontra difficoltà quando gli oggetti si muovono negli archi, richiedendo tempi più brevi ...

Quindi, nel caso generale di un gioco di media complessità, mantenere un comportamento coerente e l'equità interamente attraverso il deltaTimeridimensionamento è da qualche parte tra molto difficile e intensivo di manutenzione a assolutamente impossibile.

La standardizzazione di una frequenza di aggiornamento ci consente di garantire un comportamento più coerente in una serie di condizioni , spesso con un codice più semplice.

Mantenere la frequenza di aggiornamento disaccoppiata dal rendering ci dà la flessibilità di controllare la fluidità e le prestazioni dell'esperienza senza alterare la logica di gioco .

Anche allora non otteniamo mai un'indipendenza framerata veramente "perfetta", ma come tanti approcci nei giochi ci offre un metodo controllabile per entrare in "abbastanza buono" per le esigenze di un determinato gioco. Ecco perché viene comunemente insegnato come utile punto di partenza.


2
Nei casi in cui tutto utilizzerà lo stesso frame rate, la sincronizzazione di tutto può ridurre al minimo il ritardo tra il campionamento del controller e la reazione dello stato del gioco. Per molti giochi su alcune macchine più vecchie, il tempo peggiore sarebbe inferiore a 17 ms (i controlli vengono letti all'inizio del bianco verticale, quindi vengono applicate le modifiche allo stato del gioco e viene visualizzato il fotogramma successivo mentre il raggio si sposta sullo schermo) . Il disaccoppiamento delle cose comporta spesso un aumento significativo nel momento peggiore.
supercat

3
Sebbene sia vero che pipeline di aggiornamento più complicate semplificano l'introduzione involontaria della latenza, non è una conseguenza necessaria dell'approccio disaccoppiato se implementato correttamente. In effetti, potremmo anche essere in grado di ridurre la latenza. Facciamo un gioco con rendering a 60 fps. Con un read-update-renderpasso sicuro, la latenza nel caso peggiore è di 17 ms (ignorando la pipeline grafica e la latenza di visualizzazione per ora). Con un (read-update)x(n>1)-renderloop disaccoppiato allo stesso framerate, la latenza nel caso peggiore può essere solo la stessa o migliore perché controlliamo e agiamo sull'input con frequenza o più. :)
DMGregory

3
Su un'interessante nota a margine sui vecchi giochi senza tenere conto del tempo reale trascorso, il gioco arcade Space Invaders originale aveva un problema tecnico causato dal potere di rendering, dove il giocatore distruggeva le navi nemiche, il rendering e quindi gli aggiornamenti del gioco, avrebbero accelerato, risultando, accidentalmente , nell'iconica curva di difficoltà del gioco.
Oskuro,

1
@DMGregory: se le cose vengono eseguite in modo asincrono, sarà possibile che si verifichi un cambio di controllo subito dopo che il loop di gioco esegue il polling dei controlli, il ciclo dell'evento di gioco dopo quello corrente termina subito dopo che il loop di rendering prende lo stato del gioco e perché il loop di rendering dopo quello attuale finisca subito dopo che il sistema di output video ha acquisito il frame buffer corrente, quindi il tempo peggiore finisce per essere solo timido di due tempi di loop di gioco più due tempi di rendering più due periodi di frame. Sincronizzare correttamente le cose potrebbe dimezzarlo.
Supercat

1
@Oskuro: non è stato un problema tecnico. La velocità del ciclo di aggiornamento rimane costante indipendentemente dal numero di invasori sullo schermo, ma il gioco non disegna tutti gli invasori su ogni ciclo di aggiornamento.
supercat

8

Le altre risposte sono buone e parlano del perché il loop di gioco esiste e dovrebbe essere separato dal loop di rendering. Tuttavia, come per l'esempio specifico di "Perché eseguire il rendering di un frame quando non ci sono state modifiche?" Dipende solo dall'hardware e dalla complessità.

Le schede video sono macchine statali e sono davvero brave a fare sempre la stessa cosa. Se rendi solo le cose che sono cambiate, in realtà è più costoso, non meno. Nella maggior parte degli scenari, non c'è molto di statico, se ti sposti leggermente a sinistra in un gioco FPS, hai modificato i dati pixel del 98% delle cose sullo schermo, potresti anche rendere l'intero fotogramma.

Ma soprattutto, complessità. Tenere traccia di tutto ciò che è cambiato durante un aggiornamento è molto più costoso perché è necessario rielaborare tutto o tenere traccia del vecchio risultato di un certo algoritmo, confrontarlo con il nuovo risultato e renderizzare quel pixel solo se la modifica è diversa. Dipende dal sistema.

Il design dell'hardware ecc. È ampiamente ottimizzato per le convenzioni attuali e una macchina a stati è stata un buon modello da cui partire.


5
C'è una distinzione da fare qui tra il rendering (tutto) solo quando qualcosa è cambiato (ciò che la domanda chiede) e il rendering solo delle parti che sono cambiate (ciò che descrive la tua risposta).
DMGregory

È vero, e ho cercato di fare questa distinzione tra il primo e il secondo paragrafo. È raro che un frame non cambi affatto, quindi ho pensato che fosse importante prendere questo punto di vista insieme alla tua risposta completa.
Waddles,

Oltre a ciò, noterei che non c'è motivo di non eseguire il rendering. Sai che hai sempre tempo per le tue chiamate di rendering in ogni frame (è meglio sapere che hai sempre tempo per le tue chiamate di rendering in ogni frame!) Quindi c'è molto poco danno nel fare un rendering "non necessario", specialmente perché questo caso essenzialmente non si presenta mai in pratica.
Steven Stadnicki,

@StevenStadnicki E 'vero che non c'è un grande tempo costo per rendere tutto ciò ogni fotogramma (in quanto è necessario disporre di tempo nel vostro budget per farlo comunque ogni volta che un sacco di cambiamenti di stato in una sola volta). Ma per dispositivi portatili come laptop, tablet, telefoni o sistemi di gioco portatili, potrebbe essere opportuno evitare il rendering ridondante per utilizzare in modo efficiente la batteria del lettore . Ciò si applica principalmente ai giochi pesanti dell'interfaccia utente in cui ampie parti dello schermo potrebbero rimanere invariate per i fotogrammi tra le azioni dei giocatori e non saranno sempre pratici da implementare a seconda dell'architettura del gioco.
DMGregory

6

Il rendering è di solito il processo più lento nel loop del gioco. Gli umani non notano facilmente una differenza in un frame rate più veloce di 60, quindi spesso è meno importante perdere tempo a renderlo più velocemente di così. Tuttavia, ci sono altri processi che trarrebbero maggiori benefici da una velocità maggiore. La fisica è una. Una modifica troppo grande in un loop può causare il glitch di oggetti oltre i muri. Ci possono essere modi per aggirare semplici errori di collisione con incrementi più grandi, ma per molte interazioni fisiche complesse, non otterrai la stessa precisione. Se il ciclo della fisica viene eseguito più frequentemente, c'è meno possibilità di problemi tecnici, poiché gli oggetti possono essere spostati con incrementi più piccoli senza essere renderizzati ogni volta. Più risorse vanno al motore fisico sensibile e meno si sprecano nel disegnare più frame che l'utente non può vedere.

Ciò è particolarmente importante nei giochi con più grafica. Se ci fosse un rendering per ogni ciclo di gioco e un giocatore non avesse la macchina più potente, potrebbero esserci dei punti nel gioco in cui il fps scende a 30 o 40. Mentre questo sarebbe comunque un frame rate non del tutto orribile, il il gioco inizierebbe ad essere abbastanza lento se provassimo a mantenere ogni cambiamento di fisica ragionevolmente piccolo per evitare glitch. Il giocatore sarebbe infastidito dal fatto che il suo personaggio percorra solo la metà della velocità normale. Se la velocità di rendering fosse indipendente dal resto del loop, tuttavia, il giocatore sarebbe in grado di rimanere a una velocità di camminata fissa nonostante il calo della frequenza dei fotogrammi.


1
O, ad esempio, misurare il tempo tra le zecche e calcolare fino a che punto il personaggio dovrebbe andare in quel momento? I giorni delle animazioni di sprite fisse sono molto lontani!
Graham,

2
Gli umani non possono percepire le cose più velocemente di 60 fps senza alias temporale, ma il frame rate necessario per ottenere un movimento regolare in assenza di motion blur può essere molto più alto di quello. Al di là di una certa velocità, una ruota che gira dovrebbe apparire come una sfocatura, ma se il software non applica la sfocatura da movimento e la ruota gira di più di mezzo raggio per fotogramma, la ruota potrebbe apparire male anche se la frequenza dei fotogrammi era 1000 fps.
supercat

1
@Graham, allora ti imbatti nel problema dal mio primo paragrafo, in cui cose come la fisica si comportano in modo anomalo con il cambiamento maggiore per tick. Se il framerate scende abbastanza in basso, compensare con cambiamenti più grandi potrebbe far correre un giocatore attraverso un muro, mancando completamente la sua hit box.
tyjkenn,

1
Gli umani di solito non riescono a percepire più velocemente di 60 fps, quindi è inutile sprecare risorse per renderizzare più velocemente di così.Metto in dubbio questa affermazione. Il rendering degli HMD VR a 90Hz è in aumento. Credimi quando ti dico che puoi Certamente percepire la differenza tra 90Hz e 60Hz su un auricolare. Inoltre, ultimamente ho visto tanti giochi collegati alla CPU come associati alla GPU. Dire "il rendering è di solito il processo più lento" non è necessariamente vero.
3Dave il

@DavidLively, quindi "inutile" potrebbe essere stato troppo esagerato. Intendevo dire che il rendering tende ad essere un collo di bottiglia e la maggior parte dei giochi sembra a 60 fps. Certamente ci sono effetti che sono importanti in alcuni tipi di giochi come con VR che puoi ottenere solo con frame rate più veloci, ma quelli sembrano l'eccezione piuttosto che la norma. Se avessi eseguito un gioco su una macchina più lenta, avrei preferito avere una fisica funzionante piuttosto che un frame rate veloce appena percettibile.
tyjkenn,

4

Una costruzione come quella in questione può avere senso se il sottosistema di rendering ha una nozione di "tempo trascorso dall'ultimo rendering" .

Considera, ad esempio, un approccio in cui la posizione di un oggetto nel mondo di gioco è rappresentata attraverso (x,y,z)coordinate fisse con un approccio che memorizza ulteriormente il vettore di movimento corrente (dx,dy,dz). Ora, potresti scrivere il tuo loop di gioco in modo che il cambiamento della posizione debba avvenire nel updatemetodo, ma puoi anche progettarlo in modo che il cambiamento del movimento debba avvenire durante update. Con quest'ultimo approccio, anche se il tuo stato di gioco in realtà non cambierà fino al successivo update, arender-funzione chiamata ad una frequenza più elevata potrebbe già disegnare l'oggetto in una posizione leggermente aggiornata. Mentre questo porta tecnicamente a una discrepanza tra ciò che vedi e ciò che viene rappresentato internamente, la differenza è abbastanza piccola da non avere importanza per la maggior parte degli aspetti pratici, tuttavia consente alle animazioni di apparire molto più fluide.

Prevedere "il futuro" del tuo stato di gioco (nonostante il rischio di sbagliare) può essere una buona idea quando prendi ad esempio in considerazione le latenze dell'input di rete.


4

Oltre ad altre risposte ...

Il controllo del cambio di stato richiede un'elaborazione significativa. Se è necessario un tempo di elaborazione simile (o superiore!) Per verificare la presenza di modifiche, rispetto all'effettiva elaborazione, la situazione non è migliorata. Nel caso del rendering di un'immagine, come dice @Waddles, è una scheda video davvero brava a fare sempre la stessa cosa stupida, ed è più costoso controllare ogni blocco di dati per le modifiche di quanto non sia semplicemente trasferirla attraverso alla scheda video per l'elaborazione. Inoltre, se il rendering è gameplay, è davvero improbabile che lo schermo non sia cambiato nell'ultimo tick.

Supponiamo inoltre che il rendering richieda un tempo di processore significativo. Questo dipende molto dal processore e dalla scheda grafica. Da molti anni l'attenzione si concentra sull'offload di lavori di rendering progressivamente più sofisticati sulla scheda grafica e sulla riduzione dell'input di rendering necessario dal processore. Idealmente, la render()chiamata del processore dovrebbe semplicemente impostare un trasferimento DMA e il gioco è fatto. Il trasferimento dei dati sulla scheda grafica viene quindi delegato al controller di memoria e la produzione dell'immagine viene delegata alla scheda grafica. Possono farlo a loro tempo, mentre il processore in parallelocontinua con la fisica, il motore di gioco e tutte le altre cose che un processore fa meglio. Ovviamente la realtà è molto più complicata di così, ma essere in grado di scaricare il lavoro su altre parti del sistema è anche un fattore significativo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.