2010. szeptember 12., vasárnap

SlimDX9 05 - A csúcspontformátum megadása

Most már, hogy túl vagyunk pár példán észrevehettük, hogy a használt vertex struktúrát mindig az éppen aktuális feladathoz igazítottuk. Ez azért tehettük meg, mert a DirectX a vertexek attribútumait leíró rugalmas vertex formátumot használja (Flexible Vertex Format, FVF) a csúcspontok leírásához.
Egy csúcspontot a háromdimenziós térben egy koordináta számhármassal írhatunk le. Valójában azonban ennél sokkal több információra van szükség ahhoz, hogy a vertexek adatain valóban hasznos és főleg látványos műveleteket tudjunk végrehajtani.
A pozíción kívül számos egyéb paramétere lehet a geometriát leíró csúcspontnak. Az attribútumok számossága és típusa aszerint változik, hogy milyen vizuális hatást szeretnénk elérni. A pozíción kívül a normálvektor, a színérték, a pontméret, az emisszív fény színe, a textúra koordináta, stb. is nagyon fontos lehet. A szükséges jellemzőket a fejlesztők szabadon állíthatják be a Direct3D rugalmas vertex formátumán keresztül.
Az FVF használatával saját csúcspontformátumot alkothatunk, úgy, hogy a használt tulajdonságokat egy sor különböző jellemző vagy (|) kapcsolatával rögzítjük. A vertex leíráshoz felhasználható jellemzőket a következő ábra mutatja be:

DirectX Flexible Vertex Format (FVF)

A SlimDX-ben a VertexFormat felsorolás tagjai közül kerülnek ki a felhasznált jellemzők. A jellemzőket pedig a logikai vagy kapcsolat segítségével kapcsolhatjuk össze. A vertex leírása során a sorrend mindig nagyon fontos, ezért erre különösen figyeljünk.
Minden ilyen rugalmas vertex formátumhoz tartozik egy struktúra, a textúrázáshoz a következő szükséges minimálisan:
    [StructLayout(LayoutKind.Sequential)]
struct Vertex
{
public Vector3 Position;
public Vector2 Texture;
public static readonly VertexFormat Format = VertexFormat.Position | VertexFormat.Texture1;
public static readonly int Stride = Marshal.SizeOf(typeof(Vertex));

public Vertex(float x, float y, float z, float u, float v)
{
Position = new Vector3(x, y, z);
Texture = new Vector2(u, v);
}
}
Ez azt jelenti, hogy a csúcspontjaink természetesen transzformálatlanok, és egyetlen textúrát akarunk rájuk kifeszíteni (nincs jelenleg multitextúrázás). Több textúra használatáról majd később. A Stride a Vertex struktúra méretét tárolja, később még szükség lesz rá, mivel lassan bevezetjük a Vertex Buffer fogalmát is.
Remélem sikerült mindenkinek megérteni az FVF működését és mindenki képes lesz saját vertex formátumát megalkotni.

2010. szeptember 2., csütörtök

SlimDX9 04 - Anyagok és fényvisszaverődés

Legutóbb láttuk, hogy megvilágítással és fényhatásokkal jelentősen növelhetjük a megjelenített objektumok valóságosságát. Ehhez most hozzávesszük még az anyagtulajdonságokat, hogy még látványosabb hatásokat érjünk el. Az anyagok segítségével befolyásolhatjuk a beeső fény viselkedését. A SlimDX-ben a Material struktúra használható anyagok létrehozására. Adattagjain keresztül beállíthatjuk az anyag által visszavert fény tulajdonságait. A következő komponensek állnak rendelkezésre:
  • Diffuse (Color4): a terjedő fényvisszaverődést (diffuse light) határozza meg. Szórt megvilágítás és matt felületek esetében találkozhatunk a diffúz fényvisszaverődéssel.
  • Ambient (Color4): ez a komponens a szórt fény visszaverődését határozza meg adott anyaggal bevont felületről . A szórt fény mindenütt jelen van.
  • Specular (Color4): a fényfolt-visszaverődés jellemzőit állíthatjuk be általa. A sima, csiszolt felületekre az jellemző, hogy fény hatására fényes foltok képződnek rajtuk.
  • Emissive (Color4): ezzel az adattaggal a sugárzó felületek jellemzőit állíthatjuk be. A sugárzó felületek saját fényt bocsátanak ki, amelyet a színe jellemez.
  • Power (float, értéke [0,∞]): a komponenssel állíthatjuk be a fényfolt visszaverődés mértékét, minél nagyobb az értéke, annál élesebb és kisebb, míg kisebb értékek esetében elmosottabb és nagyobb kiterjedésű fényfoltot kapunk.
A DirectX-ben az anyag (material) gyakorlatilag a felületek fényvisszaverő, illetve fénykibocsátó tulajdonságait határozza meg. Gyakorlati példánkban három kockát jelenítünk meg, amelyeket a következő, egymástól eltérő anyagokkal látunk el:
  • téglaszerű, vörös, matt anyag, amely csak a terjedő fényt veri vissza;
  • sárga, erősen fényfolt visszaverő anyag;
  • ezüstös anyag, csillogó felület mintázására.
Az anyagokat a következő módon deklarálunk:

Material gRedMaterial, gYellowMaterial, gWhiteMaterial;

Az anyagok inicializálását a void InitMaterials() függvény végzi. Elsőként a téglaszerű anyagot definiáljuk:

gRedMaterial = new Material()
{ Diffuse = new Color4(1.0f, 1.0f, 0.5f, 0.2f) };

Csak a Diffuse komponenst állítjuk, ezáltal az anyag csak a terjedő fényt fogja visszaverni, tehát matt felületű lesz. A következő anyag sárga, erősen fényfolt visszaverő:
          gYellowMaterial = new Material()
{
// terjedő fényvisszaverődés beállítása
Diffuse = new Color4(1.0f, 0.5f, 0.5f, 0.0f),
// szórt fény visszaverődésének beállítása
Ambient = new Color4(1.0f, 1.0f, 1.0f, 0.5f),
// fényfoltvisszaverődés
Specular = new Color4(1.0f, 1.0f, 1.0f, 0.0f),
// éles, kis kiterjedésű fényfolt
Power = 80
};
Mivel minden egyes komponens esetében a vörös és zöld értékei érvényesülnek, végeredményül egy sárga anyagot kapunk. A Power érték kis kiterjedésű fényfoltot eredményez. A harmadik anyag ezüstös, csillogó anyagot, ezért a terjedő és fényfolt képző komponenseken van a hangsúly:
          gWhiteMaterial = new Material()
{
// terjedő fényvisszaverődés beállítása
Diffuse = new Color4(1.0f, 0.3f, 0.3f, 0.3f),
// szórt fény visszaverődésének beállítása,
// ezüstös anyag, teljes mértékben visszaveri a környezet fényeit
Ambient = new Color4(1.0f, 1.0f, 1.0f, 1.0f),
// sugárzó anyag
Emissive = new Color4(1.0f, 0.4f, 0.4f, 0.4f),
// fényfoltvisszaverődés
Specular = new Color4(1.0f, 1.0f, 1.0f, 1.0f),
// kiterjedtebb fényfolt
Power = 15
}
A jobb hatás érdekében ezüstös anyagunk saját fénnyel is rendelkezik, amely fokozza a ragyogást, a Power kisebb értéke pedig nagyobb fényfoltot eredményez. Természetesen a visszavert fényhez szükség van fényforrásra is. Ehhez létrehozunk egy direkcionális fényforrást, továbbá engedélyezzük a fényfolt képződést. Először is definiáljuk a fényforrás irányát, ami egy vektor lesz:

Vector3 dirVec = new Vector3(2.0f, 2.0f, -5.0f);

Normalizáljuk az irányvektort: dirVec.Normalize();

Ezután a Light struktúrát feltöltjük:
          Light light = new Light()
{
Type = LightType.Directional,
Direction = dirVec,
Diffuse = new Color4(1.0f, 1.0f, 1.0f, 1.0f),
Ambient = new Color4(1.0f, 0.1f, 0.1f, 0.1f),
Specular = new Color4(1.0f, 1.0f, 1.0f, 1.0f),
};
Mivel egy direkcionális fényforrást szeretnénk létrehozni – itt az irány és a szín adott, azonban a forrás helyzete nem meghatározott –, ezért kizárólag csak a típusát, az irányát, a diffúz és a fényfolt képző adattagokat kell beállítani. Ahhoz, hogy fényfoltok képződjenek, engedélyezni kell azt a következő hívással:

device.SetRenderState(RenderState.SpecularEnable, true);

A fényfolt képzés elég számításigényes feladat, éppen ezért alapból nincs is engedélyezve. A fenti beállításokon kívül természetesen még be kell kapcsolni a megvilágítást valamint meg kell adni a kockák felületeinek normálvektorait is. Ehhez, most is definiálunk egy saját vertex formátumot, amely a vertex koordinátákon kívül tartalmazza a normálvektorokat is:
  [StructLayout(LayoutKind.Sequential)]
struct Vertex
{
public Vector3 Position;
public Vector3 Normal;
public static readonly VertexFormat Format =
VertexFormat.Position | VertexFormat.Normal;

public Vertex(float vX, float vY, float vZ,
float nX, float nY, float nZ)
{
Position = new Vector3(vX, vY, vZ);
Normal = new Vector3(nX, nY, nZ);
}
}
A kockánkat pedig az OnResourceLoad() metódus fogja megvalósítani, amiben meghívjuk a InitMaterials() metódust és feltöltjük a vertices tömböt:
      private void OnResourceLoad()
{
InitMaterials();

vertices = new Vertex[]
{
// elülső oldal
new Vertex(-1.5f, +1.5f, -1.5f, 0, 0, -1),
new Vertex(+1.5f, +1.5f, -1.5f, 0, 0, -1),
new Vertex(-1.5f, -1.5f, -1.5f, 0, 0, -1),
new Vertex(-1.5f, -1.5f, -1.5f, 0, 0, -1),
new Vertex(+1.5f, +1.5f, -1.5f, 0, 0, -1),
new Vertex(+1.5f, -1.5f, -1.5f, 0, 0, -1),

// jobb oldal
new Vertex(+1.5f, +1.5f, -1.5f, +1, 0, 0),
new Vertex(+1.5f, +1.5f, +1.5f, +1, 0, 0),
new Vertex(+1.5f, -1.5f, -1.5f, +1, 0, 0),
new Vertex(+1.5f, -1.5f, -1.5f, +1, 0, 0),
new Vertex(+1.5f, +1.5f, +1.5f, +1, 0, 0),
new Vertex(+1.5f, -1.5f, +1.5f, +1, 0, 0),

// hátsó oldal
new Vertex(+1.5f, +1.5f, +1.5f, 0, 0, +1),
new Vertex(-1.5f, +1.5f, +1.5f, 0, 0, +1),
new Vertex(+1.5f, -1.5f, +1.5f, 0, 0, +1),
new Vertex(+1.5f, -1.5f, +1.5f, 0, 0, +1),
new Vertex(-1.5f, +1.5f, +1.5f, 0, 0, +1),
new Vertex(-1.5f, -1.5f, +1.5f, 0, 0, +1),

// bal oldal
new Vertex(-1.5f, +1.5f, +1.5f, -1, 0, 0),
new Vertex(-1.5f, +1.5f, -1.5f, -1, 0, 0),
new Vertex(-1.5f, -1.5f, +1.5f, -1, 0, 0),
new Vertex(-1.5f, -1.5f, +1.5f, -1, 0, 0),
new Vertex(-1.5f, +1.5f, -1.5f, -1, 0, 0),
new Vertex(-1.5f, -1.5f, -1.5f, -1, 0, 0),

// kocka teteje
new Vertex(-1.5f, +1.5f, +1.5f, 0, +1, 0),
new Vertex(+1.5f, +1.5f, +1.5f, 0, +1, 0),
new Vertex(-1.5f, +1.5f, -1.5f, 0, +1, 0),
new Vertex(-1.5f, +1.5f, -1.5f, 0, +1, 0),
new Vertex(+1.5f, +1.5f, +1.5f, 0, +1, 0),
new Vertex(+1.5f, +1.5f, -1.5f, 0, +1, 0),

// kocka alja
new Vertex(-1.5f, -1.5f, -1.5f, 0, -1, 0),
new Vertex(+1.5f, -1.5f, -1.5f, 0, -1, 0),
new Vertex(-1.5f, -1.5f, +1.5f, 0, -1, 0),
new Vertex(-1.5f, -1.5f, +1.5f, 0, -1, 0),
new Vertex(+1.5f, -1.5f, -1.5f, 0, -1, 0),
new Vertex(+1.5f, -1.5f, +1.5f, 0, -1, 0),
};
}
Mivel a kocka ezen oldala felénk néz, normálvektora merőleges a képernyő síkjára, és kifelé – azaz a szemlélő felé – mutat, ezért koordinátái (x,y,z) formában (0,0,-1) . Ezt a logikát kell követni a többi oldal esetében is. Végezetül tekintsük meg az eredményt:

Anyagok + fények + transzformációk

A teljes forrás letölthető innen:


A tutoriál írása során nagy hasznomra volt Nyisztor Károly: Grafika és játékprogramozás DirectX-szel c. könyve, amit mindenkinek nyugodt lelkiismerettel ajánlok!