De térjünk is vissza a bittérképes textúrázáshoz. Egy textúrázott test azt jelenti, hogy a felszínének pontjait kölcsönös kapcsolatba hozzuk egy kép pontjaival, azaz a test pontjainak színét egy képből kapjuk. A testet, mint megbeszéltük, felbonthatjuk háromszögekre. Ezek a háromszögek és egy kép pontjai között kell leképezési módszert találnunk. Ez DirectX-ben nagyon egyszerű, a háromszög minden pontjához megmondjuk, hogy a textúra melyik pontja esik rá. Ehhez csupán a textúra-koordinátarendszert kell ismerni. A textúra egy kép, azaz egy téglalap. A téglalap bal felső sarka az origó, a két, ebből a pontból kiinduló oldalak pedig a bázisvektorok. Azaz a jobb felső sarok az (1,0), a balalsó sarok pedig a (0,1) pont:
Mivel a textúrák mérete nem egységes, be kellett vezetni egy egységes rendszert, amelyben és
között mozognak. A DirectX-ben megengedett a mintakoordináták határértékeinek a túllépése. Azon értékek esetében, amelyek a [0,1] intervallumon kívül esnek, az éppen beállított szabály lép életbe. Megadhatjuk, hogy a minta egész koordinátaérték túllépésekor tükröződjön, ismétlődjön stb.
Az elmélet után vegyünk egy nagy levegőt és vágjunk bele a megvalósításba. Az első és egyik legfontosabb dolog a megfelelő vertexformátum kialakítása:
[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 vertexeink természetesen transzformálatlanok, és egyetlen textúrát akarunk rá 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, mivel bevezetjük majd a Vertex Buffer fogalmát. Most azonban egyenlőre maradjunk a textúrázásnál.
A textúrához felhasználni kívánt képet a SlimDX-ben a
Texture.FromFile() metódus felhasználásával tölthetjük be, ami a következő paramétereket várja:
device a DirectX eszköz,
fileName a betöltendő kép neve,
usage a használat körülményeit rögzíti, míg a
pool a memóriakezelést állítja be. A függvény a .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, és .tga típusú képeket tud feldolgozni:
Texture texture = Texture.FromFile(device, "directx.png", 0, Pool.Managed);
A textúrák megjelenítését szűrők alkalmazásával finomíthatjuk. A következő két sor egy anizotróp nagyító, illetve kicsinyítő szűrőt állít be, amelyeket az első (nulla indexű) megjelenítési szakaszban alkalmazunk:
device.SetSamplerState(0, SamplerState.MinFilter, TextureFilter.Anisotropic);
device.SetSamplerState(0,
SamplerState.MagFilter,
TextureFilter.Anisotropic);
Megjelenítéskor a létrehozott textúrát hozzárendeljük a mintavételezési szakaszhoz (s
ampler stage) a SetTexture() hívással: device.SetTexture(0, texture);
A textúrázott kockát az
OnResourceLoad() metódusban töltjük fel, csak itt a színek helyett a textúrakoordinátákat adjuk meg a pozíció mellett:
data = new Vertex[]
{
// elülső oldal
new Vertex(-1.5f, +1.5f, -1.5f, 0, 0),
...
Ezzel tulajdon képen végeztünk is a textúrázással, a részletek a mintakódban megtalálhatóak. Persze, nagyon nagy ez a téma, és itt csak az alapokat beszéltük át. Most viszont ismerkedjünk meg a Vertex Buffer fogalmával, mivel nélküle elképzelhetetlen a hatékony renderelés.
Hárdkódolás: Vertex buffer
A vertex buffer egy olyan tároló, persze a memóriában, mely a vertex adatokat tárolja. A Direct3D ezt használja adatfolyamként, azaz ebből veszi ki a vertexeket sorrendben. Ezért fontos ismernünk a vertex buffer létrehozási módját, illetve feltöltését. Tehát nézzük, hogy kell létrehozni egy vertex buffert. Nyilván kell egy buffer:
private VertexBuffer vertices;
Ezt kell most létrehoznunk:
vertices = new VertexBuffer(device, 36 * Vertex.Stride, Usage.WriteOnly, Vertex.Format, Pool.Managed);
Itt csupán a paramétereket kell megbeszélni. Ez első paraméter a DirectX eszköz, amit létrehoztunk. A következő a természetesen a vertex buffer mérete, ami a vertexek száma és a Vertex struktúra méretének szorzata. Mivel egy kockát jelenítünk meg ezért 36 vertexre van szükség. A következő paraméter általánosan a használat körülményeiről szól. Komoly optimalizálást jelenthet a csak írható puffer létrehozása, amelyet az
Usage.WriteOnly jelez. Ezáltal közöljük a rendszerrel, hogy csak is írni fogunk az adott buffer memóriaterületére, ami lehetővé teszi az írás és képszintézis szempontjából optimális memóriaterület kiválasztását. A következő paraméter a tárolt vertexek típusát adja meg, ezt tudni kell a tárolás módja miatt. A
Pool.Managed a memóriakezeléssel kapcsolatos (hova rakja a buffert), ilyenkor a DirectX automatikusan abba a memóriába menti a vertexeket, amihez hozzáfér (általában a rendszermemória). Most már csak fel kellene tölteni a buffert. Ezért először a buffer címe kell, amit a
Lock paranccsal tudjuk lekérni (illetve ez engedélyezi az írást illetve olvasást, mivel a
Lock és az
Unlock között lezárjuk a memóriát, kívülről nem elérhető). Ehhez létrehozunk egy
DataStream objektumot és zároljuk a buffert:
DataStream stream = vertices.Lock(0, 0, LockFlags.None);
Az első paraméter azt adja meg, hogy hányadik bájttól kezdve akarjuk zárolni, a második pedig azt, hogy onnantól kezdve mekkora területet (bájtokban). Ez mindkettő 0, hiszen ekkor az egész buffert zárolja. Az utolsó paraméter a zárolás módját jellemzik, flagek, nekünk most nem kell. Ekkor a data tömbben tárolt vertexeket át kell ide másolni: stream.WriteRange(data);
Ezután lezárjuk a buffert: vertices.Unlock();
Kész is lennénk, már csak meg kell jeleníteni. Ez a vertex buffer-es módszer azért jó, mert sokkal gyorsabb, mint sima tömbből töltögetni be a vertexek adatait, illetve az adatfolyamban a típusigazítás is gyorsít. A következő beállítandó az aktuális adatfolyam, amelyből a vertexeket kivesszük:
device.SetStreamSource(0, vertices, 0, Vertex.Stride);
Az első paraméter az adatfolyam száma, azaz ezt állítjuk be. Alapértelmezésben a 0 aktív. Ez azért jó, mert több adatfolyamot egyszerre beállíthatunk, majd azt megjelenítésnél gyorsan váltogathatjuk a
SetStreamSourceFreq() függvénnyel, de ez most nem fontos. A következő annak a vertexbuffernek a mutatója, amelyikhez akarjuk kötni az adatfolyamot, ez nyilván az előbb létrehozott bufferünk, benne a háromszögek pontjaival. A következő paraméter egy eltolás paraméter, azaz honnan kezdődnek az adatfolyamban a vertexek adatai (bájtokban). Ezt nem is biztos, hogy támogatja az eszköz. Az utolsó paraméter FVF vertexeknél egy vertex mérete, ugyanis ekkora „adagokban” kapjuk a vertexeket. Most pedig jön az érdemi rajzolás, méghozzá a
device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
függvénnyel. Ez most különálló háromszögeket rajzol. Ez a függvény az aktuális adatfolyamban található, második paraméterben megadott sorszámú vertextől kezdődően rajzol annyi primitívet, ahányat az utolsó paraméterben megadtunk. Vertex Buffer használata esetén a
DrawPrimitives függvény használatos a
DrawUserPrimitives helyett. Most már nincs más hátra, mint letölteni a példa forrását és gyönyörködni a textúrázott objektumokban:
DOWNLOADTextúrázott kockák...
Ezzel a végére is értünk a mai SlimDX anyagnak és egy nagy lépést tettünk a hatékony és látványos DirectX alkalmazások felé. A bejegyzés megírásában nagy segítségemre voltak
Nyisztor Károly DirectX-el kapcsolatos könyvei.