FBX file-ok használata
Az FBX egy 3D-s modellek tárolására szolgáló file formátum, amit sok szerkesztő program tud kezelni. Ami számunkra érdekesebb, hogy ezeket az XNA segítségével kényelmesen tudjuk betölteni és megjeleníteni.
FBX exportálható például blender-ből vagy 3ds Max-ből.
Előfeltétel
Innen letölthető az előző gyakorlaton készített per-pixel lighting-os program lecsupaszított változata.
Közös feladat
Írjunk olyan programot, ami betölt egy FBX modellt, és kirajzolja azt a múlt órán készített shader segítségével!
A project Content könyvtárába másoljuk be az fbx-columns.zip tartalmát. Ez a .fbx file-on kívül a használt textúrát is tartalmazza. A .fbx file-t hozzá kell adni a project-hez, de a textúrákat nem szükséges.
A file-t a Content.Load metódussal tölthetjük be egy Model objektumba.
Ezért adjunk hozzá egy új adattagot a Game osztályunkhoz:
Model myModel;
A Game.LoadContent-ben töltsük be a modelt az FBX file-ból:
protected override void LoadContent() { // ... myModel = Content.Load<Model>("columns"); }
Így létrehozva a Model alapértelmezett Effect-eket fog használni a kirajzolás során. Mi nem ezt szeretnénk, hanem az elkészült per-pixel lighting-os shader-ünket akarjuk használni. Ehhez a Model minden egyes ModelMesh-ének minden ModelMeshPart-jának le kell cserélni az Effect-jét a sajátunkra. Erre használható a következő eljárás:
public static void RemapModel(Model model, Effect effect) { foreach (ModelMesh mesh in model.Meshes) { foreach (ModelMeshPart part in mesh.MeshParts) { part.Effect = effect; } } }
Itt nem jön létre új Effect, minden part ugyanarra a példányra fog hivatkozni, ezért az effect minden változása hat majd minden ModelMeshPart-ra a Model-ben.
Adjuk hozzá ezt az eljárást a Game osztályunkhoz, és javítsuk az előző LoadContent-et:
protected override void LoadContent() { // ... myModel = Content.Load<Model>("columns"); RemapModel(myModel, effect); }
Természetesen már régebben be kellett töltenünk ehhez az effect-et.
A kirajzoláshoz elég három sort hozzáadni a Draw metódushoz:
protected override void Draw(GameTime gameTime) { // törlés // ... // effect paraméterek // ... foreach (ModelMesh mesh in myModel.Meshes) { mesh.Draw(); } // ... base.Draw(gameTime); }
Megjegyzés: blender-ből exportált modellek esetén figyelni kell, hogy ott az Y helyett a Z tengely mutat felfele ezért a world mátrixot egy Matrix.CreateRotationX(-MathHelper.PiOver2) -vel érdemes kezdeni.
Önálló feladat
Adj hozzá még egy Model-t a programhoz, és ez mozogjon az előzőtől függetlenül (pl. ezt)!
Tippek:
- Ennek a modellnek legyen egy saját world mátrixa!
- Ezt a mátrixot át kell adni a shader-nek a modell kirajzolása előtt.
Textúrák visszahozása
Azzal hogy lecseréltük a Model Effect-jeit elveszítettük a betöltött textúrákat is. Ahhoz hogy ezeket használhassuk saját Effect-tel kicsit trükközni kell.
(Hogy látható legyen a probléma, a perpixel.fx 73, 74 sorából ki kell venni a sor végi kommentet a textúraolvasás elöl.)
Írjuk át a RemapModel eljárást úgy, hogy a textúrákat elmenti egy Dictionary-be mielőtt lecserélne az Effect-et! A ModelMeshPart pontosan anyagonként csoportosítja egy mesh részeit, ezért a Dictionaty-t hozzuk létre ModelMeshPart-ok és Texture-k között.
Kell egy úgy adattag a főosztályunkba:
Dictionary<ModelMeshPart, Texture> textures = new Dictionary<ModelMeshPart,Texture>();
És ezt tölti fel a RemapModel:
public void RemapModel(Model model, Effect effect) { foreach (ModelMesh mesh in model.Meshes) { foreach (ModelMeshPart part in mesh.MeshParts) { textures.Add(part, ((BasicEffect)part.Effect).Texture); part.Effect = effect; } } }
Fontos! Ez a változat már nem static.
A következő probléma, hogy az Effect-ünknek part-onként kéne megadni a textúrát, de csak a ModelMesh-nek van Draw metódusa. Az egyes part-okat mi is kirajzoltathatjuk, ehhez hozzunk létre egy külön metódust:
public void DrawPart(ModelMesh mesh, ModelMeshPart part) { part.Effect.Parameters["tex"].SetValue(textures[part]); part.Effect.Begin(); foreach (EffectPass pass in part.Effect.CurrentTechnique.Passes) { pass.Begin(); graphics.GraphicsDevice.VertexDeclaration = part.VertexDeclaration; graphics.GraphicsDevice.Vertices[0].SetSource( mesh.VertexBuffer, part.StreamOffset, part.VertexStride ); graphics.GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, part.BaseVertex, 0, part.NumVertices, part.StartIndex, part.PrimitiveCount ); pass.End(); } part.Effect.End(); }
Itt először a textures segítségével átadjuk a textúrát a shader-ünknek, majd az IndexBuffer-eknél tanult módok rajzoljuk ki az aktuális darabot. A part és a mesh közösen tartalmaz minden paramétert, ami a kirajzoláshoz kell.
A Draw függvényben módosítanunk kell a kirajzolást:
protected override void Draw(GameTime gameTime) { // törlés // ... // effect paraméterek // ... foreach (ModelMesh mesh in myModel.Meshes) { graphics.GraphicsDevice.Indices = mesh.IndexBuffer; foreach (ModelMeshPart part in mesh.MeshParts) { DrawPart(mesh, part); } } // ... base.Draw(gameTime); }
Mivel az index buffer egy ModelMesh-en belül azonos, azt elég a DrawPart-on kívül beállítani. (Igazából a vertex buffer is ilyen, de annak a megadásakor más-más az offset minden part esetén.)
Önálló feladat
Azok a részek, mikhez nincs textúra rendelve, feketén jelennek meg, mert egy null textúrából vett értékkel számolja a shader a színüket. Írd át a programot úgy, hogy ezek a részek az anyag színnel legyenek árnyalva.
Tippek:
- Hozz létre egy új technique-et az FX file-ban, ami az eddigitől annyiban különbözik, hogy a pixel shader nem használ textúrákat!
- Az új technique használhatja a régi vertex shader-t, de új pixel shader függvény kell hozzá.
- A használni kívánt technique-et minden DrawPart-ban be kell állítani, és a textures[part] alapján eldönthető, hogy kell-e textúrázás, vagy sem.