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.

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:

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];Queste chiamate servono per posizionare ogni mesh nella giusta posizione per far si ceh il modello sia correttamente composto.
model.CopyAbsoluteBoneTransformsTo(transforms);
foreach(ModelMesh mesh in model.Meshes){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).
foreach (BasicEffect be in mesh.Effects) {
be.EnableDefaultLighting();
be.Projection = camera.projection;
be.View = camera.view;
be.World = GetWorld() * mesh.ParentBone.Transform;
}
mesh.Draw();
}
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;E' molto semplice come struttura, nella draw e nella update scorriamo tutti i modelli inseriti nella lista models del componente.... niente dipiù.
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();
}
}
}
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:
3 commenti:
Bellissimo tutorial, grazie mille, ma se volessi ruotare e / o spostare il modello?
devi applicare a be.World una matrice di spostamento / rotazione
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