The Xna-Way: Tutorial 3: Modelli 3D

Piccola introduzione all'uso dei modelli tridimensionali!
Ma proprio minimale eh ^^

Intanto ecco a voi il codice di questa piccola classe che ho creato per gestire un modello 3D.
Da dire che il concetto è sbagliato, perchè è errato pensare di fare e di creare un game component per ogni oggetto che vogliamo gestire. Avremmo in questo modo un aumento della complessità realizzativa e un calo di prestazioni dovuto al grande numero di componenenti in gioco. Di solito si userà un gestore per quegli oggetti che hanno un comportamento comune, e un oggetto apposito per quelli che hanno un comportamento particolare.
Ma per ora, per questi esempi semplici va più che bene.
Questo componenten va aggiunto al progetto myLibrary.

Model3D.cs
Show

Come si può vedere la classe Model3D.cs deriva da DrawableGameComponent, in questo modo ha il suo metodo Draw.

Model3D ha come campi privati un oggetto myModel di tipo Model che servirà per contenere il modello che andremo a caricare.
Poi ha un vettore per la sua posizione nel mondo 3D della scena, 3 variabili per il valore della rotazione lungo i 2 assi (X, Y, e Z) e una variabile per la scala dell'oggetto (cioè per dire quanto deve essere grande l'oggetto).

La matrice chiamata world è la matrice che costruiremo usando le varabili che caratterizzano la posizione del nostro oggetto nel mondo.

Ha anche un oggetto di tipo CameraI che andrà a contenere il riferimento al servizio della telecamera. Ma come lo recuperiamo? :O

Al costruttore gli passiamo (oltre al game) il percorso dove è situato il modello che vogliamo caricare. In questo modo possiamo caricarlo ed assegnarlo al nostro oggetto.
Per fare questo usiamo il metodo Load di Content. Questo Metodo ci permette di caricare molti dei contenuti multimediali che vogliamo usare nel nostro gioco, come i modelli appunto, musica, immagini, etc.
Non mi dilungo nello spiegare cosa faccia, ma in parole povere è come se riscrivesse l'oggetto che vogliamo caricare in un formato compatibile ed utilizzabile da XNA sia su pc che sulla 360.

Concentriamoci un attimo su questa riga:
myCamera = (CameraI)game.Services.GetService(typeof(CameraI));

Cosa fa? Abbiamo visto nel file Camera.cs come aggiungere un servizio, che poi non era altro che un gameComponent che implementava un'interfaccia.
Questo metodo ci permette di recuperare il riferimento ad un servizio che abbiamo aggiunto, e di far riferimento sempre alla solita copia di quel servizio da ogni punto del nostro gioco.
In questo caso andiamo a prendere il riferimento al serivizio che si occupa gestire la telecamera.
Naturalmente potremo accedere solo ai metodi e proprietà definite nell'interfaccia.
Ci serve di recuperare il riferimento alla telecamera perchè per la fase di rendering dell'oggetto abbiamo bisogno di un paio di informazioni da parte della camera.

Fatto questo cambiamo il valore z del vettore position, in modo che sia distante dal punto di osservazione della telecamera e sia ben visibile.

Nel metodo update aggiungiamo questo:
angleY += 5f;
world = Matrix.CreateScale(scale) *
Matrix.CreateFromYawPitchRoll(angleY, angleX, angleZ) *
Matrix.CreateTranslation(position);
Questo codice incrementa il valore dell'angolo di rotazione sull'asse Y di 5 gradi ad ogni esecuzione del metodo update, e dopo ricostruisce la matrice world.
La matrice world è costruita prima calcoando la matrice per la scala, poi per la rotazione ed infine per la traslazione.
Per calcolare la rotazione usiamo il CreateFromYawPitchRoll: questi termini (Yaw, Pitch, Roll) indicano rispettivamente il valore della rotazione attorno agli assi Y, X, e Z (cioè Yaw = Y, Pitch = X, e Roll = Z). Ecco perchè al metodo gli passo prima l'angolo per l'asse Y, poi per X ed infine per Z.

Di solito si segue questo ordine, anche perchè la moltiplicazione tra matrici non è commutativa ed eseguire questa moltiplicazione con un ordine diverso porta a risultati doversi.

Ora andiamo al metodo Draw!

foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect be in mesh.Effects)
{
be.EnableDefaultLighting();
be.Projection = myCamera.Projection;
be.View = myCamera.View;
be.World = world;
}

mesh.Draw();
}

base.Draw(gameTime);


Questo è il classico ciclo di esecuzione per renderizzare un modello tridimensionale.
Fatto copia incolla dal manualone (vantiamocene eh XD).
Quello che fa è più o meno questo: per ogni mesh contenuta nel modello (dato che un modello può essere composto da più mesh) prende il basicEffect della mesh attuale, imposta i valori per la projection matrix, view matrix (per dire come è fatto il punto di osservazione) prelevandoli dal servizio che gestisce la telecamera (ecco a cosa ci serviva il riferimento ad essa!). Poi dobbiamo impostare il valore per la world matrix, che rappresenta la posizione, la rotazione e la scala del nostro oggetto nella scena. Infine renderizza la parte in esame.

Come si fa ad usare questo componente che abbiamo appena creato?
Nel progetto che stiamo usando, dopo aver aggiungo la telecamera come visto l'altra volta, andiamo nel file Game1.cs e nel metodo Inizialize aggiungiamo questo codice:
Model3D modell = new Model3D(this, @"Models\asteroid1");
Components.Add(modell);
Per fare questo dovrete aggiungere nella cartella Content del progetto due cartelle: una chiamata Models, dove andremo a mettere il modello del nostro asteroide, e un'altra chiamata Textures, dove andremo a mettere la texture del nostro modello (che verrà caricata in automatico assieme al modello).
Come possiamo notare, quando andiamo a caricare il modello, e a specificare il nome dell'oggetto da caricare, non mettiamo l'estensione. Questo perchè non ci serve, dato che l'oggetto è già stato passato nella content Pipeline di XNA. Se vogliamo quindi riferirci ad un oggetto per caricarlo, come in questo caso, gli dobbiamo dare solo il nome, senza estensione.
Questo codice crea ed aggiunge li componente per la gestione del modello.

Avviando il gioco se tutto va bene dovremmo vedere un meteorite che ruota su se stesso.
Ora abbiamo due possibilità: o il metorite ruota lentamente o va come una scheggia.
Se va lentamente è perchè non abbiamo creato ed aggiunto il gameComponente per il conteggio degli FPS, se va come una scheggià per perchè lo abbiamo creato.
Ricordo che il componente per il conteggio degli FPS toglie il limite al numero di volte che il metodo Update viene chiamato in un secondo.
Per usare l'FPSCounter e far ruotare il modello alla velocità giusta cambiamo il codice per l'aggiornamento dell'angolo in questo modo:

angleY += 5f * (float)gameTime.ElapsedRealTime.TotalSeconds;

Accedendo al gameTime, e prendendo il valore ElapsedRealTime non facciamo altro che recuperare il tempo passato dall'ultima invocazione del metodo Update. Così facendo l'incremento non sarà di 5 gradi ad ogni invocazione del metodo, ma di una frazione di 5 ad ogni invocazione del metodo.
In questo modo l'incremento sarà fatto alla giusta velocità, sia usando che non usando l'FPSCounter. Difatti se proviamo ad eseguire il codice prima con FPSCounter e poi senza notiamo che non c'è differenza nella velocità di rotazione dell'oggetto.

Spero sia chiaro.

Come ho detto questo è solo un esempio, e di solito non si fa una cosa del genere. Ma per addentrarci un po' nel caricamento e nella renderizzazione degli oggetti 3D credo possa andare.

Eccovi intanto i sorgeti di quanto fatto:
XNA-tut3.rar
Alla prossima :)

0 commenti:

Donazioni

My Menu'