DrawableGameComponent vizsgálat, index bufferek, bevilágítás

Célkitűzés

A modellhez használt textúra.

DrawableGameComponent vizsgálat

Eddig a geometriák kirajzolásához egy-egy DrawableGameComponent-et hoztunk létre, ami önmaga tartalmazott mindent ami a kirajzolásához szükséges. Ezek a tartalmazott információk következők: geometria, anyagbeállítások, transzformációk.

Vizsgáljuk meg, hogy valóban érdemes-e így használni a DrawableGameComponent-et, hogy egy objektum kirajzolásával kapcsolatos minden paramétert összefogjon! Vegyünk két modellt, amit a programunkban szeretnénk megjeleníteni.

1. Különböző geometria, különböző anyag, különböző transzformációk

Ilyen lehet például a tereptárgyak viszonya a játékos kezében tartott tárgyakoz képest egy FPS játékban.

Itt az objektumok külön DrawableGameComponent-ben tárolása kényelmes és hatékony megoldás.

2. Különböző geometria, különböző anyag, különböző world, de azonos view és projection

Ilyenek a színtérben elhelyezett különböző objektumok. Mivel az Effect osztály leszármazottai egyben tárolják az anyagjellemzőket és transzformációkat, ezért mindenképpen külön Effect-ekre, de legalábbis különböző beállításokra van szükség. Külön Effect-ek esetén a view és projection mátrixokat minden egyes objektum számára át kell adni.

3. Különböző geometria, azonos anyag, különböző world, de azonos view és projection

Hatékonysági szempontból az ilyen modelleket érdemes anyag szerint csoportokba gyűjteni, és csak egy közös Effect-et használni egy csoport minden tagjára.

Mivel az modellek a program logikája szerint nagyon különbözőek lehetnek (márvány oszlop vs. márvány sakkfigura), egy ilyen csoportosítás nagyon elbonyolíthatja a programot.

4. Azonos geometria

Az összes többi szemponttól függetlenül, azonos geometriájú modellek többszöri letárolása felesleges pazarlás. Ilyenkor érdemes a DrawableGameComponent-től független osztályt létrehozni, ami betölti/létrehozza és tárolja a modell geometriáját, és minden felhasználás ennek az objektumnak egyetlen példányát használja. Példa: ne tároljuk a 16 gyalog geometriáját 16-szor, csak egyszer.


Index bufferek

A legtöbb modell esetén egy csúcs adatai (szín, normál vektor, textúra koordináták) azonosak attól függetlenül, hogy a csúcs éppen a modell melyik primitívben szerepel.

Ilyenkor használhatók az index bufferek. Ekkor minden csúcsot egyszer sorolunk fel, és ezeket eltároljuk egy VertexBufferben. Ezután az egyes primitívek megadásakor csak a csúcspont sorszámát adjuk meg, és ezt tároljuk egy IndexBuffer objektumban.

Példa

Egy kocka megadásához index buffer nélkül meg kell adnunk 12 háromszöget, minden háromszöghöz három csúcsot, minden csúcshoz 3 float értéket. Ha a float mérete 4 byte, akkor a kocka mérete 12*3*3*4=432 byte.

Ugyanez index bufferrel: 8 csúcs, csúcsonként 3*4 byte: 96 byte, 12 háromszög, háromszögenként 3*1 byte index: 36 byte. Összesen 132 byte.

Azaz 300 byte-ot nyertünk már egy ilyen egyszerű modellen, kevesebb mint harmad annyi memóriát foglal, mint az index buffer nélküli.

Ellenpélda

Nem minden esetben érdemes index buffert használni: alig nyerünk egy olyan esetben, amikor a kocka minden lapja más színű.

Kocka

Közös feladat

Hozzunk létre index bufferrel egy tóruszt (fánkot)! Minden csúcsponthoz vegyünk fel textúra koordinátákat és normál vektort is (majd a megvilágításnál lesz szerepe)!

Adjuk meg a tórusz csúcspontjait:

VertexPositionNormalTexture[] verts = 
    new VertexPositionNormalTexture[c1 * c2];

for (int i = 0; i < c1; ++i)
{
    float phi = MathHelper.Pi * 2.0f * i / c1;
    Vector3 centre = 
	    new Vector3((float)(r1 * Math.Cos(phi)),
		0,
		(float)(r1 * Math.Sin(phi)));
    for (int j = 0; j < c2; ++j)
    {
        float theta = MathHelper.Pi * 2.0f * j / c2;
        verts[i * c2 + j].Position = 
        	new Vector3((float)((r1 + r2 * Math.Cos(theta)) * Math.Cos(phi)),
			(float)( r2 * Math.Sin(theta)),
			(float)((r1 + r2 * Math.Cos(theta)) * Math.Sin(phi)));
        verts[i * c2 + j].Normal = (verts[i * c2 + j].Position - centre);
        verts[i * c2 + j].Normal.Normalize();
        verts[i * c2 + j].TextureCoordinate = 
        	new Vector2(1.0f * i / c1, 1.0f * j / c2);
    }
}

vbuffer = new VertexBuffer(device,
	VertexPositionNormalTexture.SizeInBytes * verts.Length,
    BufferUsage.WriteOnly);
vbuffer.SetData(verts);

Adjuk meg a tórusz lapjait csúcspont indexek segítségével:

ushort[] indices = new ushort[c1 * c2 * 2 * 3];
int idx = 0;

for (int i = 0; i < c1; ++i)
{
    for (int j = 0; j < c2; ++j)
    {
        indices[idx++] = (ushort)(i * c2 + (j + 1) % c2);
        indices[idx++] = (ushort)(i * c2 + j);
        indices[idx++] = (ushort)(((i + 1) % c1) * c2 + (j + 1) % c2);

        indices[idx++] = (ushort)(((i + 1) % c1) * c2 + j);
        indices[idx++] = (ushort)(((i + 1) % c1) * c2 + (j + 1) % c2);
        indices[idx++] = (ushort)(i * c2 + j);
    }
}

ibuffer = new IndexBuffer(device, typeof(ushort), indices.Length,
	BufferUsage.WriteOnly);
ibuffer.SetData(indices);

A két ciklus magját össze lehet vonni!

A modell kirajzolása:

device.VertexDeclaration = decl;
device.Vertices[0].SetSource(vbuffer, 0, 
		VertexPositionNormalTexture.SizeInBytes);
device.Indices = ibuffer;

device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 
		0, 0, c1 * c2, 0, c1 * c2 * 2);

Bevilágítás

A BasicEffect segítségével egyszerűbb bevilágítás hozható létre.

Megadható max. 3 irányfényforrás:

effect.DirectionalLight0.Enabled = true;
effect.DirectionalLight0.Direction = 
		Vector3.Normalize(new Vector3(0.0f, -1.0f, 0.0f));
effect.DirectionalLight0.DiffuseColor = Vector3.One;
effect.DirectionalLight0.SpecularColor = Vector3.One;

effect.DirectionalLight1.Enabled = true;
effect.DirectionalLight1.Direction = 
		Vector3.Normalize(new Vector3(1.0f, 0.0f, 0.0f));
effect.DirectionalLight1.DiffuseColor = Vector3.UnitX;
effect.DirectionalLight1.SpecularColor = Vector3.One;

A világítás számítást engedélyezni kell, és engedélyezhető per-pixel lighting is.

effect.LightingEnabled = true;

effect.PreferPerPixelLighting = true;