Pagina principale

Motion blur

L'effetto di motion blur prevede che gli oggetti che si muovono velocemente risultino sfocati all'occhio umano in misura tanto maggiore quanto più velocemente si spostano.

Con questi presupposti, se attivato, il motion blur abilita due effetti:

  1. quando l'elicottero si sposta nella scena, il terreno e gli altri oggetti lasciano una breve scia sfumata tanto più pronunciata quanto più intensa è la velocità del velivolo.
  2. Le pale dell'elicottero – quando quest'ultimo è acceso – appaiono leggermente più “trasparenti” per meglio simulare l'elevata velocità di rotazione.

Per il primo risultato si fa un utilizzo abbastanza semplice dell'accumulation buffer (AB): si salva cioè una parte del frame renderizzato in un secondo buffer e al frame successivo si fondono adeguatamente le due immagini, col risultato che tutti gli oggetti che erano stati salvati nell'AB ricompaiono – leggermente sfumati – nel nuovo frame e così via anche nel successivo finché non verranno tanto sfumati da risultare invisibili. Il risultato complessivo mostra come i vari elementi della scena lasciano una leggera scia dietro di loro.

L'effetto appena descritto è realizzato nelle fasi finali della procedura di rendering renderScene() in game.c. I passi da seguire sono sostanzialmente i seguenti:

  1. glAccum(GL_MULT, blurAmount);
    Si moltiplica il contenuto dell'accumulation buffer per un fattore blurAmount in modo che i colori dei vari frammenti del buffer (cioè un'istantanea della scena precedente) perdano saturazione. blurAmount è calcolato in modo da essere un valore compreso tra 0 e maxBlur (con maxBlur < 1) e direttamente proporzionale alla velocità dell'elicottero. Si osserva comunque una soglia di velocità minima sotto la quale l'effetto non ha alcuna intensità.
  2. glAccum(GL_ACCUM, 1 – blurAmount);
    Al contenuto dell'accumulation buffer viene sommato quello del frame buffer (l'immagine che si sta renderizzando) moltiplicato per il complementare a 1 del fattore blurAmount. In parole povere, se blurAmount vale ad esempio 0.4, l'AB conterrà un'immagine composta per il 40% dal rendering precedente e per il 60% dal frame buffer attuale.
  3. glAccum(GL_RETURN, 1.0f);
    L'intero contenuto dell'accumulation buffer viene riversato senza modifiche nel frame buffer. Si ricordi che questa operazione non ha l'effetto di pulire o resettare l'AB e pertanto, il risultato finale di ogni rendering si ripercuote sul successivo fintanto che blurAmount si mantiene sufficientemente maggiore a zero.

Il momento esatto in cui vengono svolte le precedenti operazioni è di importanza critica: tutto quello che si è già disegnato prima di questo punto presenterà l'effetto scia descritto, quello che viene eventualmente aggiunto in seguito apparirà completamente nitido. Per questo motivo queste tre istruzioni vengono richiamate quando tutta la scena è stata resa ad eccezione dell'elicottero che viene renderizzato solo in seguito, visto che la telecamera lo segue instancabilmente e non sarebbe realistico vedere una sua scia.

Il motion blur porta però con sé un difetto: non essendo rigidamente scandito dallo scorrere del tempo in accordo con gli slot fisici, come avviene per tutte le procedure di aggiornamento di stato e simulazione, la sua applicazione avviene tanto più frequentemente quanto maggiore è il framerate raggiunto dal dispositivo grafico. Applicare più volte i passaggi sopra descritti senza che gli oggetti si spostino (cioè senza che tra render successivi si inneschi la procedura di simulazione fisica) porta ad un intrinseco annullamento dell'effetto stesso, visto che il contenuto dell'accumulation buffer viene sfumato più e più volte e sommato alla stessa immagine. Viceversa, se il framerate è troppo basso, avvengono numerosi step di simulazione tra un render e l'altro e gli oggetti si spostano parecchio tra immagini successive (da cui la tipica sensazione di “scatto”) e il motion blur ha l'effetto di rendere l'immagine notevolmente confusa.

Dosare adeguatamente la formula con cui viene calcolato blurAmount non aiuta a risolvere questo difetto, visto che si finirebbe sempre col favorire uno o l'altro caso. Un'utile accorgimento è quello di impostare a livello di driver grafico la funzionalità di VSync che impedisce al device grafico di produrre più frame di quanti lo schermo sia in grado di visualizzare in un secondo (sopratutto per annullare il famoso effetto di tearing delle immagini). Con questo settaggio impostato, se la macchina è sufficientemente potente, il framerate dovrebbe appiattirsi sui 60-70 frame al secondo, che è esattamente la frequenza sulla quale è stato dosata l'implementazione del motion blur.

Per ottenere il secondo effetto inizialmente citato, si utilizza una tecnica che va oltre all'utilizzo dell'accumulation buffer. Quando il motion blur è abilitato, prima di svolgere le tre operazioni sull'AB si richiama la procedura renderPrevRotor() di copter.h che si occupa di renderizzare esclusivamente le pale dell'elicottero così come apparivano dal risultato dello step fisico precedente. In breve: vengono disegnate le eliche in una posizione lievemente ruotata rispetto a quella realmente assunta. Inoltre le pale vengono renderizzate con il blending attivato e tali che risultino semitrasparenti. La procedura renderCopter() renderizza ovviamente l'intero elicottero (comprese le pale nella loro posizione corretta) ma solo dopo le operazioni sull'AB.

Bisogna infine discutere dell'impatto sulle performance dovuto all'uso dell'accumulation buffer. L'impiego di un buffer aggiuntivo comporta in ogni caso dei costi aggiuntivi (sempre più onerosi man mano che la dimensione del framebuffer cresce). Sfortunatamente, alcune implementazioni di OpenGL (o alcuni driver video) non accelerano l'accumulation buffer via hardware e il suo utilizzo in questi casi abbatte drasticamente il framerate della simulazione, con tutti i riscontri negativi che ne derivano.

Pagina principale