Tex-tor-túra
Kezdetben a textúra nem volt más, mint egy kétdimenziós tömb, melynek minden pontja az adott pontbeli szín koordinátáit tartalmazta (körülbelül, mint egy bitmap formátumú kép (.bmp), a neve is hasonló: bittérképes textúra). Később megjelentek összetettebb textúrázási módszerek is, például a procedurális textúrázás, illetve a 3D textúrák. Előbbi lényege, hogy a textúránkat olyan módszerekkel generáljuk, melyek különféle zajokat adnak a kapott bittérképhez, például fraktál- vagy turbulencia-alapú zajokat. Ezek hasonlítanak a természetben előforduló „zajokra”, így egy igen realisztikus képet kapunk, amely minden újra-generáláskor egy kicsit változik. Ezzel a módszerrel például fa, szikla, gránit és márvány anyagokat szokás generálni. A másik módszer, a háromdimenziós textúrázás esetén n darab (ahol n kettő hatványa) kétdimenziós textúránk van, és mindig a mélységtől függ, melyiket használjuk.
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 UV értékei 0 és 1 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.
Mivel a textúrák mérete nem egységes, be kellett vezetni egy egységes rendszert, amelyben és UV értékei 0 és 1 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.
Hárdkódolás: Textúra betöltés
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:
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:
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:
Megjelenítéskor a létrehozott textúrát hozzárendeljük a mintavételezési szakaszhoz (sampler 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:
Ezt kell most létrehoznunk:
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:
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:
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
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: DOWNLOAD
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.
[StructLayout(LayoutKind.Sequential)]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.
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);
}
}
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 (sampler 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),
...
{
// 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: DOWNLOAD
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.
0 megjegyzés :
Megjegyzés küldése