Dai precedenti esempi abbiamo visto cos'è un vertice e come si compone un oggetto 3D. Ma è possibile creare quei bellissimi modelli che si vedono nei giochi. Forse settando a mano tutti i vertici? Sicuramente no! Come era prevedibile è possibile caricare da file i modelli 3D come carichiamo una texture e qualsiasi altra cosa. DirectX usa ormai da tempo un suo formato con estenzione "X". I file X contengono tutte le informazioni del modello 3D rendendo sufficiente questo file e le texture dell'oggetto per disegnare qualsiasi cosa. Potete quindi disegnare qualcosa con un comodo editor 3D e salvare in questo formato per utilizzarlo nei vostri giochi (come fanno comunemente tutti gli sviluppatori di giochi). Non tutti gli editor possono però esportare direttamente in formato X (anche il 3D studio max) ma non è importante dato che è sufficiente salvare il modello in formato 3DS e convertirlo con un tool gratuito ed estremamente semplice della microsoft: il conv3ds che potete trovare anche sul mio sito nella sezione Game & Code. Il formato 3DS è più comune del formato bitmap e anche tramite l'uso di plugins e ad un mare di convertitori non possono esistere problemi di sorta.
I file X non vengono caricati in array di vertici ma servono per creare un oggetto molto utile chiamato mesh. Le mesh contengono un mare di funzioni utili per il rendering e la gestione dei modelli 3D rendendo inutili ogni draw primitive. Per chi viene da DirectX8 le mesh di DirectX9 sono molto migliori sotto diversi aspetti. Una mesh contiene però solo le informazioni sui vertici e non sulle texture che devono essere caricate insieme alla mesh. Di conseguenza conviene inventare una struttura che ospiti anche questo. Ecco la mia struttura: la oggX.
'Struttura per la gestione semplice delle mesh
Structure oggX
Public mesh As Mesh
Public numX As Integer
Public tex() As Texture
Public mat() As Material
End Structure
Questa struttura contiene una mesh, il numero di parti in cui si compone la mesh e due array, una per le texture e una per i materiali (usati per le luci).
Un modello 3D viene diviso in diverse parti, una per Materiale. Se ad esempio un oggetto può avere un certo numero di texture e materiali (il numero dei due array è uguale) allora la mesh viene suddivisa in quel numero in modo da renderizzare solo una parte alla volta per poter cambiare la texture. Se ci sono 3 texture ci sono quindi 3 subset (nome della parte). Ora creiamo una funzione che carichi la mesh da file.
Function creaMesh(ByVal fileSrc As String, ByVal materialiOn As Boolean, ByVal textureOn As Boolean, ByVal TexPath As String) As oggX
With creaMesh
'crea una mesh dichiarando i materiali in un extendMaterial
Dim materiali() As ExtendedMaterial
.mesh = Mesh.FromFile(fileSrc, MeshFlags.Dynamic, device, materiali)
'setta il numero di materiali e ridimensiona gli array
.numX = UBound(materiali)
ReDim .tex(.numX)
ReDim .mat(.numX)
Dim i As Integer
'con un loop carica i materiali da file
For i = 0 To .numX
If textureOn Then
'solo se il nome della è texture non è vuoto eseguo il caricamento
If materiali(i).TextureFilename <> "" Then
.tex(i) = TextureLoader.FromFile(device, TexPath & "\" &_
materiali(i).TextureFilename)
End If
End If
If materialiOn Then
.mat(i) = materiali(i).Material3D
.mat(i).Ambient = .mat(i).Diffuse 'pongo l'ambiente uguale alla diffuse
End If
Next
End With
End Function
Spieghiamola bene. La funzione chiede la posizione del file X(filesrc, 2 booleani che chiedono se caricare o meno le texture e la posizione della cartella in cui si trovano le texture del modello.
Dim materiali() As ExtendedMaterial
.mesh = Mesh.FromFile(fileSrc, MeshFlags.Dynamic, device, materiali)
Dichiariamo un array di extendedMaterial che conterrà i dati per creare texture e materiali e usiamo l'oggetto mesh (notate che gli oggetti servono sia per dichiarare che per creare) per caricare il file. Dovreste riconoscere ogni parte tranne MeshFlags che è l'opzione di creazione della mesh. C'è ne sono molte ma al momendo Dynamic è la migliore. Ora la mesh esiste e materiali ci permetterà di creare i materiali e le texture (i materiali che creiamo sono differenti dall'extendedMaterial).
.numX = UBound(materiali)
ReDim .tex(.numX)
ReDim .mat(.numX)
Settiamo numX pari al numero di materiali e ridimensioniamo gli array.
Ora se i valori booleani sono settati a true (quindi se vogliamo caricare quelle componenti).
.tex(i) = TextureLoader.FromFile(device, TexPath & "\" & materiali(i).TextureFilename)
Questo è un caricamento più rapido di quello usato nel precedente tutorial. Materiali(i).TextureFilename contiene il nome della texture, ma in genere mai il percorso ed è necessario quindi montare una stringa con tutto il percorso del file (ecco perchè è necessario dirgli la directory delle texture). Notate che ho inserito anche un'altro if: se materiali(i).TextureFilename<>"". Se per quel materiale non esiste texture il nome sarà una stringa vuota: non caricate e la texture conterrà Nothing. Al momento del rendering la passerete al device e questo toglierà la texture (come è giusto che sia per quella parte in cui non c'è).
Infine
.mat(i) = materiali(i).Material3D
.mat(i).Ambient = .mat(i).Diffuse
carica il materiale ma ora prendetela per buona.
Ecco un esempio di utilizzo
Dim modello As oggX
modello = creaMesh(AppPath() & "\cubo.x", True, True, AppPath)
AppPath è una funzione che restituisce la cartella in cui si trova l'eseguibile. In Visual Basic 6 esisteva proprio una funzione, in VB.Net dobbiamo crearla noi (chissà perchè l'hanno tolta). Perchè usarla? Semplice, dato che non si può sapere dove viene installato l'esempio, non si sanno le posizione dei file, ma mettendole nella cartella dove si trova l'eseguibile (o in sottocartelle) c'è lo facciamo dire da lui.
Function AppPath() As String
Dim s As String = Application.ExecutablePath
Dim i As Integer
For i = s.Length - 1 To 0 Step -1
If s.Chars(i) = "\" Then
Return s.Substring(0, i)
End If
Next
End Function
Application.ExecutablePath restituisce il path completo del file e noi gli togliamo il nome per avere la cartella (basta cancellare indietro fino al primo "\" quindi C:\documenti\app.exe restituisce C:\documenti). Omaggio della ditta!
Come usare un file? Mettete al posto di un drawPrimitives (dopo aver settato la matworld)
For i = 0 To modello.numX
device.Material = modello.mat(i)
device.SetTexture(0, modello.tex(i))
modello.mesh.DrawSubset(i)
Next
1. Per ogni parte della mesh (numX parti) settiamo il materiale
2. device.Material = modello.mat(i)
3. settiamo la texture
4. device.SetTexture(0, modello.tex(i))
5. e renderizziamo la parte
6. modello.mesh.DrawSubset(i)
DrawSubset esegue da solo la drawPrimitive (ma in modo migliore).
Basta così, per usare la mesh caricate in questo modo e renderizzate in quest'altro. Notate che è sufficiente settare la matWorld all'inizio per tutto l'oggetto (mica vorrete spostare diversamente ogni pezzo?) e che: OGNI VOLTA CHE USATE UN DRAWSUBSET VIENE DISEGNATA UNA PARTE, ANCHE PIU' VOLTE. UNA MESH PUO' ESSERE DISEGNATA ANCHE MILLE VOLTE. Lo metto per inciso perchè molti credono che una mesh può essere renderizzata solo una volta. Se caricate un cubo e dovete disegnare molti cubi uguali potete usare sempre la stessa mesh, basta cambiare la matWorld prima dell'inizio. Facile facile. Con mesh e texture siete quasi pronti per creare un gioco 3D, non vi serve molto altro. Vi lascio all'esempio che mostra come caricare e renderizzare la figura geometrica tormentone del mio sito fin dalle prime lezioni di DirectX8: il cubo con il mio logo!
Esempio VB.Net
Esempio C#
I commenti sono disabilitati.