Archivi per la categoria ‘Programmazione’

DirectX 9 - Inizializzazione

martedì, 20 gennaio 2009

directx9c DirectX 9   Inizializzazione

In questo nuovo articolo riguardante l’uso di DirectX inizieremo a lavorare direttamente con questo framework, andando ad impostare in ogni minimo dettaglio le caratteristiche della nostra applicazione.

Per prima cosa create una nuova applicazione Windows Forms Application in Visual Basic .NET 2008, quindi cliccate col tasto destro sull’icona della radice della vostra soluzione nella finestra “Solution explorer” e scegliete “Add Reference…”.
Nella finestra che vi apparirà selezionate la tab “.NET” ed individuate le voci “Microsoft.DirectX”, “Microsoft.DirectX.Direct3D” e “Microsoft.DirectX.Direct3DX”. Selezionatele tutte facendo Ctrl+Click su ciascuna di esse e cliccate su Ok. Se per qualsiasi motivo queste voci non esistono dovrete scaricarvi ed installarvi le managed DirectX 9.0c.
Bene, ora avete aggiunto al vostro progetto le librerie di base su cui si basano le DirectX 9 per piattaforma Net.

In questo esempio inizializzeremo solamente l’ambiente e, pertanto, non sarà necessario aggiungere alcun tipo di controllo sul form.

Ora un po’ di teoria…

DirectX utilizza un oggetto chiamato “Device” che in pratica si occupa di effettuare il rendering di ciò che vogliamo nel modo in cui vogliamo ed inizializzarlo consiste nel definire una serie di opzioni che verranno utilizzate dal device stesso per regolare la qualità ed il tipo di rendering. Queste opzioni sono contenute all’interno un oggetto di tipo PresenteParameters. Vediamo cosa significano le proprietà di questo oggetto, alcune richiamanti concetti visti nel primo tutorial su DirectX:

AutoDepthStencilFormat - Formato del DepthStencilBuffer. Normalmente 4 sono i formati supportati: DepthFormat.D16 (16 bit di superficie, solo depth buffer), DepthFormat.D32 (32 bit di superficie, solo depth buffer. La più usata se non dovete utilizzare lo stencil), DepthFormat.D24X8 (32 bit di superficie, 24 bit di depth buffer e 8 inutilizzati. Si usa per adattare la superficie da 24 bit a schede che supportano solo multipli di 2) o DepthFormat.D24S8 (32 bit di superficie, 24 di depth buffer e 8 di stencil buffer. L’unica che supporta lo stencil). Gli altri formati sono scarsamente supportati o non sono rilevanti.

BackBufferCount - Numero dei backbuffers da utilizzare, varia da 1 a 3 a seconda del modello della scheda video.

BackBufferFormat - Formato del backbuffer. Indica quanti colori supporta la superficie e in che misura. La proprietà è un enumerativo che presenta un numero enorme di opzioni ma in realtà questa proprietà ne supporta solo alcune, cioè Format.X8R8G8B8 (formato a 24 bit adattati a 32, 8 bit per canale che comportano un numero di variazioni per canale pari a 256 ed un numero di colori massimo pari a 16.777.216. E’ il formato più usato in assoluto, dei 32 bit totali 8 vengono ignorati), Format.R8G8B8 (Formato a 24 bit identico a quello di sopra. Alcune schede non digeriscono formati che non siano multipli di due e pertanto questo è scarsamente supportato), Format.R5G6B5 (Formato a 16 bit con 5 bit per i canali rosso e blu e 6 bit per il canale verde. Consente variazioni di rosso e di blu tra 32 valori diversi e variazioni di verde tra 64. Ampiamente supportato.), Format.X4R4G4B4 (Formato a 16 bit con 4 bit per canale. 4 bit non sono usati. Difficilmente supportato). Normalmente utilizzerete sempre o il primo o il terzo. Piccola curiosità: perchè nel formato a 16 bit il verde ha un bit in più e non magari il rosso o il blu? La risposta è meno intuitiva di quello che sembra ma è breve:  l’occhio umano può percepire più tonalità di verde piuttosto che di rosso o di blu.

BackBufferWidth, BackBufferHeight - La risoluzione del gioco, corrispondente alla dimensione del backbuffer. Normalmente si usano valori come 640×480, 800×600, 1024×768, 1280×1024 oppure risoluzioni leggermente “allungate” per i widescreen.

DeviceWindow - Per poter renderizzare la scena il device deve essere associato ad un oggetto su cui verrà visualizzata la scena. Di solito è un form o una picturebox.

DeviceWindowHandle - Ha la stessa funzione della proprietà di sopra solo che invece che un controllo richiede un puntatore ad esso, ricavabile tramite la proprietà Handle di un qualsiasi controllo. Ovviamente si imposta o questa come proprietà o quella di sopra.

EnableAutoDepthStencil - In pratica è un booleano che indica se l’applicazione farà uso del DepthStencilBuffer o meno. Attivare questa proprietà indica che l’applicazione farà un uso “normale” del 3D, con gli oggetti più lontani renderizzati dietro a quelli più vicini. Se viene disattivata, invece,  il device renderizzerà gli oggetti in base all’ordine con cui vengono renderizzati, ovvero l’ultimo rendering sovrascrive tutti quelli precedenti. Per applicazioni quali giochi deve essere messo a true.

FullScreenRefreshRateInHz - Questa proprietà è valida solo per applicazioni in fullscreen. La proprietà serve per impostare la frequenza massima di aggiornamento dell’immagine su schermo. Aumentare il valore evita alcuni effetti di sfarfallio ma è computazionalmente più dispendiosa. Se l’applicazione gira a velocità superiori a questa il processo di rendering si blocca ed aspetta che lo schermo si liberi prima di effettuare una nuova rasterizzazione.

MultiSample - Può assumere fino a 17 valori differenti e serve per impostare l’antialiasing. Un valore pari a None disattiva l’effetto del tutto, tutti gli altri, eccetto NonMaskable, corrispondono alla traduzione in lettere del sampling che vogliamo applicare alla scena, cosicché se desideriamo un applicazione con anti aliasing (abbreviato in FSAA, fullscreen anti aliasing) 4x sceglieremo MultiSampleType.FourSamples e così via. Occhio alla compatibilità con la vostra scheda. Non è possibile indicare un valore diverso da None a meno che lo SwapEffect non è pari a Discard (vedi dopo).

MultiSampleQuality - Proprietà che acquista un senso solo se la precedente è impostata a MultiSampleType.NonMaskable e permette di inserire in cifre il valore di sampling. Se vogliamo 4x di FSAA allora inseriremo 4 e così via. Come per la scorsa proprietà questa è valida solo se lo SwapEffect è Discard.

PresentationInterval - Proprietà che serve principalmente per debuggare le applicazioni. Normalmente DirectX non consente al device di renderizzare immagini ad una frequenza più alta di quella dello schermo, questo per evitare che scene successive vengano renderizzate contemporaneamente producendo un fastidioso effetto di tearing. A cosa serve renderizzare più immagini del dovuto? A niente… o quasi; questa proprietà ci permette di cambiare tale comportamento. Può assumere fino a 5 valori:  PresentInterval.Default (il valore di default, non sortisce nessun effetto in pratica), PresentInterval.One (è identico a quello precedente), PresentInterval.Two (il device renderizza fino al doppio della frequenza di rasterizzazione), PresentInterval.Three (renderizza fino al triplo della frequenza di rasterizzazione), PresentInterval.Four (renderizza fino al quadruplo della frequenza di rasterizzazione), PresentInterval.Immediate (renderizza al massimo della frequenza, è in assoluto la più utilizzata per debuggare).

SwapEffect - Effetto di page swapping. Indica come il front buffer deve essere alternato ai backbuffer e questi ultimi gli uni agli altri. Può assumere tre valori: SwapEffect.Copy (Valido solo se il numero di backbuffer è pari a 1. Indica al device che quando il backbuffer viene inviato allo schermo per essere rasterizzato il suo contenuto viene copiato interamente sul frontbuffer lasciando la superficie inalterata. E’ l’unico procedimento che non comporta nessun page flipping), SwapEffect.Discard(Quando il front buffer viene renderizzato esso viene immediatamente scartato e questo influenza in positivo la velocità dell’applicazione. Il device sceglie per noi il metodo più efficiente per la rasterizzazione e quindi questo valore è il più usato. Usare questo valore quando si usano 1 o più backbuffer ci permette di utilizzare l’effetto di FSAA, cosa impossibile per gli altri effetti di swap. In fase di debug ha un comportamento molto particolare in quanto la scheda video riempie con dati random il vecchio frontbuffer per permettere agli sviluppatori di vedere se tutto funziona correttamente e questo influenza negativamente le prestazioni limitatamente alla fase di debug), SwapEffect.Flip (Valida solo se i backbuffer sono 1 o più. Indica al device che se un backbuffer deve essere rasterizzato, esso diventa immediatamente il frontbuffer e il frontbuffer precedente diventa un comune backbuffer. La superficie che ora non è ne frontbuffer e ne lo è stata durante il flipping attuale è la nuova superficie candidata per il rendering. Tutte le superfici rimangono intatte).

Windowed - Proprietà booleana molto intuitiva. Mettetela e true se desiderate un’applicazione in finestra o a false se ne volete una in fullscreen.

Come potete ben vedere le opzioni sono tantissime… e non sono nemmeno finite!

Per inizializzare la nostra applicazione dovremo richiamare il metodo costruttore dell’oggetto device che utilizzeremo:

New Device (Adapter, DeviceType, RenderWindowHandle, BehaviorFlags, PresentationParameters).

Adapter - E’ un intero che indica la scheda video da utilizzare. Posto a 0 indica la scheda video di default. Per le configurazioni SLICrossfire potete mettere tranquillamente a 0 il valore.

DeviceType - Supporta tre valori possibili: Hardware (Tutto il processo del rendering viene effettuato sfruttando la potenza dell’accelleratore grafico. E’ la migliore in assoluto perchè abilita l’HAL),  Reference(Il processo di rendering viene simulato da un driver standard, sfrutta l’HEL),  Software (Identico al Reference solo che il driver non è standard ma può essere definito dall’utente. Non lo userete mai).

RenderWindowHandle RenderWindow - A seconda dell’overload della funzione che usate per creare il device essa deve essere pari alla proprietà DeviceWindowHandle  o DeviceWindow definita nell’oggetto di tipo PresentParameters. Indica l’oggetto su cui verrà mostrata la scena.

BehaviorFlags - Può assumere diversi valori e se ne possono concatenare più di uno attraverso l’operatore di somma logica OR. I valori che può assumere sono AdapterGroupDevice (Utile per abilitare la gestione multi-gpu (non solo SLICrossfire ma anche multischermo etc.) dell’applicazione. Se impostate questo flag l’Adapter deve essere necessariamente una scheda video con funzione di Master altrimenti l’applicazione andrà in errore), DisableDriverManagementDisableDriverManagementEx (Obbliga DirectX a gestire le risorse al posto dei driver presenti. Lo sconsiglio), FpuPreserve (Indica al driver che l’applicazione necessita di valori reali a precisione doppia. Intacca le prestazioni dell’applicazione), HardwareVertexProcessing (I vertici verranno processati via hardware, può migliorare le prestazioni),  SoftwareVertexProcessing (I vertici verranno processati via software),  MixedVertexProcessing(I vertici verranno processati via hardware o via software a seconda delle necessità, può migliorare le prestazioni), Multithreaded (Informa DirectX che la vostra applicazione farà uso di più thread il che potrebbe talvolta bloccare DirectX a causa di deadlock. Questo flag evita questo genere di blocchi ma intacca le prestazioni per via dei controlli massivi che vengono richiamati frequentemente). Gli altri valori non vengono quasi mai utilizzati. Attenzione! Hardware, Software e Mixed VertexProcessing sono mutuamente esclusivi e non potrete sceglierne una combinazione dei tre.

PresentationParameters - E’ un oggetto di tipo PresentParameters (visto prima) che indica tutti gli altri settings.

Prima di iniziare ad analizzare il codice vi sono due funzioni che dovete conoscere:

Device.Clear(Target,Color,Depth,Stencil)

Cancella il contenuto del backbuffer colorandolo con un colore user defined. E’ un operazione che se si può evitare si evita, specialmente se ogni rendering della scena ridisegna l’intero buffer…

Target - Può essere composto da un flag o da una combinazione tramite l’operatore OR: Target (Cancella il contenuto del backbuffer), ZDepth (Cancella il contenuto del depthbuffer), Stencil (Cancella il contenuto dello stencil). Con un po’ di fantasia permette di creare effetti particolari…

Color - Il colore del nuovo backbuffer

Depth - Se il depth buffer viene cancellato esso viene riempito con questo valore (di solito viene messo a 1)

Stencil - Se lo stencil buffer viene cancellato esso viene riempito con questo valore (di solito viene messo a 0).

Device.Present()

Dice al device che l’immagine è pronta: il contenuto del backbuffer viene copiato o cambiato con quello del frontbuffer (a seconda dello swapeffect utilizzato) e l’immagine viene rasterizzata. Ci sono un sacco di overloads della funzione ma si usano difficilmente e comunque sono molto intuitivi…

Ed ora il tanto atteso codice:

‘Importa tutti i namespaces necessari

Imports Microsoft.DirectX

Imports Microsoft.DirectX.Direct3D

‘Inizio della classe

Public Class MioForm

Public Dev As Device ‘Gestisce il rendering

Public DevSettings As New PresentParameters ‘Opzioni per il device

Public Sub InitGraph(Target As IntPtr, Width As Integer, Height As Integer, BackBufferCount As Integer, Optional _Windowed As Boolean = False, Optional _16Bit As Boolean = False, Optional _ASync As Boolean = False, Optional _FSAA As Integer = 0)

With DevSettings

.DeviceWindowHandle = Target ‘Dove verrà visualizzata la scena

.BackBufferWidth = Width ‘Risoluzione orizzontale

.BackBufferHeight = Height ‘Risoluzione verticale

.BackBufferCount = BackBufferCount ‘Conteggio dei BB

.Windowed = _Windowed ‘Finestra o FullScreen

‘16 bit o 32 bit

.BackBufferFormat = IIf (_16bit, Format.R5G6B5, Format.X8R8G8B8)

.PresentationInterval = IIf (_ASync, PresentInterval.Immediate, PresentInterval.Default)

If _FSAA Then

‘AntiAliasing attivo

.MultiSample = MultiSampleType.NonMaskable

.MultiSampleQuality = _FSAA

Else

‘Nessun AntiAliasing

.MultiSample = MultiSampleType.None

End If

.EnableAutoDepthStencil = True ‘Attiva lo ZStencil

.AutoDepthStencilFormat = DepthFormat.D24S8 ‘Formato a 32 bit

.SwapEffect = SwapEffect.Discard ‘Necessario per il FSAA

End With

‘Crea il device sfruttando l’HAL e con processo vertici misto. Usa la scheda video di default

Dev = New Device ( 0, DeviceType.Hardware, Target, CreateFlags.MixedVertexProcessing, DevSettings)

End Sub

Private Sub MioForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

‘Mostra il form e lo aggiorna

Me.Show

Me.Refresh

InitGraph(Me.Handle, 1024, 768, 2) ‘Inizializza DirectX

Dev.Clear(ClearFlags.Target, Color.Black, 1, 0) ‘Cancella il backbuffer

Dev.Present() ‘Rasterizza la scena

End Sub

Private Sub MioForm_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Click

Dev.Dispose() ‘Cancellazione delle parti unsafe del device

End ‘Fine programma

End Sub

End Class

Il codice è molto semplice e pulito. Se lo avviate visualizzerete una schermata completamente nera, se ciò avviene l’inizializzazione è riuscita altrimenti c’è qualcosa che non va. Cliccate una volta dove volete per terminare l’applicazione. Fate prove su prove…

Alla prossima…

DirectX 9 - Vettori (Approfondimento)

domenica, 5 ottobre 2008

directx9c DirectX 9   Vettori (Approfondimento)

Il secondo articolo dedicato alle DirectX sarà prevalentemente teorico e figura sotto la voce di “approfondimento” in quanto non sarà necessario saperlo (dato che directx ha delle funzioni apposite) ma che risulterà certamente utile a chi non si volesse limitare solo ad usare questo framework ma bensì miri a comprenderne ogni sua funzione nel minimo dettaglio, quindi mettetevi comodi che iniziamo.

In questa parte verranno presentati alcuni cenni teorici sui vettori, entità matematiche fondamentali per lo sviluppo di un videogame, indispensabili per i più svariati campi quali fisica, gestione dell’illuminazione, controllo della telecamera, rilevazione collisioni etc.

Il vettori vengono rappresentati come segmenti orientati (il che significa che hanno anche una direzione) nel piano passanti per un punto iniziale ed uno finale. Nello specifico DirectX utilizza vettori passanti per l’origine (detti anche vettori in posizione standard) che si sviluppano solitamente in uno spazio 3D (ma non è escluso che vengano usati anche vettori nel piano 2D o 4D…). Dato che sappiamo che ogni vettore passa per l’origine sarà sufficiente riportare solo il punto finale dello stesso perchè quello iniziale è implicito.

Una caratteristica fondamentale di un vettore è la lunghezza, anche detta magnitudine del vettore. Per calcolare quest’ultima è sufficiente calcolare la radice della somma delle componenti del vettore elevate al quadrato; nel caso di un vettore 3D (quindi a 3 componenti) in posizione standard la magnitudine sarà data da:

 

M = (V.x2 + V.y2 + V.z2)0.5 

Se volessimo calcolare le componenti di un vettore V in posizione standard passante per due punti A e B, non dovremo far altro che calcolare la differenza delle coordinate del punto B e del punto A:


V.x = B.x - A.x
V.y = B.y - A.y
V.z = B.z - A.z

Operazioni con i vettori

Le operazioni che è possibile effettuare tra due o più vettori sono limitate alla sola somma e alla sola differenza (il prodotto e il quoziente non vengono mai usati).

Per effettuare la somma di un vettore Va con un vettore Vb è sufficiente sommare le singole componenti del vettore:


Vab.x = Va.x + Vb.x
Vab.y = Va.y + Vb.y
Vab.z = Va.z + Vb.z

La differenza si calcola in maniera analoga:


Vab.x = Va.x - Vb.x
Vab.y = Va.y - Vb.y
Vab.z = Va.z - Vb.z

Oltre alla somma e alla sottrazione è possibile scalare un vettore per un fattore F. Per far ciò è sufficiente moltiplicare ogni componente del vettore per il fattore scalare F:


Vs.x = V.x * F
Vs.y = V.y * F
Vs.z = V.z * F

Un vettore V scalato per il fattore F risulta essere un vettore con direzione pari al vettore V ma con componenti ridotte o incrementate. Se il fattore F è negativo, tuttavia, la direzione risulterà opposta.

Vettore normale

Un vettore normale è un vettore con magnitudine pari a 1. L’azione di trasformare un vettore V in un vettore normale Vn viene definita normalizzazione. Per effettuare la suddetta è sufficiente dividere ogni componente di V per la sua magnitudine M:


Vn.x = V.x / M
Vn.y = V.y / M
Vn.z = V.z / M

Operazioni avanzate con i vettori

Terminiamo la lezione proponendo ulteriori due operazioni che è possibile effettuare tra un certo numero di vettori.

La prima è definita prodotto scalare, anche detto prodotto interno (in inglese Dot Product). Questa operazione riguarda sempre due soli vettori con numero di componenti uguali e risulta in un valore. Essa è definita come:


DotProduct = Va.x * Vb.x + Va.y * Vb.y + Va.z * Vb.z

Ovviamente se volessimo usare un vettore a quattro componenti dovremmo aggiungere al risultato precedente il prodotto tra la quarta componente del vettore A (Va.w) e la quarta componente del vettore B (Vb.w).

Se il DotProduct risulta essere 0 allora il vettore A è perpendicolare a B, se è inferiore a 0 allora l’angolo tra i due vettori è ottuso, altrimenti se è maggiore di 0 allora l’angolo tra i due vettori risulta essere acuto.

Se il vettore A e il vettore B sono entrambi normali allora il DotProduct è proprio pari al coseno dell’angolo tra i due vettori (e quindi varia tra -1 e 1) e questa è una proprietà fondamentale per quanto riguarda l’illuminazione (trattata più avanti)

La seconda operazione è definita come prodotto vettoriale anche detto prodotto esterno (in inglese Cross Product) ed è un’operazione che riguarda un numero di vettori pari al numero delle componenti di ciascun vettore meno 1 e risulta in un nuovo vettore. Se volessimo fare un prodotto esterno tra vettori 3D allora necessiteremmo di 2 vettori 3D (3-1), se volessimo fare il prodotto vettoriale tra vettori 4D ne useremo 3 e così via. Per questioni di semplicità ed utilità analizzeremo solo il cross product tra vettori 3D perchè gli altri non vengono quasi mai usati e sono decisamente più difficili da comprendere. Il prodotto tra due vettori 3D A e B risulta in un vettore Vcp tale che:


Vcp.x = A.y * B.z - A.z * B.y
Vcp.y = A.z * B.x - A.x * B.z
Vcp.z = A.x * B.y - A.y * B.x

Il vettore Vcp è un vettore 3D perpendicolare sia ad A che a B (anche detto vettore mutuamente ortogonale rispetto ad A e B). Per esempio se il vettore A indica un vettore giacente sull’asse X e il vettore B indica un vettore giacente sull’asse Y, il vettore Vcp risulterà giacente sull’asse Z, proprio in virtù del fatto che l’asse Z è perpendicolare sia all’asse X che all’asse Y.

DirectX 9 - Basi

mercoledì, 1 ottobre 2008

directx9c DirectX 9   Basi

In questo tutorial introduttivo riguardante la programmazione ci addentreremo nell’elegante universo del game developing, sfruttando tutta la potenza offerta da uno dei framework dedicati allo sviluppo di applicazioni 3D più famoso in assoluto, ovvero DirectX 9.

Come molti sapranno, programmare un videogame sfruttando le API offerte da Windows (o qualsiasi altro SO) rasenta quasi l’impossibile in quanto il SO stesso, attraverso le sue politiche di gestione risorse molto rigide, non ci permetterebbe di avere prestazioni sufficienti a “disegnare” una scena svariate dozzine di volte al secondo, anche perchè le suddette API non beneficiano dell’accelerazione hardware che solo una scheda video è in grado di fornire.
E’ proprio per questo motivo che è necessario sviluppare applicazioni utilizzando metodi alternativi, proprio per evitare appesantimenti eccessivi ai danni della CPU.
DirectX ci offre un’interfaccia più o meno semplice che ci permette di bypassare il controllo del sistema operativo e che, sfruttando l’accelerazione hardware, ci permette di elaborare le scene per creare effetti impensabili.
Putroppo se fosse sufficiente solo avere un po’ di DLL per creare un gioco non avremmo di che lamentarci ma sarà necessario, oltre che alla quasi perfetta conoscenza di un linguaggio di programmazione supportato, avere delle ottime conoscenze in svariati campi come quello della matematica, della fisica, etc.

DirectX 9 è supportato sia dal celeberrimo C++ e sia da un qualsiasi altro linguaggio della piattaforma Net (C# e Vb.Net, scaricabili gratuitamente dal sito della microsoft).

Prima di poter cominciare ci occore scegliere uno dei tre linguaggi succitati, in quanto essi non sono esattamente la stessa cosa ma hanno alcune peculiarità che non vanno trascurate.
Generalmente il C++ è il linguaggio più usato in assoluto in quanto offre delle prestazioni veramente alte, tuttavia sviluppare applicazioni in questo linguaggio è piuttosto avvilente per i neofiti per via della sintassi abbastanza ostica.
Il C# e il Vb Net sono, purtroppo, linguaggi interpretati da un motore chiamato JIT (Just In Time) e per questo risultano abbastanza lenti, tuttavia, interfacciandosi alle librerie DirectX le quali sono programmate in C++, le parti “lente” saranno relativamente ridotte; nonostante ciò, sia grazie all’IDE che alla sintassi molto semplice, questi due linguaggi permettono di sviluppare applicazioni anche 10 volte più velocemente rispetto al C++.

Detto questo possiamo passare alla teoria, parte assolutamente necessaria per poter capire a fondo come lavora DirectX.

Bisogna sapere che, purtroppo, non tutte le schede supportanto tutti gli effetti che DirectX ci mette a disposizione: in questo caso la scheda video non potrà elaborare la scena e quindi l’intero processo sarà effettuato dalla CPU la quale è molto più lenta di una GPU, in caso contrario, invece, sarà proprio la GPU ad eseguire il processo incrementando notevolmente le prestazioni. In quest’ultimo caso si dice che l’applicazione è “accelerata in hardware” attraverso il cosiddetto HAL (Hardware Abstraction Layer) che altro non è che la parte della scheda video che si occupa di processare le informazioni. Nel primo caso, invece, si parla di HEL (Hardware Emulation Layer) proprio perchè la CPU emula le funzioni che normalmente dovrebbero essere adibite alla GPU.

Dopo aver detto ciò possiamo inziare a descrivere come funziona l’intero framework, a prescindere se l’applicazione gira in HAL o REF (Reference, sostantivo associato all’HEL).

Iniziamo introducendo il concetto di superficie. Quest’ultima è una matrice di dimensione variabile nella quale ogni “cella” occupa una certa dimensione in byte (il cosiddetto formato). Queste matrici possono essere adibite a diversi compiti, come ad esempio contenere le informazioni sui colori, informazioni sulle profondità etc. Generalmente quando si inizializza un’applicazione DirectX, quest’ultimo provvede ad allocare in memoria video un numero variabile di superfici ed associare a ciascuna di essa un determinato compito, impostando per ciascuna di essa un determinato formato.
Le superfici che vengono allocate in memoria sono principalmente due: il Back Buffer e il DepthStencil Buffer (anche detto ZStencil Buffer).
Il primo di questi è una superficie di dimensioni pari alla risoluzione video scelta dal programmatore (ad esempio 1024×768, 1280×1024, 800×600 etc.) in cui ogni cella contiene informazioni sul colore. Il formato supportato da questa superficie può essere scelto generalmente tra tre alternative: 32 bit, 24 bit (questo, di solito, è difficilmente supportato) e 16 bit. Essa contiene l’immagine che dovrà essere mandata a schermo durante il processo di rasterizzazione (ovvero quando a partire dai dati contenuti nella superficie, lo schermo “accende” i pixel opportuni).
Lo ZStencil Buffer, invece, è una superficie “speciale” in quanto ne può contenere fino a due: il depth buffer (obbligatorio) e lo stencil buffer (opzionale).
Di solito il formato dello ZStencil Buffer può essere a 24 o a 32 bit: nel primo caso i 24 bit vengono usati solo ed esclusivamente dal Depth Buffer (e quindi lo Stencil è disattivato), nel secondo caso i 32 bit vengono ripartiti in 24 per il Depth e 8 per lo Stencil.
Come suggerisce il nome, il Depth Buffer contiene le informazioni sulle profondità, valori essenziali per il cosiddetto Depth Test (argomento che verrà trattato più avanti), mentre lo Stencil Buffer, la cui funzionalità è meno ovvia, viene usato per effetti avanzati, come quelli di masking, oppure per la simulazione di ombre molto realistiche e molto altro (questo verrà trattato in un’apposita lezione).

Normalmente viene allocato un solo DepthStencil Buffer e un numero compreso tra 1 e 3 Back Buffer.
Il numero variabile di BackBuffer serve per incrementare le prestazioni a discapito della memoria video attraverso il processo noto come Page Flipping (oppure Page Swapping).
Vediamo di analizzare a grandi linee come funziona questo processo.
Quando un Back Buffer viene riempito con tutte le informazioni che desideriamo, esso viene inviato allo schermo: in questo caso si dice che la superficie è diventata il Front Buffer, una superficie che non sarà più possibile utilizzare fin quando lo schermo non avrà finito di rasterizzare il suo contenuto. Da ciò si capisce che se avessimo un solo Back Buffer, una volta inviatolo allo schermo, dovremo necessariamente aspettare che il processo di rasterizzazione sia finito prima di poter renderizzare (ovvero disegnare qualcosa sulla superficie) nuovamente.
Tutto ciò viene abilmente aggirato da DirectX usando più back buffers: quando un back buffer è impegnato in un’operazione di rastering, DirectX provvede a renderizzare la scena in uno dei back buffer liberi, quindi il Front Buffer passa dalla superficie precedentemente impegnata (che ritorna ad essere un comune Back Buffer) a quella nuova e, pertanto, non sarà necessario aspettare tempi troppo lunghi prima di poter renderizzare nuovamente.

Ma come fa DirectX a stabilire che un oggetto che vogliamo venga renderizzato va disegnato avanti o dietro ad uno già renderizzato?
La soluzione a questa domanda è più semplice di quanto pensiate: quando voi disegnate un oggetto su schermo, il Back Buffer aggiornerà il colore dei pixel che quell’oggetto occuperà, mentre il Depth Buffer verrà “sporcato” non con il valore dei colori ma bensì con il valore di profondità di ogni singolo pixel occupato da quell’oggetto. Quando disegnerete un nuovo oggetto, anche semi sovrapposto a quello precedente, verrà prima comparato il valore di profondità di ogni singolo pixel dell’oggetto con quello contenuto nel depth buffer e, solo se questo valore risulterà minore di quello già presente in memoria verrà aggiornato il pixel opportuno nel Back Buffer (che quindi verrà sovrascritto), altrimenti sarà efficientemente rifiutato e non verrà renderizzato. Se questo confronto risulterà vero il valore del DetphBuffer verrà sovrascritto, altrimenti rimarrà uguale.
Questo confronto viene chiamato Depth Test (o ZTest) e DirectX ci permette addirittura di modificarlo a seconda delle nostre necessità (anche se, a dir la verità, non interverremo quasi mai su questo processo…).

bb DirectX 9   Basi


Nella prima immagine viene simulato il rendering di una sfera rossa. Il layer superiore rappresenta il backbuffer il quale contiene il colore dell’immagine renderizzata, il layer sottostante rappresenta il depth buffer (la profondità viene simbolicamente indicata con una tonalità di grigio, più chiaro è e più la profondità diminuisce). Nella seconda immagine viene renderizzato un cubo blu. Si può vedere che dato che il cubo ha profondità maggiore rispetto alla sfera, quest’ultima risulterà davanti al cubo. Nella terza immagine la zona evidenziata di giallo mostra le zone in cui il Depth Test ha fallito, rifiutando i nuovi pixel e mantenendo in memoria quelli vecchi.

Sperando di non avervi “ubriacato” con questa lezione, vi rimando alla prossima…