VertexBuffer, textúrák, transzformációk (már megint)
Közös feladat
Egy programot készítünk, ami kirajzol egy textúrázott quad-ot, majd ezt módosítjuk, hogy a quad magától forogjon, vagy a felhasználó forgathassa.
A quad - nekünk - egy két háromszögből épített téglalapot jelent.
A program szerkezete
Létrehozunk egy üres projekt-et, és ehhez hozzáadunk egy DrawableGameComponent-t, ami a rajzolást, forgatást, és az ezekhez szükséges műveleteket fogja megvalósítani.
Az XNA programunk részeit komponensekből építjük fel. Ha a program egy része csak logikai feladatokat lát el (számítás, file-kezelés, kommunikáció, stb.) akkor azt a GameComponent, ha grafikus feladatokat is, akkor azt a DrawableGameComponent
osztályból származtatott saját osztállyal valósítjuk meg.
Egy saját komponensnek fölül kell definiálnia a GameComponent.Update, DrawableGameComponent.Draw (csak DrawableGameComponent
esetén) és GameComponent.Initialize metódusokat.
Ezeket majd a Game-ból származtatott osztályunk automatikusan meghívja, ha a komponest regisztráltuk.
A regisztrálást a fõ osztályunkban a Components.Add hívásával lehet megtenni.
Megvalósítás
Hozzunk létre egy új projektet!
Adjunk hozzá egy új osztályt, ami majd a DrawableGameComponent-ünk lesz! Nevezzük el ezt az osztályt Quad-nak!
A megjelenő listán nem szerepel a DrawableGameComponent, ezért válasszuk a GameComponent-et!
Itt kézzel kell átírni, hogy a DrawableGameComponent legyen az ősosztálya az új osztályunknak.
Írjuk meg a DrawableGameComponent Draw metódusát, amit majd később bővítünk.
Végül a fő osztályunkban (Game1) hozzunk létre egy Quad típusú objektumot, és ezt regisztráljuk a komponensek közé.
A metódusok kiegészítése során az Update és Draw metódusokban a base.Udpade és base.Draw legyen az utolsó függvényhívás, az Initialize-ban viszont a base.Initialize() legyen az első!
3D objektumok létrehozása és kirajzolása
1. Meg kell határozni, hogy a pozíciókon kívül milyen adatokat akarunk tárolni a csúcspontok mellett, ha egyáltalán akarunk valamit. Az ilyen csúcspont adatok tárolására szolgálnak a VertexPositionColor, VertexPositionColorTexture, VertexPositionNormalTexture, VertexPositionTexture struktúrák, de saját formátum is létrehozható.
A használt struktúra jellemzőit tároljuk el egy VertexDeclaration objektumban, amivel rajzolás előtt átadjuk a GPU-nak, hogy mit jelentenek az adatok, amiket küldünk.
VertexDeclaration vertexDeclaration; // Inicializáláskor vertexDeclaration = new VertexDeclaration(this.GraphicsDevice, VertexPositionTexture.VertexElements); // Rajzolás előtt this.GraphicsDevice.VertexDeclaration = vertexDeclaration;
A quad-hoz a VertexPositionTexture struktúrát fogjuk használni.
2. Hozzunk létre egy tömböt, aminek az elemei választott VertexPosition... típusúak, és a tömbnek annyi eleme van, ahány csúcsa lesz a modellünknek. Most a modell két háromszögből áll, amiket külön adunk meg, ezért egy 6 elemű tömbre lesz szükségünk.
A háromszögek csúcsainak sorrendje nem mindegy. Ha a kirajzolás során a csúcsok az óramutató járásával ellentétes (CCW) sorrendben szereplenek, akkor a GPU úgy tekinti, hogy ennek a háromszögnek a hátulja látszik, ezért nem rajzolja ki. Ezt a viselkedést állítja a GraphicsDevice.RenderState.CullMode tulajdonság.
Megjegyzés: A Cull az eldobás, tehát ez azt határozza meg, hogy mi legyen eldobva, nem azt, hogy mi maradjon meg.
Megjegyzés: Ha nem önálló háromszögeket rajzolunk, hanem TriangleStrip-et, akkor a rendszer automatikusan megfordítja háromszögenként a CullMode-ot, mert itt csak az első háromszög csúcsainak a sorrendjét tudjuk szabályozni, a többi háromszögben mindig az előző sorrendjének a fordítottja alakul ki.
A Quad.Initialize metódusban hozzunk létre egy hat elemű VertexPositionTexture tömböt, és töltsük fel az alábbi rajznak megfelelő adatokkal!
A csúcsokat az óramutató járásának megfelelő (CW) sorrendben adjuk meg!
3. Az előbb létrehozott tömb a rendszer memóriában van. Sokkal hatékonyabb, ha a GPU memóriából rajzolunk, ezért hozzunk létre egy VertexBuffer objektumot, és másoljuk bele a tömb tartalmát!
- Adjunk hozzá egy VertexBuffer típusú adattagot a Quad osztályhoz.
- A Quad.Initialize metódusban hozzunk létre egy új VertexBuffer objektumot, és ezt tároljuk el az előbb megadott adattagban. A VertexBuffert konstruktor paraméterei: this.GraphicsDevice, a használt VertexPosition... SizeInBytes tulajdonsága szorozva a fenti tömbünk méretével, és BufferUsage.None (itt lehet BufferUsage.WriteOnly is)
- Töltsük fel a VertexBuffert a tömb adataival a SetData metódus segítségével, paraméterként a tömböt megadva.
4. Adjuk hozzá a projekthez a képet, amit majd textúraként akarunk használni!
Ezt majd egy Texture2D objektumon keresztül érhetjük el.
- Adjunk hozzá egy Texture2D típusú adattagot a Quad osztályhoz.
- A Quad.Initialize metódusban töltsük be ebbe az adattagba a textúrát:
texture = this.Game.Content.Load<Texture2D>("a_textúra_neve");
5. Egy BasicEffect objektummal végeztetjük majd a textúrázást, és később a transzformációkat.
- Adjunk hozzá egy BasicEffect típusú adattagot a Quad osztályhoz.
- A Quad.Initialize metódusban hozzunk létre egy új BasicEffect objektumot, és tároljuk el ebben az adattagban. A konstruktor első paramétere a this.GraphicsDevice, a második null legyen.
- Engedélyezzük a textúrák használatát a TextureEnabled tulajdonság true-ra állításával.
- A Texture tulajdonságnak adjuk meg a 4. pontban létrehozott textúrát
6. Kirajzolás
A létrehozott modell kirajzolásához egészítsük ki a Quad.Draw metódust:
this.GraphicsDevice.VertexDeclaration = vertexDeclaration; this.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionTexture.SizeInBytes); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); this.GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 2); pass.End(); } effect.End();
Először beállítjuk a VertexDeclaration, mint az 1. pontban. Ezután meg kell adni, hogy a GraphicsDevice.DrawPrimitives művelete honnan olvassa az adatokat, ezt csinálja a SetSource metódus.
A rajzolás előtt meg kell hívni a használt Effect Begin, és utána az End metódusát. Egy effekt több menetből (pass) állhat, ezekhez is Begin-End párok tartoznak. Tipikusan minden menetben kirajzoljuk az adott modellt.
A tényleges rajzolás a GraphicsDevice.DrawPrimitives metódussal történik. Ennek a paraméterei, hogy milyen primitíveket akarunk kirajzolni, hanyadik csúcsponttól kezdve, és hány darab ilyen primitív kerüljön kirajzolásra.
Transzformációk
A BasicEffect három transzformációt tart nyilván. Ezek a World, View és Projection.
A World írja le a modell helyzetét a térben, a View a nézőpontból adódó transzformációkat írja le, a Projection pedig egy vetítést, ami előkészíti a sík képet.
Példa: Forgassuk folyamatosan a kirakott quad-ot!
Az Update-ben módosítjuk a World mátrixot, úgy hogy az elfordulást a gameTime-ból számolja.
effect.World = Matrix.CreateRotationY( (float)gameTime.TotalGameTime.TotalMilliseconds / 1000.0f);
Hogy az eredmény jobban nézzen ki bővítsük ki az Initialize metódust a következőkkel:
effect.View = Matrix.CreateTranslation(0, 0, -4); effect.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, 4.0f / 3, 0.0001f, 100);
Példa: Forgassuk a jobbra/balra nyilakra a kirakott quad-ot!
A billentyűzet állapota lekérdezhető a Keyboard.GetState metódussal. Ez egy KeyboardState objektumot ad vissza, és ennek a IsKeyDown metódusa adja meg, hogy adott gomb le volt-e nyomva.
Adjunk a Quad osztályhoz egy új float változót. Update-ben ezt növeljük/csökkentsük, ha gombnyomás történt. Ha változott az értéke, akkor a BasicEffect World mátrixát számítsuk újra vele.
Önálló feladat
A Quad mintájára készíts egy tetraéder komponenst! Ne legyen textúrázott, hanem minden oldala legyen más színű! Ehhez majd true-ra kell állítani a BasicEffect objektum VertexColorEnabled tagját.