Budapesti Műszaki Főiskola Neumann János Informatikai Főiskolai Kar Szoftvertechnológia Intézet
TUDOMÁNYOS DIÁKKÖRI DOLGOZAT
KÉPFELDOLGOZÁS A DIRECTX® 9 MAGAS SZINTŰ ÁRNYALÓ NYELVÉNEK SEGÍTSÉGÉVEL
Szerzők:
Szőke Imre műszaki informatika szak, III. évfolyam
Konzulens:
Vámossy Zoltán főiskolai docens
Budapest, 2005
Tartalomjegyzék 1
BEVEZETÉS ............................................................................................................................................... 4
2
DIRECTX ARCHITEKTÚRÁJA.............................................................................................................. 5 2.1 2.2
3
HLSL NYELV ISMERTETÉSE ................................................................................................................ 8 3.1 3.2 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.3 3.3.1 3.3.2 3.3.3 3.4 3.4.1 3.4.2 3.5 3.6
4
RENDSZER INTEGRÁCIÓ ........................................................................................................................ 5 GRAFIKUS FUTÓSZALAG ....................................................................................................................... 6 KULCSSZAVAK ..................................................................................................................................... 8 ADATOK ............................................................................................................................................... 8 Skalár adattípusok........................................................................................................................... 9 Vektor adattípusok........................................................................................................................... 9 Mátrix adattípusok .......................................................................................................................... 9 Textúrák ........................................................................................................................................ 10 Mintavételezők............................................................................................................................... 10 Struktúrák...................................................................................................................................... 11 Típusmódosítók ............................................................................................................................. 11 Típus konverziók............................................................................................................................ 12 MŰVELETEK ....................................................................................................................................... 13 Saját függvények............................................................................................................................ 13 Matematikai függvények................................................................................................................ 14 Textúra mintavételező függvények................................................................................................. 14 ÁRNYALÓ PROGRAM BEMENETEI ........................................................................................................ 15 Változatlan bemenő adatok ........................................................................................................... 15 Változó bemenő adatok ................................................................................................................. 15 ÁRNYALÓ PROGRAM KIMENETEI ......................................................................................................... 17 SAJÁT PROGRAMBA VALÓ ÁGYAZÁS ................................................................................................... 18
KÉPFELDOLGOZÁS .............................................................................................................................. 20 4.1 KÉPTÉRBEN TÖRTÉNŐ MŰVELETEK ..................................................................................................... 20 4.1.1 Pont alapú transzformációk .......................................................................................................... 20 4.1.2 Ablak vagy maszk alapú transzformációk ..................................................................................... 23 4.1.3 Teljes képre vonatkozó transzformációk ....................................................................................... 25 4.1.4 Geometriai transzformációk.......................................................................................................... 25 4.2 FREKVENCIA TARTOMÁNYBAN TÖRTÉNŐ MŰVELETEK ....................................................................... 25
5
ÉRTÉKELÉS............................................................................................................................................. 26
6
MAGASSZINTŰ ÁRNYALÓ NYELVEK JÖVŐJE ............................................................................. 26
7
ELÉRHETŐSÉG....................................................................................................................................... 26
8
IRODALOMHIVATKOZÁSOK ............................................................................................................. 27
9
ÁBRAJEGYZÉK....................................................................................................................................... 27
2
Tartalmi kivonat A dolgozat rövid áttekintést ad a Direct3D architektúrájáról rámutatva, hogy miként és miért használható hatékonyan a grafikus processzor bizonyos képfeldolgozási feladatok megoldására. A tematikában kiemelt szerepet kap az elméleti alapok lefektetése után a magas szintű árnyaló nyelv szintaktikájának és szemantikájának bemutatása, kitérve a saját programba való beágyazására is. Végül bemutatásra kerül pár alapvető képfeldolgozó algoritmus elméleti összefoglalása, amit az implementálásuk követ.
3
1 Bevezetés Gondolkodtunk már azon, hogy mi hajtja végre a programjaink utasításait? Valószínűleg nem. Ugyanis a válasz olyan egyszerű, hogy gondolkodásra sem érdemes. Vagy mégis? Nincs még egy olyan számítógépes alkatrész, amely dinamikusabban fejlődne, mint a videokártya. Valóban csak a játékmániásoknak érdemes lecserélni a videokártyát félévenként? Ha már kiadjuk a pénzt egy komolyabb videokártyára, talán elvárható lenne, hogy ne csak a játékprogramok használják ki a grafikus hardver nyújtotta lehetőségeket. Véleményem szerint ideje a fejlesztőknek elgondolkodni, azon hogy ne csak a CPU-t terheljék kódjaikkal, hanem a GPU-t is. Mire alapozom ezen kijelentést? Vegyünk egy élvonalba tartozó CPU-t és egy GPU-t és hasonlítsuk össze mindössze a tranzisztorok számát. Pentium XE 840 230 milliót, a Radeon X1800 pedig 321 millót tartalmaz. Továbbá elmondható a Radeon X1800-ról, hogy 16 pixel és 8 csúcspont árnyaló processzort foglal magába többszálas technikát is használva. Természetesen nem lehet teljes mértékben összevetni egy általános célú processzort egy grafikai feladatok megoldására kihegyezett cél processzorral, de meg kell állapítanunk gyakran kihasználatlan erőforrás lapul gépünkben. 2002-ben megjelentek az első magas szintű árnyaló nyelvek, melyek segítségével már kényelmes programozási keretek között cserélhetjük le a "beégetett" funkciókat a saját ötleteinkkel. A téma fontosságát bizonyítja, hogy mindegyik vezető grafikus alrendszer készítő és videokártya gyártó kifejlesztette a saját nyelvét. Az ATI ASHLI, az nVidia Cg, a DirectX HLSL az OpenGL pedig GLSL néven illeti saját megvalósításait. Jelen dolgozat a DirectX HLSL nyelvét mutatja be, mely segítségével a grafikus kártyát programozva valósíthatjuk meg elképzeléseinket. A nyelv általános ismertetése után következő példák ugyan csak képfeldolgozás témaköréből kerülnek ki, de számos más feladat is hatékonyan oldható meg videokártya segítségével.
4
2 DirectX architektúrája 2.1 Rendszer integráció [6][7] alapján A grafikában alkalmazott eljárások, módszerek tárgyalásához tisztában kell lennünk azzal a számítógépes környezettel, amelyben a grafikus alkalmazásaink futnak. A számítógépes környezet szoftver és hardver komponensekből áll.
[A1]. Ábra – Direct3D rendszer integráció Az [A1]. ábra a Windows operációs rendszeren futó grafikus alkalmazás környezetét mutatja be. A grafikus programok futtatásához szükség van célhardverre. A grafikus hardvert az operációs rendszer a hozzá kapcsolódó illesztőprogramok interfészein (DDI) keresztül kezeli. A hardvereket az operációs rendszerek biztonságos és ellenőrzött interfészek mögé "rejtik" el. Számunkra ez azt jelenti, hogy a grafikus alkalmazások közvetlenül nem kezelhetik a grafikus hardvereket, csak az operációs rendszer által biztosított interfészeken, illetve az ezekre épülő könyvtárakon keresztül érhetik el azokat. A Windows GDI (Graphics Device Interface) egy olyan absztrakciós szintet definiáló rendszerkomponens, amelynek a segítségével az alkalmazások a képernyőre tudnak rajzolni. A GDI-t sajnos nem grafikus és multimédia célokra, hanem üzleti alkalmazásokhoz (szövegszerkesztő, táblázatkezelő) fejlesztették ki. A GDI filozófia szerint a keletkező kép a rendszermemóriába, és nem a videokártyába kerül, továbbá a GDI felület nincs felkészítve bővítésre, a videokártyák által támogatott új funkciók befogadására. Játékokhoz és multimédiás alkalmazásokhoz (videózás) a lassúsága miatt sem alkalmas. A megoldást a DirectX API grafikai részét tartalmazó Direct3D jelenti, amely eszközfüggetlen interfészt biztosít az alkalmazás számára a grafikus processzor eléréséhez. A Direct3D egy létrehozott eszközön (device) keresztül fér hozzá a hardverhez. A Direct3D eszköz egy megjelenítési alkotóelem, amely magában foglalja a rajzolási állapotot is. Mindemellett egy Direct3D eszköz tartalmazza a transzformációs és megvilágítási műveleteket és a képernyő ponttá való átalakítást (raszterizálás) is. Az előzőeket az [A2]. ábra szemlélteti.
5
[A2]. Ábra. – Direct3D eszköz architektúra Jelenleg a Direct3D két fő eszköz típust támogat: a HAL eszközt és a referencia eszközt. HAL (Hardware Abstraction Layer) eszköz: Az elsődleges eszköztípus a HAL eszköz, amely támogatja a hardveresen gyorsított raszterizálást és mind a hardveres, mind pedig a szoftveres csúcspont feldolgozást. A hardver absztrakciós réteget (HAL) a videokártya gyártók csatolóprogramban (driver) valósítanak meg. A kompatibilitás érdekében minden kártyagyártó kötelezően ugyanazt a HAL felületet mutatja. A HAL-nak része egy olyan metódus, amellyel lekérdezhető, hogy melyek a kártya által hardveresen gyorsított rajzolási funkciók. Ha egy funkció hiányzik, akkor azt a DirectX HEL emulációs rétege (Hardware Emulation Layer) fogja elvégezni. Természetesen ez lelassítja a programot, de a sebességcsökkenéstől eltekintve az alkalmazás ebből semmit sem vesz észre. Referencia eszköz: A referencia eszköz támogat minden Direct3D jellemzőt. A minden funkció megvalósítását szoftveresen végzi a CPU segítségével, így az eredmény nem túl gyors, de minden gépen lefut. A referencia eszközt csak funkciók tesztelésére, demonstrálására használják.
2.2 Grafikus futószalag [6] alapján A grafikus futószalag a hardver adottságokat kihasználva biztosítja a hatékony színtér feldolgozást és megjelenítését. Az [A3]. ábra szemlélteti a futószalag építőelemeit.
[A3]. ábra – Direct3D grafikus futószalag
6
Futószalag komponens Csúcspont adatok (Vertex Data) Primitív adatok (Primitive Data)
Tesszeláció (Tesselation) Csúcspont feldolgozás (Vertex processing) Geometriai feldolgozás (Geometry processing) Textúra felület (Textured surface) Textúra mintavételezés (Texture sampler) Pixel feldolgozás (Pixel processing) Pixel megjelenítése (Pixel rendering)
Leírás A modell transzformálatlan csúcspontjai, ami a memóriában egy csúcspont pufferben kerül tárolásra A geometriai primitívekről szóló adatokat a csúcspont adatokhoz kapcsolják index puffer segítségével. Ez meghatározza, miként dolgozzuk fel a csúcspont adatokat. Primitív típusa lehet pont, vonal, háromszög és poligon. A tesszelációs egység a magas szintű primitíveket, hálókat, elemi szintűekre bontja, és csúcspont pufferben eltárolja Direct3D transzformációkat hajtanak végre a tárolt csúcspontokon. (Pl. a modell koordináta rendszerből áttérés a képtér koordinátarendszerébe) Kamera modellnek megfelelő képsík vágás, takarási feladatok megoldása, transzformált csúcspontok raszterizálása (képernyő pixelekre való bontás) Felületek textúra koordinátáinak megadása Az input textúrán szűrést hajtunk végre a részletezettség szintjének megfelelően. Pixel árnyaló műveleteket használunk a bejövő csúcspont és textúra adatokon, hogy a kimenő szín értékeket meghatározzuk. A végső megjelenítési fázis módosítja a pixel színét az alfa csatornának, áttetszőségnek és mélységnek megfelelően.
A grafikus processzorok lehetőséget biztosítanak arra, hogy ezen működési modellbe két pontos is "belenyúljunk". A geometriai motor működését, azaz a csúcspontok és kapcsolódó adataik transzformálását a csúcspont-árnyalók segítségével módosíthatjuk, míg az árnyaló egység előre beprogramozott algoritmusait a pixel-árnyalókkal cserélhetjük le.
7
3 HLSL nyelv ismertetése [1] alapján 3.1 Kulcsszavak A kulcsszavak előre definiált azonosítók, amelyeket a HLSL nyelv fenntart, így ezeket nem használhatjuk a programunkban azonosítóként. A '*' jelölt kulcsszavak érzékenyek a kis- és nagybetűkre. asm * decl* extern half inout pass * shared technique * uniform volatile
bool do false if int pixelshader * static texture * vector * while
compile double float in matrix * return string * true vertexshader *
const else for inline out sampler struct typedef void
A következő kulcsszavak jelenleg nem használtak, de fent vannak tartva jövőbeni felhasználás céljából: auto char default explicit mutable private reinterpret_cast static_cast throw typename virtual
break class delete friend namespace protected short switch try unsigned
compile case const_cast dynamic_cast goto new public signed template using
const catch continue enum long operator register sizeof this union
3.2 Adatok A HLSL az adattípusok széles választékát kínálja. A skalár és struktúra típusú adatok a hagyományos magas szintű programozási nyelvekben megszokottak. Az újdonságok a vektorok és mátrixok adattípusok nyelvi szintű támogatásában rejlik. Továbbá a nyelv sajátosságainak megfelelően új adattípusok (textúra, mintavételező) is szerepelnek.
8
3.2.1 Skalár adattípusok A nyelv a következő skalár adattípusokat támogatja: Adattípus bool int half float double
Ábrázolható értékek igaz vagy hamis 32 bites előjeles egész szám 16 bites lebegőpontos szám 32 bites lebegőpontos szám 64 bites lebegőpontos szám
3.2.2 Vektor adattípusok Az egyik leggyakrabban használt adattípus a HLSL nyelvben a vektor adattípus. Vektorok deklarálásának számos módja van, beleértve a következőket is: vector vector
Egy négydimenziós vektor, melynek minden eleme float típusú A vektor dimenziója size és az egyes elemek adattípusa valamilyen skalár típus (type)
A szokásos C stílusú vektor deklaráción kívül, leggyakrabban a típust és azt közvetlenül követő 2, 3 vagy 4 szám megadásával teszik. A következő példában egy 4 elemű float típusú vektorokat deklarálunk különböző módon: float4 fVector0; float fVector1[4]; vector fVector2; vector fVector3; A vektor definiálása után a komponensekhez hozzáférhetünk a szokásos tömbindexeléssel, valamint az {x,y,z,w} vagy a {r,g,b,a} névtereken keresztül. Egyszerre több elemhez is hozzáférhetünk, de ügyeljünk arra, hogy ne keverjük a névtereket. float4 float float float float2 float2
pos = {3.0f, 5.0f, 2.0f, 1.0f}; value0 = pos[0]; // value0 értéke 3.0f value1 = pos.x; // value1 értéke 3.0f value2 = pos.g; // value2 értéke 5.0f vec0 = pos.xy; // vec0 értéke {3.0f, 5.0f} vec1 = pos.ry; // Érvénytelen a névterek keverése miatt
3.2.3 Mátrix adattípusok Egy másik gyakran használt változó típus a mátrix, amely egy kétdimenziós adattömb. A vektorokhoz hasonlóan a mátrixok is elemi adattípusokból épülnek fel: bool, int, half, float, double. A mátrixok mérete bármekkora lehet, de általában 4 sorból és oszlopból állok a leggyakoriabbak. A vektorokhoz hasonlóan deklarálhatjuk őket:
9
float3x4 mat0; matrix mat1; float4x4 mat2; Az egyes elemek eléréséhez, használhatjuk a szokásos tömbindexelési technikát. Például a mat2 mátrix bal felső eleme: float fValue = mat2[0][0]; Használhatunk beszédes neveket is. A nulla alapú sor oszlop elérés a következő: _m00, _m01, _m02, _m03 _m10, _m11, _m12, _m13 _m20, _m21, _m22, _m23 _m30, _m31, _m32, _m33 Az egy alapú sor oszlop elérés a következő: _11, _12, _13, _14 _21, _22, _23, _24 _31, _32, _33, _34 _41, _42, _43, _44 Mátrix deklarálást, elem és vektor szintű hozzáférést a következő példa szemlélteti: float2x2 fMat = {3.0f, 5.0f, // row 1 2.0f, 1.0f}; // row 2 float value0 = fMat[0]; // value0 is 3.0f float value1 = fMat._m00; // value1 is 3.0f float value2 = fMat._12 // value2 is 5.0f float value3 = fMat[1][1] // value3 is 1.0f float2 vec0 = fMat._21_22; // vec0 is {2.0f, 1.0f} float2 vec1 = fMat[1]; // vec1 is {2.0f, 1.0f}
3.2.4 Textúrák Egy textúrat a texture kulcsszóval vezethetünk be. Léteznek még texture1D, texture2D, texture3D és textureCUBE kulcsszavak is amelyek a textúra típusát pontosíthatják, de alapvetően a mintavételezési állapot és textúra színt kikereső függvények határozzák meg az eredményt. A textúra kép fájlból való betöltését vagy generálását és átadását a főprogram végzi el. Példa egy textúra deklarálására: texture g_MeshTexture;
3.2.5 Mintavételezők Minden egyes textúrához deklarálni kell egy mintavételezőt a sampler kulcsszóval. Lehetőség van egy textúrához több mintavételezőt definiálni különböző paraméterekkel. Ezt a technikát gyakran használják a képfeldolgozás során. A textúra térkép típusától függően a felhasználható kulcsszavak:
10
sampler1D sampler2D sampler3D samplerCUBE
1 dimmenziós textúra mintavételezésére 2 dimmenziós textúra mintavételezésére 3 dimmenziós textúra mintavételezésére Kiterített kocka alapú textúra mintavételezésére
Egy mintavételező deklarálása a sampler kulcsszóval kezdődik, amit a mintavételező neve követ. Egyenlőségjelet követően a mintavételezési állapotot vezetjük be a sampler_state kulcsszóval, ami meghatározza a textúra szűrés típusát és a textúra címzés módját. A következő példában egy g_ MeshTexture nevű textúra mipmap, kicsinyítési, nagyítási szűrőjét lineáris interpolációra állítottuk be. sampler MeshTextureSampler = sampler_state { Texture = ; MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR; }; Mintavételezési állapotról további részletes információkat a DirectX SDK-ban találhatunk.
3.2.6 Struktúrák HLSL nyelv is biztosítja a lehetőséget a saját adatszerkezetek létrehozására. Struktúrák használata növeli az áttekinthetőséget, kényelmes teszi az adatok kezelését. Struktúra deklarálását a struct kulcsszóval kezdjük, amit a struktúra neve követ. Kapcsos zárójelek között pedig felsorolásra kerül az elemi és összetett adattípusok. Egy példa a struktúrák definiálására: struct VS_OUTPUT { float4 Pos : POSITION; float3 View : TEXCOORD0; float3 Normal:TEXCOORD1; float3 Light1: TEXCOORD2; float3 Light2: TEXCOORD3; float3 Light3: TEXCOORD4; };
3.2.7 Típusmódosítók Van néhány típusmódosító, amelyre szükségünk lehet a HLSL árnyaló programunkban. A const kulcsszó lehetővé teszi, hogy a változó értékét ne lehessen megváltoztatni az árnyaló programunkban, azaz definiálás során megadott állandó értékkel szerepel a program futása során. Amennyiben a változót az értékadás bal oldalára tennénk, fordítási hibát kapnánk.
11
A row_major és a col_major típus módosítók a mátrix elvárt tárolási szerkezetét határozzák meg a hardverben. A row_major azt jelöli, hogy a mátrix minden egyes sora egy konstans regiszterbe lesz tárolva, míg a col_major esetén a mátrix egyes oszlopai lesznek egy regiszterben tárolva. Az oszlop folytonos az alapértelmezés. Tárolási osztálymódosítók A tárolási osztály módosító a megadott változó érvényességi körét és élettartamát közli a fordító felé. Ezek a módosítók opcionálisak és bármilyen sorrendben a változó típusa elé írhatók. Egy változó lehet static vagy extern (a két módosító egymást kizárja). Globális hatáskörrel a static módosító azt jelenti, hogy a változót csak az árnyaló program érheti el az alkalmazás az API-n keresztül nem. Minden nem statikus globális változót a főprogram az API-n keresztül módosítani tud. Lokális hatáskörű statikus változót, akkor használunk, ha a függvényhívások között is meg kívánjuk tartani a változó értékét. Az extern módosítót globális változóknál használhatjuk, hogy módosítható legyen kívülről is. Ez redundáns ugyanis ez az alapértelmezett viselkedése egy globális változónak. A shared módosítót használatos, hogy megadjunk egy megosztott globális változót, amit így a többi árnyaló programunk is elér. A uniform típusú változó értékét a HLSL árnyaló programon kívülről is be lehet állítani (a Set*ShaderConstant*() API függvényen keresztül). A globális változókat automatikusan uniform típusként kezelik. Például a következő globális változókat deklaráljuk: extern float translucencyCoeff; const float gloss_bias; static float gloss_scale; float diffuse; A diffuse és a translucencyCoeff változók értéke beállítható a Set*ShaderConstant*() API-n keresztül és az árnyaló program képes ezek értékét megváltoztatni. A konstans gloss_bias változó szintén beállítható Set*ShaderConstant*() segítségével, de nem módosíthatja az értékét az arnyaló program. Végül a gloss_scale statikus változót nem lehet beállítani a Set*ShaderConstant*() függvénnyel, de az árnyaló képes ennek értékét módosítani.
3.2.8 Típus konverziók Hasznos, ha jártasak vagyunk a típuskonverziókkal, ugyanis így elkerülhetünk csapdákat és hatékony kódot írhatunk. Alacsonyabb vagy magasabb elemszámra hozás esetén gyakran használják a konverziót. Például a következő esetben a konstans 0.0f float számot float4{0.0f, 0.0f, 0.0f, 0.0f} alakúra kényszerítjük, hogy inicializáljuk a vResult változót. float4 vResult = 0.0f; Hasonló módon magasabb dimenziószámú adattípust (mint a mátrix vagy vektor) alacsonyabb dimenziószámra konvertálhatunk. Ebben az esetben az extra adatokat kihagyjuk. Erre példa a következő pár sor:
12
float3 vLight; float fFinal, fColor; fFinal = vLight * fColor; Ebben az esetben a vLight vektort float típusra hoztuk, úgy hogy a vektornak csak az első elemét szoroztuk be az fColor float számmal. Az utolsó sorral ekvivalens kifejezés a következő: fFinal = vLight.x * fColor; További típuskasztolási szabályokat lásd [1] 12-13 oldala.
3.3 Műveletek Az adatokat operátorok és függvények segítségével dolgozhatjuk fel. Az operátorok a megszokott módon működnek a következőktől eltekintve. A HLSL esetén nyelvi szintre emelték a vektorok és mátrixok kezelését, azaz a megszokott operátorok (+,-,*,/) értelmezve van az adattípusokon, de fontos megjegyezni, hogy ezek elemenkénti végrehajtást jelentenek. Lássunk egy példát: float4 vTone = vBrightness * vExposure; Feltételezve hogy vBrightness és vExposure float4 típusú, a következő kifejezéssel ekvivalens float4 vTone; vTone.x = vBrightness.x * vExposure.x; vTone.y = vBrightness.y * vExposure.y; vTone.z = vBrightness.z * vExposure.z; vTone.w = vBrightness.w * vExposure.w; Tehát a * operátor nem a két vektor keresztszorzata. A mátrixok szorzatára is érvényes az a megállapítás, hogy ez a típusú szorzás nem a matematikában megszokott szorzás. A mátrix szorzatot és a kereszt szorzatot a mul() belső függvény segítségével érhetjük el. A mul() függvényen kívül a HLSL nyelv számos belső függvényt tartalmaz, melyek megkönnyítik a programozók dolgát. Nagyrészük matematikai függvény, ami a kényelmet szolgálja, míg a többi függvény a textúra adatok kinyerésére szolgálnak.
3.3.1 Saját függvények A HLSL esetén is írhatunk tetszőleges számú függvényt. Mindössze az utasítások számát illik szemügyre venni, hogy a kiválasztott fordítási célhardver maximális utasításhosszát ne lépjük túl. Példa egy saját függvényre: float4 myFunction(float4 a, float4 b, float4 c ) { return (a * b + c); }
13
3.3.2 Matematikai függvények A HLSL fordító a matematikai függvényeket mikro utasításokká alakítja. Néhány esetben, mint például az abs() és a dot(), ezek közvetlenül egy assembly szintű utasításra képeződnek le, míg a többi assembly utasítások sorozatára (pl: refract()). Néhány esetben, mint például a ddx(), ddy() és fwidth(), pedig nem lehet minden hardverre lefordítani. Beépített matematikai utasítások listája: abs(x) asin(x) clip(x) D3DCOLORtoUBYTE4(x) distance(a, b) faceforward(n, i, ng) frexp(x, out exp) isnan(x) lerp(a, b, s) max(a, b) pow(x, y) round(x) sin(x) sincos(x, out s, out c) smoothstep(min, max, x)
acos(x) atan(x) cos(x) ddx(x) dot(a, b) floor(x) fwidth(x) ldexp(x, exp) log(x) min(a, b) radians(x) rsqrt(x) sinh(x) tan(x)
all(x) atan2(y, x) cosh(x) ddy(x) exp(x) fmod(a, b) isfinite(x) len(v) log10(x) modf(x, out ip) reflect(i, n) saturate(x) sqrt(x) tanh(x)
any(x) ceil(x) cross(a, b) degrees(x) exp2(a) frac(x) isinf(x) length(v) log2(x) mul(a, b) refract(i, n, eta) sign(x) step(a, x) transpose(m)
Az utasítások nevei elég beszédesek, de pontos leírást találunk a DirectX SDK-ban.
3.3.3 Textúra mintavételező függvények Tizenhat textúra mintavételező utasítás szolgál az adatok kinyerésére a textúrából. Négy textúra típus van (1D, 2D, 3D, Cube) és mindegyikhez négyfajta betöltési mód (hagyományos, hagyományos deriválttal, projektív és eltolt súlypontú). Így áll elő a tizenhat kombináció: tex1D(s,t) tex2D(s,t) tex3D(s,t) texCUBE(s,t)
tex1D(s, t, ddx, ddy) tex2D(s, t, ddx, ddy) tex3D(s, t, ddx, ddy) texCUBE(s, t, ddx, ddy)
tex1Dproj(s, t) tex2Dproj(s, t) tex3Dproj(s, t) texCUBEproj(s, t)
tex1Dbias(s, t) tex2Dbias(s, t) tex3Dbias(s, t) texCUBEbias(s, t)
Értelmezés: s egy előzőleg deklarált mintavételező t a textúra dimmenziójának és a betöltés típusának megfelelő vektor ddx és ddy az x, y irányú derivált vektorok.
14
3.4 Árnyaló program bemenetei Csúcspont és pixel árnyalóba érkező adatok lehetnek változók (varying) és változatlanok (uniform). Csúcspont árnyaló esetén a változó adatok (mint pl. a pozíció, normál vektor, stb.) a csúcspont pufferből érkeznek. A változatlan adatok (mint pl. anyag színe, világ transzformáció) pedig állandóak az egyes csúcspontokra nézve, így konstans táblából származnak.
3.4.1 Változatlan bemenő adatok A HLSL nyelvben kétféle módon is definiálhatunk változatlan adatokat. A leggyakrabban használt metódus erre, hogy globálisnak deklarálunk egy változót. Egy árnyaló függvény paraméterlistájában a változó elé írt uniform a másik lehetőség. Mindkét esetet illusztrálja a következő kódrészlet: float4 UniformGlobal; float4 main( uniform float4 UniformParam ) : POSITION { return UniformGlobal * UniformParam; } Az árnyaló programban használt uniform változókat az alkalmazás egy konstans táblán keresztül ér el. A konstans tábla tartalmazza a típus információkat és az alapértelmezés szerinti értéket. A konstans tábla változói külön regiszterbe kerülnek, konstans regiszterekbe. Ezt a táblát fel kell tölteni az árnyaló program végrehajtása előtt.
3.4.2 Változó bemenő adatok Bemeneti sémákkal lehet specifikálni a változó bemenő adatokat. Minden árnyaló bemenetet sémával kell ellátni, vagy uniform kulcsszóval konstansként kell megadni. Ha egy bemenő adatot nem jelölünk meg sémával vagy a uniform kulcsszóval, akkor az árnyalót nem lehet lefordítani. Sémákat azért használunk, hogy a grafikus futószalag előző lépésének kimenetét a következőhöz kapcsoljuk. Például a POSITION0 séma arra használható, hogy a csúcspont puffer pozíció adatait a csúcspont árnyalóhoz kössük A csúcspont és pixel árnyalóknak különböző bemenő sémáik vannak, mivel a feldolgozás különböző fázisait végzik el. A csúcspont árnyaló bemenő sémái csúcsonkénti információkat tartalmaznak, amelyek a csúcspont pufferből kerülnek az árnyaló programba (mint például pozíció, normál vektor, textúra koordináta, szín, stb.). A pixel árnyaló bemeneti sémái pixelenkénti információkat tartalmaznak a raszterizáló egység számára. A csúcspont árnyaló által szolgáltatott kimenetet dolgozza fel a pixel árnyaló.
15
A bemenő adatokhoz kétféle képen is hozzárendelhetjük a sémákat. Az egyik mód a változó után írt ':' és a séma neve. A másik mód egy bemeneti struktúra definiálása, ahol minden egyes elemhez hozzárendeljük a sémát. A következő példa mindkettőt bemutatja: // Bemeneti struktúra definiálása sémával struct InStruct { float4 Pos1 : POSITION1 }; // Pozíció adatként deklarált Pos nevű változó float4 main( float4 Pos : POSITION0, InStruct In ) : POSITION { return Pos * In.Pos1; } // Színként deklarált Col nevű változó float4 mainPS( float4 Col : COLOR0 ) : COLOR { return Col; } Csúcspont árnyaló bemeneti sémái: Séma POSITIONn BLENDWEIGHTn BLENDINDICESn NORMA n PSIZEn COLORn TEXCOORDn TANGENTn BINORMALn TESSFACTORn
Leírás Pozíció Vegyítési súly Vegyítési index Normálvektor Pont mérete Szín Textúra koordináták Tangens Kétirányú normális Tesselációs faktor
Pixel árnyaló bemeneti sémái: Séma Leírás COLORn Szín TEXCOORDn Textúra koordináták Ahol n egy egész szám. Például: POSITION0, COLOR1, stb.
16
3.5 Árnyaló program kimenetei A csúcspont és pixel árnyalók kimenő adatokat biztosítanak az őket követő grafikus futószalag egységeknek. A kimeneti sémák specifikálják, hogy a generált kimenő adatokat milyen módon kapcsolódnak a következő futószalag-fokozat bemenetéhez. Csúcspont árnyaló kimeneti sémái a pixel árnyalóhoz és raszterizálási fázishoz kötik a kimenő adatokat. Minden csúcspont árnyaló kimenetének tartalmazni kell legalább a pozíció adatokat, amit a raszterizáló egység fog felhasználni és nem a pixel árnyaló. Séma POSITION PSIZE FOG COLORn TEXCOORDn
Leírás Pozíció Pont mérete Köd Szín (pl. COLOR0) Textúra koordináták (pl. TEXCOORD0)
Pixel árnyaló kimenetének minimálisan egy szín sémát kell tartalmaznia, amit az alfa csatorna alapján történő színvegyítési fokozathoz kapcsol. A mélység kimeneti sémát a cél mélység értékének megváltoztatására szokták használni az adott raszter pozíció esetén. Megjegyzendő, hogy a mélység kimeneti sémát és a több megjelenítési célt nem minden árnyalási modell támogatja. Séma Leírás COLORn Szín az n-ik megjelenítési célhoz Mélység érték DEPTH Ahol n egy egész szám. Például: COLOR0, stb.
17
3.6 Saját programba való ágyazás Árnyaló kód főprogramba való beépítéséhez a D3DX Effect függvényeit fogjuk felhasználni. A D3DX a Direct3D segédkönyvtárait foglalja magába, beleértve az Effect komponenst is. Egy effect fájl assembly vagy HLSL nyelvű árnyalási kódot tartalmaz. Az árnyalási program egy .fx vagy .fxl kiterjesztésű állomány, ami egy vagy több Techniques-nek nevezett effektet tartalmaz. Egy tipikus effect fájl a következő képen nézhet ki: float4x4 view_proj_matrix; texture g_ImgTex0; sampler SamplerTex0 = sampler_state { Texture = ; MinFilter = Linear; MagFilter = Linear; MipFilter = Linear; }; struct VS_INPUT { float4 pos float4 tex0 };
:POSITION; :TEXCOORD0;
struct VS_OUTPUT { float4 pos :POSITION; float4 tex0:TEXCOORD0; }; VS_OUTPUT VertScene( VS_INPUT In ) { VS_OUTPUT Out = (VS_OUTPUT) 0; Out.pos = mul(In.pos, view_proj_matrix); Out.tex0 = In.tex0; return Out; } float4 PixScene( VS_OUTPUT In ) : COLOR { return tex2D( SamplerTex0, In.tex0); } technique RenderScene { pass P0 { VertexShader = compile vs_1_0 VertScene(); PixelShader = compile ps_1_0 PixScene(); } }
18
Miután eltároltuk az effeteket egy fájlban, szeretnénk azt a programunkban használni. Első dolgunk a fájl betöltése és az alapján az effect létrehozása, amihez a D3DXCreateEffectFromFile() API függvényt használjuk. Ezt követően megfelelően feltöltünk minden konstanst: g_pEffect->SetMatrix( "view_proj_matrix", &mViewProjection ); g_pEffect->SetTexture("g_ImgTex0", g_pTexture );
Majd beállítjuk a kívánt technikát, és kirajzoltatjuk a geometriát: g_pEffect->SetTechnique( "RenderScene" ); g_pEffect->Begin( &cPasses, 0 ); // Az első (és egyetlen) technikánk kiválasztása g_pEffect->BeginPass( 0 ); // Alakzat kirajzolása pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); pd3dDevice->SetStreamSource (0, g_pVB, 0, sizeof(CUSTOMVERTEX)); pd3dDevice->DrawPrimitive (D3DPT_TRIANGLELIST, 0, 2); g_pEffect->EndPass(); g_pEffect->End();
Lehetőség van a HLSL kódunk D3DX Effect nélküli integrációjára is. Ez némi többletmunkával jár, amit eddig az Effect interfész vállalt magára. Ennek ismertetésére nem tér ki jelen dokumentum. Részletesen megtalálható a DirectX SDK[3] HLSLwithoutEffect nevű példaprogramjában. Érdemes figyelemmel kísérni a DirectX SDK[3] frissítéseket, ugyanis így hasznos D3DX segédeszközökhöz és kiforrottabb, optimalizáltabb HLSL fordítókhoz juthatunk hozzá.
19
4 Képfeldolgozás [2][3] alapján 4.1 Képtérben történő műveletek 4.1.1 Pont alapú transzformációk
Eredeti képpont visszanyerése Átviteli függvény: g(x,y) = f(x,y)
Az eredeti képünk egy textúrában van eltárolva. A textúrához egy mintavételezőn keresztül férünk hozzá. Egy pixelt a textúrán a textúra koordináta azonosít. Tehát egy adott pixelhez tartozó színértékeket a tex2D() függvénynek átadott mintavételező és textúrakoordináta alapján kapható meg. Ennek megfelelően az eredeti képet előállító pixel árnyaló kódja: //------------------------------------------------------------------------// Original Image ([A4]. Ábra) //------------------------------------------------------------------------float4 PixScene( VS_OUTPUT In ) : COLOR { return tex2D( SamplerTex0, In.tex0); }
Invertált kép Átviteli függvény: g(x,y) = (Maximális érték) - f(x,y) Hagyományos képfeldolgozás esetén a maximális érték 255, mivel az egyes színkomponenseket általában egy 8 bites egész szám ábrázol. A HLSL esetén minden színkomponenst egy 32 bites lebegőpontos szám azonosít, aminek az értéktartománya 0.0-tól 1.0-ig kell hogy terjedjen, különben csonkításra kerül. Tehát egy pixel negáltja a következő képen kapható: //------------------------------------------------------------------------// Inverted Color ([A5]. ábra) //------------------------------------------------------------------------float4 InvertScenePS( VS_OUTPUT In ) : COLOR { return 1.0f - tex2D( SamplerTex0, In.tex0); }
20
[A4]. Ábra – Eredeti kép
[A5]. Ábra – Invetált kép
Szürkeárnyalatos kép Egy színes kép szürkeárnyalatos megfelelőjét megkapjuk, ha színösszetevőnként vett összeget elosztjuk hárommal. Fontos megjegyezni, hogy a pixel alfa csatornáját ne változtassuk meg. Erre szolgál a (float3) típus konverzió, így csak az első három komponensen (r,g,b) végezzük el a műveleteket. //------------------------------------------------------------------------// Grayscale Image ([A6]. Ábra) //------------------------------------------------------------------------float4 GrayScalePS( VS_OUTPUT In ) : COLOR { // Hagyományos megoldás //float4 c = tex2D( SamplerTex0, In.tex0); //return (float4)((c.r + c.g + c.b)/3.0f);
}
// Tömörebb megoldás return dot( (float3)tex2D( SamplerTex0, In.tex0), 0.3333333) ;
[A6]. Ábra – Szürkeárnyalatos kép
21
Egyedi átviteli függvények Képletekkel nehezen leírható átviteli függvények és időkritikus alkalmazások esetén hasznos minden egyes bemenő értékhez előre kiszámítani a neki megfelelő kimenő értéket. Ezek után a későbbiekben már csak elő kell keresni a kimenő értéket. Ezt a technikát használva gyorsíthatunk meglévő kódot és utasításokat spórolhatunk meg, amely a kötött utasításhosszal rendelkező árnyaló nyelv esetén fontos lehet. Az előre kiszámított értékeket egy 1D textúrában tárolunk, és a címzéshez az adott pixel szürkeárnyalatos értékét használjuk fel. Példaként álljon itt a Sepia átviteli függvény: //------------------------------------------------------------------------// Sepia Transfer function ([A7]. Ábra) //------------------------------------------------------------------------float4 SepiaPS( VS_OUTPUT In ) : COLOR { float3 lut = dot( (float3)tex2D( SamplerTex0, In.tex0), 0.3333333) ; return tex2D(SamplerSepiaTex,lut); }
Pont alapú küszöbölés Átviteli függvény: g(x,y) = (maximális érték) Ha f(x,y) > Küszöb érték g(x,y) = (minimális érték) Ha f(x,y) <= Küszöb érték Az esetünkben a maximális érték 1, a minimális pedig 0. //------------------------------------------------------------------------// Threshold ([A8]. Ábra) //------------------------------------------------------------------------float4 ThresholdPS( VS_OUTPUT In ) : COLOR { float gray = dot( (float3)tex2D( SamplerTex0, In.tex0), 0.3333333) ; if(gray > g_UserInput) gray = 0.0f; else gray = 1.0f; }
return (float4)gray;
[A7]. Ábra – Sepia stílus
[A8]. Ábra – Küszöbölésen átesett kép
22
4.1.2 Ablak vagy maszk alapú transzformációk A kép adott pixelét a környezetében lévő pixelek függvényében módosítjuk, pixelről pixelre haladva. A pont alapú műveletekhez hasonlítva idő- és számításigényesebb, de hatékonyságuk is jelentősebb. Maszkok használata: • A forrás kép minden pixelére egy maszkot helyezünk, úgy hogy annak origója az adott pixelre essen • A forrás kép maszk alatti pixeleit megszorozzuk a maszkban szereplő súlyokkal • Az eredmény a súlyozott pixelek összege vagy azok skálázott értéke A megvalósításhoz ki kell számítani a szomszédos pixelekhez tartozó textúrakoordináta eltolást. A textúratér 0-tól 1-ig terjed. A [0,0] a kép bal alsó sarkát, az [1,1] pedig a jobb felső sarkát azonosítja. Amennyiben a kép szélességét az imageWidth, a kép magasságát az imageHeight változó reprezentálja, az eltolások a következő képen számítható: float width = 1.0f/(float)imageWidth; float height= 1.0f/(float)imageHeight;
Ezek alapján tetszőleges méretű, alakú maszkot hozhatunk létre. Például egy 3x3-as maszk eltolási textúrakoordinátái a következőek: [-w,-h] [-w, 0] [-w, h]
[ 0,-h] [ 0, 0] [ 0, h]
[ w,-h] [ w, 0] [ w, h]
A következő átlagoló szűrőt megvalósító programban az offsetsPS tömb tárolja az említett eltolásokat. Természetesen a maszk közepét kivéve, ugyanis az az eredeti pixel. // Indexek értelmezése // 0 1 2 // 3 P 4 // 5 6 7 float4 offsetsPS[8]; //------------------------------------------------------------------------// Átlagoló szűrő ([A9]. Ábra) // 1 1 1 // 1 1 1 // 1 1 1 //------------------------------------------------------------------------float4 AveragePS( VS_OUTPUT In ) : COLOR { float4 texCoords; float4 texSamples[8]; for(int i=0; i<8; ++i) { // Szomszedos pixel tex. koord. kiszamitasa texCoords = In.tex0 + offsetsPS[i]; // Szín kinyerese texSamples[i] = tex2D( SamplerTex0, texCoords); } float4 avg = 0.0f;
23
}
for(int i=0; i<8; ++i) { // Pixel környezetének hozzáadása avg += texSamples[i]; } // Pixel hozzáadása avg += tex2D( SamplerTex0, In.tex0); avg /= 9.0f; return avg;
A gyorsabb kód elérése érdekében célszerű elvonatkoztatni a maszkok elméleti definícióitól és pl. egy általános 3x3-a maszk függvény helyett több speciális függvényt megírni. Általános megvalósítás esetén 8 szorzás és 7 összeadás művelet szükséges, összevonásokat használva 6 szorzást spórolunk meg a Kirsch szűrő esetén. //------------------------------------------------------------------------// Kirsch ([A10]. Ábra) // 5 5 5 // -3 0 -3 // -3 -3 -3 //------------------------------------------------------------------------float4 KirschPS( VS_OUTPUT In ) : COLOR { float4 texCoords; float4 texSamples[8]; for(int i =0; i < 8; i++) { texCoords = In.tex0 + offsetsPS[i]; texSamples[i] = tex2D( SamplerTex0, texCoords); texSamples[i] = dot( (float3)texSamples[i], .333333f); }
}
float4 ret; ret = 5 *(texSamples[0] + texSamples[1] + texSamples[2]); ret -= 3 *(texSamples[3] + texSamples[4] + texSamples[5] + texSamples[6] + texSamples[7]); return ret;
[A9]. Ábra – Átlagoló szűrő hatása
[A10]. Ábra – Kirsch szűrő hatása
24
4.1.3 Teljes képre vonatkozó transzformációk Az eredmény pixelérték olyan műveleten eredményeképpen jön létre, amely két vagy több képet használ fel. Általában az eredmény pixel ugyanabban a helyzetben marad. Művelet Összeadás Kivonás Szorzás, osztás Átlagolás Vagy (OR) És (AND)
Hatása Információk kombinálása Változásdetektálás Fényesség beállítás Képminőség javítás Kép adott részének hozzáadása Kép adott részének kivágása
HLSL megvalósítása nem okozhat problémát. Két kép esetén mindössze annyi a teendőnk, hogy két mintavételezőt deklarálunk a két képnek, egyenként kiolvassuk a pixelértékeket és elvégezzük rajtuk a kívánt műveletet.
4.1.4 Geometriai transzformációk Geometriai transzformációk alatt a pixelek eltolását, elforgatását, skálázását értjük. Ezen transzformációk célja kiszámítani, hogy miként nézne ki az adott kép, ha más kamerapozícióból készítettük volna a képet. A transzformációk eredménye könnyen megkapható mindössze annyi a feladatunk, hogy a virtuális kameránkat eltoljuk vagy épp más orientációba helyezzük. Ezen műveletek a Direct3D általános programozásához tartoznak és nem közvetlenül a grafikus kártya HLSL nyelvű programozásához, így itt nem kerül részletesebb tárgyalásra.
4.2 Frekvencia tartományban történő műveletek A pixel árnyaló 2.0-ás verziójától kezdve elérhetővé vált a frekvencia tartományba való áttérés lehetősége, a megnövekedett programhossznak és a több regiszternek köszönhetően. A Fast Fourier transzformáció valós idejű megvalósítását [2]-es referenciában megtalálhatjuk.
25
5 Értékelés A dokumentum egyedülálló és hiánypótló abban a tekintetben, hogy nem áll rendelkezésre a dokumentum írásának pillanatában magyar nyelvű szakirodalom. A mű felhívja a figyelmet a magas szintű árnyaló nyelvek fontosságára és aktualitására. Továbbá lépésről lépésre részletezi a HLSL nyelvet, azzal a céllal, hogy az olvasó is képes legyen egyedül megvalósítani programozási elképzeléseit. Konkrét képfeldolgozási példákon keresztül mutatja be a gyakorlati használhatóságot. A dolgozat fő célját elérte, ami az volt, hogy felkeltse a figyelmet a jövő technikáira és az érdeklődő minimális erőfeszítések árán képes legyen egy "Hello világ!" HLSL program megírására. Jelen dolgozat nem tudott a teljességre törekedni. Egyrészt a szerző egy régebb óta fejlesztett projekttel volt elfoglalva, másrészt a dolgozat befejezésének pillanatában jutott olyan kurrens idegen nyelvű dokumentum[5] birtokába, amelyre már nem jutott idő feldolgozni.
6 Magasszintű árnyaló nyelvek jövője Egyértelműen kijelenthető, hogy új korszak kapujához érkeztünk. Oda, ahol a CPU és a GPU vállvetve dolgozik programjaink futtatásán. Az árnyaló nyelvek 3.0-ás verziójának megjelenésével már nem is annyira a jövőről beszélünk, hanem a jelenről. 2005 augusztusában Los Angeles-be megtartott nemzetközi számítógépes grafikai konferencián [4] szó volt a grafikus hardver általános célú számításokra történő használásáról. Mostanra pedig mindenki számára elérhetővé tették az interneten. Kellemes videókártya programozást és a témában való elmélyülést [5] kíván a szerző.
7 Elérhetőség A szerző bármilyen észrevételt szívesen fogad a következő címen: Szőke Imre: [email protected] A dolgozat és a hozzá kapcsolódó alkalmazás honlapja a következő címről érhető el: http://www.starfleet.hu/szokeimre/hlsl/index.htm
26
8 Irodalomhivatkozások [1] Craig Peeper and Jason L. Mitchell, "Introduction to the DirectX 9 High Level Shading Language" in ShaderX 2 - Introduction And Tutorials with DirectX 9.0, Wolfgang Engel editor, Wordware, September 2003. http://www.ati.com/developer/ShaderX2_IntroductionToHLSL.pdf (2005-11-05) [2] Jason L. Mitchell, Marwan Y. Ansari and Evan Hart, "Advanced Image Processing with DirectX 9 Pixel Shaders" in ShaderX 2 - Shader Tips and Tricks, Wolfgang Engel editor, Wordware, Sept. 2003. http://www.ati.com/developer/shaderx/ShaderX2_AdvancedImageProcessing.pdf (2005-11-05) [3] Jason L. Mitchell "Image Processing with Pixel Shaders in Direct3D" http://mirror.ati.com/developer/shaderx/ShaderX_ImageProcessing.pdf (2005-11-06) [4] SIGGRAPH2005 The 32nd International Conference on Computer Graphics and Interactive Techniques http://www.siggraph.org/s2005/ (2005-11-06) [5] SIGGRAPH 2005 GPGPU COURSE, SIGGRAPH 2005, Los Angeles, August 2005 GPGPU : General Purpose Computation On Graphics Hardware http://www.gpgpu.org/s2005/FullCourseNotes.pdf (2005-11-06) [6] Microsoft. DirectX 9 Software Development Kit http://msdn.microsoft.com/directx/ (2005-11-05) [7] Dr. Szirmay-Kalos László, Antal György, Csonka Ferenc, „Háromdimenziós grafika, animáció és játékfejlesztés”, Computerbooks, 2003.
9 Ábrajegyzék [A1–3] DirectX 9 Software Development Kit [6] Help ábrái [A4–10] Saját programból készült képernyőmentés
27