martedì 28 aprile 2009

3D.3) Xna tutorial 3d: Caricare un modello 3d

In attesa del nuovo sito preferisco iniziare a pubblicare qualche nuovo articolo. Il tempo e' tiranno e chissà quando riuscirò a buttarmi sullo sviluppo del nuovo spazio....per evitare di rimanere totalmente fermo, nel frattempo cerco di chiudere i tutorial relativi all'area 3d!

Oggi vi mostro come caricare un semplice modello 3d, utilizzando un componente che avrà lo scopo di fare da manager e una classe model3d che rappresentarà il modello da caricare.

Modelli 3d:
Nell'ultimo post avete visto come disegnare una semplice forma e colorarla, come potete immaginare creare un'intera figura 3d complessa utilizzando le primitive è abbastanza scomodo... se non impossibile.

Esistono appositi tool per la creazione di modelli 3d complessi, ne cito solo alcuni :
Maya, 3d Studio Max, XSI, Cinema4d , Sketchup....etc. La curva di apprendimento di questi strumenti non è ripidissima solitamente (almeno 3 o 4 mesi fatti bene ci vogliono però...), per produrre dei bei modelli dovrete però smanettare parecchio (non parliamo poi del discorso animazione....).
Io per ora sto utilizzando dei modelli fatti in sketchup e convertiti in directx , dato che il formato di sketchup non è letto dalla pipeline di xna.


Prima di tutto posto il link dal quale ho reperito il modello per questo tutorial:
Sketchup warehouse, qui potete trovere una bellissima gallery di modelli di Battlestar Galactica :D.
Il formato sketchup può essere convertito in directx tramite questo plugin (sul sito trovate tutte le info necessarie, tranne l'esportazione dei materiali. Vi spiego al volo che per esportare una texture dovrete aprire la finestra materiali di sketchup, cliccare sulla texture con il tasto destro e salvarla in formato immagine. Ora nel vostro directx cercate a fondo file il punto nel quale viene definita la texture (solitamente .tga se esportato da sketchup)
TextureFilename { "COLCOL1C.tga"; } e rinominatela con estensione e nome della texture salvato qualche step prima (dalla finestra materiali esportate in jpg... non fatevi fregare e corregete l'estensione presente nel directx altrimenti in fase compilazione del modello si generano errori).

Camera:
Prima di tutto ci servirà una camera, quella del post precedente va benissimo, importatela tra i file del vostro nuovo progetto (Tasto destro sul solution explorer aggiungede un "existing item" e cercare la classe camera.cs del vecchio tutorial. Cambiate il namespace con lo stesso che state utilizzando per il nuovo progetto).

Anatomia di un modello 3d:
Come dice Aaron Reed nel libro "Learning XNA 3.0" (che come vi dicevo, è il libro dal quale sto studiando XNA ... :P faccio un pò di pubblicità dato che sto usando tantissimo di quello che imparo per scrivere questi post!) "Per capire come disegnare un modello in xna bisogna prima capire come è composto".

Un modello3d contiene uno o più oggetti chiamati mesh , rappresentati tramite XNA dalla classe ModelMesh, un Mesh a sua volta contiene colori o texture storizzate in altri oggetti presenti all'interno di ogni mesh. Queste parti sono identificate da oggetti di tipo ModelMeshPart.
Ogni MeshPart di un ModelMesh quindi contiene i materiali e gli effetti attraverso i quali disegnare le mesh.

Ora dato che si è creata una gerarchia di informazioni un pò complessa, riassumiamo:

Modello (che non vi ho ancora detto, è identificato dalla classe Model)
---|
---|_____Mesh (Classe ModelMesh)
-----------|
-----------|_____MeshParts (Classe ModelMeshParts)
----------------------|
----------------------|____Materiali/Effects

Un esempio potrebbe essere la rappresentazione di una macchina:
Avremmo un modello composto da 2 mesh, la carrozzeria e le ruote. Ogni mesh avrebbe il suo specifico materiale posizionato nelle meshParts.

Per quanto riguarda gli effects, ricordate che di default viene utilizzato un effetto chiamato "BasicEffect" che ha già delle impostazioni molto semplici che vi permettono di renderizzare modelli senza troppa fatica e senza dover scrivere alcun HLSL ( high level shadare language, il linguaggio utilizzato in direct x per la generazione dei materiali ...come già accennato nel blog, farò un post appositamente dedicato a questo argomento).

Passiamo quindi al codice:
Prima di tutto aggiungete una nuova classe e chiamatela model3d.cs
Vi sconsiglio di fare copia e incolla direttamente da qui.... usate sempre i sorgenti presenti a fondo articolo!!

MODEL3D:
using System;

using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
Microsoft.Xna.Framework;
using
Microsoft.Xna.Framework.Graphics;

namespace
imparandoxna_3dmodello
{

class
model3d
{

public
Model model { get; protected set; }
protected
Matrix world = Matrix.Identity;

public
model3d(Model m) {
model = m;
}


public virtual
void Update() {

}


public
void Draw(Camera camera) {
Matrix[] transorms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transorms);

foreach(ModelMesh mesh in model.Meshes){
foreach (BasicEffect be in mesh.Effects) {
be.EnableDefaultLighting();
be.Projection = camera.projection;
be.View = camera.view;
be.World = GetWorld() * mesh.ParentBone.Transform;
}

mesh.Draw();
}
}


public virtual
Matrix GetWorld() {
return
world;
}
}
}

Questa classe ci permetterà di rappresentare e disegnare un modello 3d.
al costruttore viene inviato direttamente un oggetto di tipo Model (oggetto base di XNA che dopo vedremo come caricare nella pipeline) e la funzione DRAW si occupa di scorrere la gerarchia che abbiamo visto prima eseguendo alcune operazioni utili al disegno dell'oggetto, ricevendo in ingresso la camera attuale.

         Matrix[] transforms = new Matrix[model.Bones.Count];

model.CopyAbsoluteBoneTransformsTo(transforms);

Queste chiamate servono per posizionare ogni mesh nella giusta posizione per far si ceh il modello sia correttamente composto.
         foreach(ModelMesh mesh in model.Meshes){

foreach (BasicEffect be in mesh.Effects) {
be.EnableDefaultLighting();
be.Projection = camera.projection;
be.View = camera.view;
be.World = GetWorld() * mesh.ParentBone.Transform;
}

mesh.Draw();
}
Qui invece scorriamo le famose mesh e per ogni mesh utilizziamo i loro effetti (in qeusto caso utilizziamo solo gli effetti basic. (Sinceramente pensavo di dover scorrere anche le ModelMeshParts di ogni ModelMesh.... ma vedo che in molti tutorial gli effetti vengono presi direttamente dai ModelMesh....uhm... x?X).
Ogni mesh a questo punto passa all'effetto le informazioni di posizione e definizione della camera (view e projection) e infine chiama la funzione getWorld per restituire le informazioni di posizionamento della mesh nel "mondo".....che in questo caso sarà semplicemente una chiamata ad una matrice indentità quindi non vengono spostate dalla loro posizione originale. Se volessimo modificare informazioni posizionali degli oggetti creati con questa classe, dovremmo aggiungere un metodo update e modificare in questo punto la matrice World.

Come importare un modello:
Tasto destro su Content nella solution explorer, aggiungete un "existing item" e caricate il modello presente nei sorgenti (file .x). Ricordatevi di spostare la texture nella cartella content (proprio nella cartella eh! non tramite il solution explorer) del progetto (esempio c:/doc/visualc#/xna/questotutorial/content).
Sotto alla solution explorer, cliccando sul file .x importato, dovreste vedere una finestra descrittiva che riporto qui :



Ho modificato la voce Asset Name mettendo qualcosa di più semplice da raggiungere (dopo capirete perchè).

Qui sotto il codice per il componente che gestisce i modelli creati dalla classe model3d.

MODELMANAGER.cs
using System;

using
System.Collections.Generic;
using
System.Linq;
using
Microsoft.Xna.Framework;
using
Microsoft.Xna.Framework.Audio;
using
Microsoft.Xna.Framework.Content;
using
Microsoft.Xna.Framework.GamerServices;
using
Microsoft.Xna.Framework.Graphics;
using
Microsoft.Xna.Framework.Input;
using
Microsoft.Xna.Framework.Media;
using
Microsoft.Xna.Framework.Net;
using
Microsoft.Xna.Framework.Storage;


namespace
imparandoxna_3dmodello
{

public class
ModelManager : Microsoft.Xna.Framework.DrawableGameComponent
{

List<model3d> models = new List<model3d>();
public
ModelManager(Game game)
:
base(game)
{
}


public
override void Initialize()
{


base.Initialize();
}


public
override void Update(GameTime gameTime)
{

for
(int i =0; i < models.Count; ++i){
models[i].Update();
}

base.Update(gameTime);
}


public
override void Draw(GameTime gameTime)
{

foreach (model3d bm in models) {
bm.Draw(((Game1)Game).camera);
}

base.Draw(gameTime);
}


protected
override void LoadContent()
{

models.Add(new model3d(Game.Content.Load<Model>("Colonial")));
base.LoadContent();
}
}
}
E' molto semplice come struttura, nella draw e nella update scorriamo tutti i modelli inseriti nella lista models del componente.... niente dipiù.
Date un'occhiata a come viene aggiunto un modello alla lista nella LoadContent():
models.Add(new model3d(Game.Content.Load<Model>("Colonial")));

Come vedete facciamo riferimento a "Colonial" che è il testo inserito nell'asset name del contenuto .x .

La funzione Load è la solita chiamata che viene usata per inserire contenuti nella pipeline (in questo caso definiamo il template come "model" ).


L'ultimo step che manca è l'aggiunta dei componenti nel file game.cs.
Copio solo la prima parte del file...il resto è immutato.
GAME1.cs
    public class Game1 : Microsoft.Xna.Framework.Game

{

GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

public
Camera camera { get; protected set; }
ModelManager modelManager;

public
Game1()
{

graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}


protected
override void Initialize()
{

camera = new Camera(this,
new
Vector3(70,60, 40),
Vector3.Zero, Vector3.Up);
Components.Add(camera);

modelManager = new ModelManager(this);
Components.Add(modelManager);
base.Initialize();
}

Aggiungiamo in poche parole le informazioni legate alla Camera e al ModelManager.

Compilando da qui vedrete una bellissima Colonial One in 3d :P



Sorgenti:
imparandoxna_3d.3.zip

Modello:
imparandoxna_3d.3.modello..zip















3 commenti:

FBSC ha detto...

Bellissimo tutorial, grazie mille, ma se volessi ruotare e / o spostare il modello?

Anonimo ha detto...

devi applicare a be.World una matrice di spostamento / rotazione

Giuseppe ha detto...

Ottimo! grazie per i tuoi tut.
Vorrei riuscire ad applicare degli effeti custom(*.FX) ad un modello, ma ancora non sono riuscito a capire come, qualcuno mi puo indicare il metodo esatto

grazie ancora

Posta un commento