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!

új projekt

Adjunk hozzá egy új osztályt, ami majd a DrawableGameComponent-ünk lesz! Nevezzük el ezt az osztályt Quad-nak!

új osztály

A megjelenő listán nem szerepel a DrawableGameComponent, ezért válasszuk a GameComponent-et!

valasztás

Itt kézzel kell átírni, hogy a DrawableGameComponent legyen az ősosztálya az új osztályunknak.

ősösztály

Írjuk meg a DrawableGameComponent Draw metódusát, amit majd később bővítünk.

draw metódus

Végül a fő osztályunkban (Game1) hozzunk létre egy Quad típusú objektumot, és ezt regisztráljuk a komponensek közé.

regisztrálás

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!

koordináták

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!

4. Adjuk hozzá a projekthez a képet, amit majd textúraként akarunk használni!

textúra hozzáadás

Ezt majd egy Texture2D objektumon keresztül érhetjük el.

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.

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.