Miről olvashatsz az oldalon:
képalkotás lépései általánosan
filterezések: point sampling, bilinear, trilinear, anisotropic
Anti Aliasing
Fill Rate
Shaderek: Vertex Shader 1.1, Pixel Shader1.4
shader példák: sepia, csökkenő fényhatás, specular fényhatás változó exponenssel, anisometrikus fény, valós idejű textúragenerálás - Pixel Shader 2.0
HLSL és Render Monkey
Vertex Shader 2.0
Pixel Shader 2.0

Képalkotás lépései

1. A grafikus API-n keresztül beérkeznek a vertex-adatok (3 koordináta) és a textúrák (valamekkora felbontású bitmapek). Ezt a lépést megelőzi a fizika és elmozdulások kiszámolása a CPUban.
2. A megérkezett csomópontokból háromszögképzés (ezek a háromszögek a 3D modellek építõkövei) - Tessellation vagy Triangularisation
3. A háromszögekből felépített poligonok transzformálása a kamera nézőpontjának megfelelően - Geometric Transformation
4. A létrehozott geometriából és a textúrákból 3 dimenziós jelenet (scene) kiszámolása:

- Clipping - a jelenet olyan részeinek törlése, melyek nem látszanak az aktuális nézőpontból - kikerülnek a látómezőből (így azokat nem kell renderelni).
- Textúrázás és képkorrekció - az elkészült és megvágott 3d-s jelenetet leképezzük a monitor síkjára. El kell dönteni, milyen színűek lesznek az egyes pixelek (pixel színét meghatározó eljárás gyakran raszterizációnak nevezik.). Ez a megadott textúrák és 3d objektumok társításával (textúratérkép(ek) létrehozása), fény és egyéb hatások (köd, tükröződés stb.) kiszámolásával történik. Ezt a folyamatot renderelésnek nevezzük. Közben képkorrekciók vagy inkább képminőség javító eljárások is végrehajtódnak, mint szűrés (filtering) és anti-aliasing (AA), melyekről még szó lesz.
Nem lehet a textúrázást és a korrekciót különválasztani, mert egy-egy pixelen több textúrázás-korrekció futam is végrehajtódhat (pl. első lépésben hullámos fémfelület és ennek szűrése, második körben ezen a felületen egy tükröződés és a tükörkép szűrése...
Jelentősen gyorsítja az eljárást, ha csak azoknak a pontoknak a színét számoljuk ki amelyek ténylegesen megjelennek a képernyőn (pl. nem kell egy autó teljes modelljét lerenderelni, ha azt eltakarja egy fal). Ehhez egy ún. Z-buffert használunk, ugyanis egy pixel kamerától mért távolságát éppen a Z koordinátája adja meg. Ha van két pixelünk, aminek az x és y koordinátája megegyezik (ugyanott lesz a képernyőn) akkor csak annak a színét számoljuk ki amelyiknek kisebb a z értéke, ugyanis eltakarja a másikat.

5. Az elkészült piexelhalom egy bufferbe kerül (frame-buffer) és a RAMDAC-on keresztül jut a képernyőre.

Filterezés

Amikor a háromszögek segítségével elkészült a 3D váz , el kell dönteni, hogy a képen megjelenő pixeleknek milyen színük legyen. Ennek a színnek az alapja egy textúra térképen (texture map) található, ami egy 2D leképezése a textúrázott 3D világnak. A textúra térkép pixeleit texeleknek hívjuk. A nagy kérdés az, hogy melyik texelek határozzák meg a képernyőnk egyes pixeleinek a színét. Legyen 1024×768 a megjeleníteni kívánt kép pixelmérete. Úgy képzelhető el az egész színmeghatározás mintha egy 1024×768 lyukú rácson keresztül szemlélnénk a mi 3Ds jelenetünket. Belesünk egy lyukon és amilyen színt látunk az lesz a képpont színe. De mi van, ha több színes pontot (texelt) látunk a lyukon keresztül (ez az eset fordul elő gyakrabban)?! Ha a térbeli alakzat, amire rálátunk a lyukon keresztül merőleges a rácsunkra akkor kör alakban látunk egy pixelcsoportot ha nem merőleges (elég gyakran) akkor ellipszis alakban látunk egy pontcsoportot.

1. Point Sampling
Egyszerűen vesszük a középső pixelt, figyelmen kívül hagyva a többit és a "rácson keresztül látott" alakzat formáját. Ez a módszer kis memóriaigényű de nem valami szép megoldás. Ha egy nagy objektum nagyon közel kerül a kamerához, akkor borzasztóan kockás lesz, mert több pixelhez is ugyanaz a texel tartozhat, ugyanazzal a színnel.

2. Bi-linear Filtering
Kör alakú alakzattal dolgozik, amihez 2×2texellel közelít (2×2texelt vesz figyelembe). A pixel színének megállapításához a 4texel átlagát veszi. A közeli objektumok így szépen elmosódnak (gyengülnek az élhibák és textúrazajok lásd AA), igaz ez csak egy mellékes hatás. Ennek a szűrésnek két hátránya van:
- kör (vagyis hát 2×2pont) formájú alakzattal dolgozik, ami az esetek többségében pontatlan (kamera irányára nem merőleges poligonok).
- 4szeres sávszélesség igénye van a Point Samplinghoz képest.

3. Tri-linear Filtering
MIP-Mapping ötletét adja hozzá a Bi-linear-hoz. Mindkét MIP szinten végrehajtunk egy Bi-linear szűrést és a két értéket átlagoljuk. A memóriaigény megint duplázódik és még mindig kör alakzattal dolgozunk.

4. Anisotropic filtering
Az eddigi szűrési eljárások valójában csak arra voltak jók, hogy a közeli alakzatokat elmossák (ez valójában mellékhatás) a távoli alakzatok még mindig pixel össze-visszaságok maradnak az alulmintavételezés miatt. A legjobb minőség akkor érhető el, ha minden egy rácspontból látható texelt felhasználunk a szín meghatározásához és figyelünk a textúratérképen látható alakzat formájára is. Persze ezek az eljárások nagyon komoly memóriaigénnyel rendelkeznek. Általában 16-32 texelt vesznek figyelembe pixelenkét és ügyelnek az alakzat elfordulására is.
Anizometrikus szűrést többféleképpen meg lehet valósítani. Az ATi pillanatnyilag RIP mapping megoldást használ, ami egyfajta elforgatott MIP mapping. Nem csak az egyes MIP szinteket számolja ki a kártya, hanem bizonyos szögelforgatott textúrákat is. Ezeket az elforgatott textúrákat használja, miután megállapította az éppen renderelt poligonlap elfordulását a kamerához képest. Az eljárás gyors mivel nem kell minden pixelnél elforgatni a textúrát csak egyszer a RIP mapok képzésekor, de nem teljesen pontos minden szögre. Az nVidia valami fejlettebb forgató eljárást alkalmaz, ami független az elfordulás szögétől. (ha valaki ismer részleteket az nVidia módszeréről legyen szíves tájékoztasson)

Aliasing és Anti Aliasing

Mi az az Alisaing? Eredendően egy mintavételezési hibáról beszélhetünk.

Van a mi kis analóg világunk a maga majdnem végtelen (szemünk számára végtelen, mert nem látunk molekulátis szinten) részletességével, és ezt szeretnénk minél jobban megközelíteni a monitoron (valósághű). Mivel a számítógép digitális, van egy részecskeméret (pl. egy pixel a monitoron) ami alá nem tud menni, azok a részletek, amik a való világban kisebbek ennél a részecskeméretnél nem - vagy csak nagyon nehezen - jeleníthetőek meg. Ezt a problémát hívják aliasingnak. Gyakorlatban 2 megjelenési formája van:

1. töredezett élek - ezzel gondolom mindenki találkozott

2. megjelenő majd eltűnő részletek - Felmerül a kérdés, hogy miért nem lehet ezt gondos tervezéssel (nem tervezek túl kis objektumokat) elkerülni. Sajnos nem lehet, mert egy hatalmas objektum is összemehet apróra ha távol kerül a kamerától.

Hogyan lehet védekezni ellene? - Anti Aliasing

Valamilyen módon finomítani kell a felbontást, persze ez további számolással és memória valamit memóriasávszélesség zabálásával jár. Persze semmi sincs ingyen még a virtuális világban sem.

Akkor hogyan? Túlmintavételezéssel!

Növeljük kétszeresére a felbontást mindkét irányba, ezzel megnő a kép részletessége viszont drasztikusan nő a feldolgozás összetettsége is. Így egyetlen pixelt már 4 pixel fog reprezentálni a megnövelt képünkön. Ezen a duplázott képen számoljuk ki a rendereléseket, majd visszaméretezzük a képet úgy, hogy 2×2esével kiátlagoljuk (összeadjuk, majd elosztjuk 4el) a pixeleket.
Ezt a kétszeres növelést (kétszeres túlmintavételezés) hívják Nyquist mintavételezési törvénynek. A túlmintavételezést megcsinálhatjuk többféleképpen is. A legegyszerűbb a duplázás x és y irányba OGSS (ordered gird super sampling), de használható elforgatással kapott mintavételezés RGSS (rotated gird super sampling) amikor a 4 (vagy több) szubpixel az eredeti kép elfogatásával jön létre. Szintén használható valamilyen fajta multi sampling, ahol nem a kép duplázásával nyerünk újabb mintavételi részleteket hanem valamilyen más transzformációval. A multisampling általában gyengébb képet ad mint a supersampling de jóval gyorsabb.




OGSS mechanizmusa

Akkor most lássuk a hatást gyakorlatban:

Először egy mintavételezés. Rajzoltam pár fekete sávot fehér háttérrel és ezeket megnéztem 110, 133 és 200 százalékos nagyításban. Látható hogy csak 200%nál maradt töredezés mentes a sáv felülete, mert itt jött ki a kétszer (egész számszor) annyi mintavevő hely.


100% - 110% - 133% - 200%
Mire képes az AA filterezéssel:
Csináltam egy fekete-sárga soronként váltakozó textúrát :

Elforgattam 45fokkal egy képfeldolgozó programmal:

 

Ugyanezt a mintát felnagyítottam kétszeresére (filterezés nélkül, erre majd visszatérek), elforgattam 45fokkal, kiátlagoltam, visszaméreteztem.
Azért van eltérés bőven...


45fokkal elforgatásnál nem tudtam elkerülni a filterezést csak úgy, ha levettem a kép színmélységét 2 színre (fekete, sárga). Így elég kiábrándító eredményt kaptam:

Látható hogy hasznos a filterezés. Meg kell jegyezni, azért választottam ezt a textúrát mert nagyon érzékeny az alulmintavételezésre (csak a sakktábla durvább), gondoljatok csak arra hogyan "viselkedik" egy aprómintás zakó a TVben :-)

 


Fill Rate - egy másodperc alatt kiszámolt pixelek vagy texelek száma.

Elvi maximális fill rate.
Ez kb. úgy hangzik: "optimális helyzetben, szerencsés esetben" vagyis SOHA! Az egészet még bonyolítja, hogy nem csak pixel hanem textúra fill rate-ről is szó lehet, sőt ezen paramétereknek aránya chipenként változik.

Maximális fill rate
Egy pixel színét a textúrázó egységek számítják ki a vertex egységek által felépített 3 dimenziós világból. A színek meghatározásához filterezéseket) használunk. Maximális érték akkor lép fel ha a legegyszerűbb használatban lévő filterezést (Bi-linear) alkalmazzuk.

Elvileg (végig optimális esetről beszélek)
Az optimum nagyban függ a grafikus chipek felépítésétől. Ha olyan felépítés van, hogy minden futószalaghoz egy textúrázó egység (TU) tartozik, akkor a bi-linear filternél jól jár, mert csak egy textúra határozza meg a pixel színét. Ha olyan az architektúra, hogy futószalagonként 2 TU van, akkor bi-linear filterezésnél a második textúrázó "alszik". Ezeknek a chipeknek a fill rate-je ugyanannyi bi-linear és tri-linear filterezésnél is, míg az egy TU/futószalagos megoldások fill rate-je a felére esik tri-linear használatánál.

Elvi maximális pixel fill rate = üzemfrekvencia × textúrázó futószalagok száma
ATi Radeon 9800pro-nál ez 380MHz × 8 = 3040 Mpixel/s
Nvidia GF FX5900U-nál ez 450MHz × 4 = 1800 Mpixel/s

Persze a valóságban ez korántsem ilyen egyszerű, mert egy pixel színét általában nem egy, hanem több textúra (alaptextúra, árnyék minden fényforrásról, esetleges tükröződések, alfák stb.) határozza meg, és nem feltétlenül bi-lineáris szűrést alkalmaznak. Ilyenkor több kört kell megtennie a pixelnek a futószalagon, és ezek a körök egymástól függhetnek. Azt lehet mondani, hogy az egy TU/futószalag felépítésben több kört tesznek a pixelek a futószalagban, míg a több TU/futószalag felépítésnél gyakrabban lehetnek kihasználatlan (idle) TU-k.

Másik tényező ami lassítja a feldolgozást a Z-buffer ürítése minden képkocka után. Ez azért fontos, hogy a kártya ne számoljon az előző képkocka mélységeivel. Az ürítés alatt várakoznak a renderelők. Egyes chipeken (S3 DeltaChrome-n, ATI R3xx-eken, Nvidia NV3x-eken) megoldották az időveszteség nélküli Z-buffer ürítést. Ez az ürítés szoftveresen szintén megvalósítható (quake2 tudott ilyet) vagy elkerülhető.

Következő lassító tényező a memória laptörés. A grafikus chip (CPU-hoz hasonlóan) nem látja egyszerre az egész rendelkezésre álló memóriát csak részeit, lapokat. Amikor olyan adat kell, ami más lapon van akkor az aktuálisat el kell tenni és elő kell venni azt amin a keresett adat van, ez időbe kerül.

Utolsó lassító tényező amit érintek az egyszerű memóriahiány, buffer kiürülés. Ez ma már talán nem jön elő, de például TNT chipeknél még néha előfordult (pl. Unreal-ban, ami korának legteljesítményigényesebb játéka volt), jelentős lelassulást eredményezett egy rövid időre. Részletesebben nem foglalkozom vele, akit érdekel gondoljon bele ütemenként hány bit adat jut egy mai VPUba a RAMból és hány bit adat kell egyetlen multitextúrás, 32bit színmélységű, bi-lineárisan szűrt pixel kiszámolásához...


Egy példa az összetettségre - enyhén rücskös fémfelületen tükröződő képen egy árnyék 2 fényforrás hatására:
alaptextúra tri-lineárisan szűrve + tükröződés (előző képkocka textúraként ráfeszítve a felületre) + Bump map (rücskösség) + árnyékok összege a két dinamikus fényforrásból (2 textúra) és akkor még nem kavartunk semmilyen ködöt az egész jelenet elé...
Itt már távol állunk az elvi maximálistól fill rate-től, de nagyon közel vagyunk a valósághoz.

Valós példa és hiányosság - nézzétek meg! - Unreal2 - hajónkon a küldetésszobából kifelé jövet, az ajtó ablakán látszik a folyosó falának textúrája, tükröződik a karakterünk arca és a mögöttünk lévő felület
1. textúra: folyosó fala
2. textúra: ablak átlátszósága (alfa)
3. textúra: karakterünk arca
4. textúra: karakterünk mögötti szoba (itt kicsit csaltak, statikus textúrát alkalmaztak, mert a mögöttünk lévő szervizaknát kinyitva is bezárt marad a tükörképen, és ha Aida mögénk áll, akkor sem látszik a tükörképe - sebaj)
5 . textúra: árnyék


Van egy másik paraméter a textúra fill rate, egysége texel/s. A képen megjelenő pixelek színe texelek segítségével határozható meg. Bi-lineáris filterezéssel egy textúránál 4 texel határozza meg a pixel színét. Elvileg a textúra fill rate négyszerese a pixel fill rate-nek. Régebben az ügyes PResek (főleg ATi-nál) ezt a négyszeres értéket adták meg fill rate-nek. Mostanában átfogalmazták ezt a paramétert, ma is szoroznak:

elvi maximális textúra fill rate = üzemfrekvencia × textúrázó futószalagok száma × textúra egységek/futószalag.
ATi Radeon 9800pro-nál ez 380Mhz × 8 = 3040 Mpixel/s
Nvidia GF FX5900U-nál ez 450MHz × 4×2 = 3600 Mpixel/s


Hogyan mérhető le a valós fill rate?

Benchmark programmal vagy valós játékkal. A 3Dmark2001-nek már egész jó a fill rate tesztje. Eléggé procifüggetlen, nekem 8500as Radeonhoz hajszálra ugyanakkora értéket mért AthlonXP 1433MHz-hez és AthlonXP 2000MHz-hez. Nemsokára részletezem a pontos technikát. A lényeg hogy olyan jelenetet generál, ami nem erőlteti meg a CPUt és a vertex egységet, hogy ki tudja magát futni a pixelező rész, és pontosan kiszámolható hány pixelművelet hajtódott végre egy kép megjelenítéséhez. Ilyen jelenet lehet 2-2 háromszögből összeállított monitorral párhuzamos síkok sorozata. A legutolsó sík kap egy átlátszatlan textúrát, a többi valamennyire átlátszót, így minden pixelhez annyi számítás kell, ahány síkunk van. 1024×768as felbontásnál 786432×síkok száma pixel. Ezt fel kell szorozni az FPSel és megvan a fill rate. Ha jó nagy eltérő textúrát használunk, akkor előjön a lapozási hiba.

Shader-kódolási folyamat

1.
Magasszintű programozási nyelvben (HLSL, CG, Render Monkey) elkészül a 3D odjektum(ok) kódja. Legtöbb 3D modellező programból (3D Studio, Maya stb.) plugin segítségével szinte egyetlen klikkeléssel lehet ilyen kódot létrehozni.
2.
A magasszintű nyelv fordítója DirectX-re vagy OpenGL-re fordítja a kódot.
3.
A grafikus kártya meghajtóprogramja a kártya számára emészthető alacsonyszintu shader kódra fordítja a DX/OGL kódot. Ez a nyelv nagyon hasonlít az assemblyre.
4.
A kódolók optimalizációs célból belenyúlhatnak az elkészült alacsonyszintű kódba.
5.
Az elkészült kódot kapja a kártya.

Vertex és Pixel Shader részletei

Az itt leírtak alapjául a 2002. szeptember 19-i ATI Mojo Day, a 2002. tavaszi Intel Developer Forum, a 2002es Game Developers Conference és a Siggraph 2002 konferencia anyaga szolgált, melyeket T2K bocsátott rendelkezésemre, ezúttal is köszönöm! gátlástalanul vagdostam a .pdf-ekben, amivel lehet, hogy megsértettem egyesek szerzői jogait, de remélem célom szentesíti eszközeimet.

Vertex stream (folyam) formájában érkeznek a 3D csomópontok a rendszer felől a VGAhoz. Emellett a folyam mellet jönnek még a textúrák és shader programok (vagy régebben fix eljárások). A csomópontok és egyes vertex shader programok alapján történik a háromszögek és polygonok létrehozása, eltolása és megvilágítás, létrejön egy 2D textúratérkép, ami párhuzamos a monitor síkjára, és leírja melyik képpontban milyen textúra (textúrák) határozza meg a végső színt. Eddig minden a hagyományos DirectX7 elvek alapján zajlott, most jön az újítás:

DirectX7ben fix eljárások voltak bedrótozva melyek módosították az alakzatok relatív pozícióját (tangens térbe helyezés az objektum teréből), megvilágítást és árnyékolást számoltak. Egyre igényesebb és egyre több eljárásra volt szükség, amelyek felhasználásai programozófüggőek (az első GeForce256nak volt egy halom olyan utasítása, amit szinte nem is használtak valós alkalmazásban mert olyan gyorsan követték a GeForce2 új és jobb utasításai).

A DirectX8-al szakítottak a régi filozófiával és egy mikroprogramozható (egyszerű aritmetikai utasítások és memória írás/olvasás a’la Assembly) architektúrát valósítottak meg nem pedig kész eljárásokat. Olyan ez mintha a programozó egy LEGO készletet kapna, nem pedig kész játékelemeket. Így mindenki kénye kedvére csinálhat saját effektusokat.

Persze felmerül a kérdés, hogy mi lesz azokkal, akik nem értenek ehhez az assembly alapú mikroprogramozáshoz, vagy nem járatosak az effektustervezésben, és most, hogy nincsenek behuzalozott eljárások, nem tudnak érdemleges munkát végezni (feltehetőleg a játékfejlesztők egy része ilyen). Őket segítik a magasszintű shader programozási nyelvek (HLSL - high level shader language) mint az nVidia Cg vagy a hivatalos Microsoft HLSL. Ezek olyan magasszintű nyelvek, amelyek hasonlítanak a, sokak által ismert, C, Pascal stb. programozási nyelvekhez, amelyek már rendelkeznek előre megírt eljárásokkal, melyekkel újabb eljárások készíthetőek és ezt az "eljárásmaszlagot" egy fordító (compiler) alakítja assambly alapú mikroprogrammá, amit emészt a shader.

Ismét jöhet a kérdés, hogy a grafikus kolléga nem ért a magasszintű nyelvhez sem, mégis szeretne hatékonyan és gyorsan dolgozni, hiszen ő a művész, ő tudja egyes fényhatások milyen tulajdonságokkal bírnak. Nekik készültek a 3D tervező programokhoz (Maya, 3D Sutidio, Lightwave stb.) pluginek és célprogramok, amik valójában olyan magas- vagy alacsonyszintű eljárások, amelyeknek meghatározó tényezőit változtatni lehet, így módosítható (finomhangolható) az eljárás.

Az egészben a poén hogy ezeket a finomhangolásokat elvégezheti egy másik shader program is valós időben!

Akkor most merüljünk alá!

Egyelőre csak ATI anyagom volt de ígérem ránézek az nVidia megoldásaira is. Akit részletesebben érdekel a téma, nézzen rá a www.ati.com/developer címre. Először nézzük az R200 szintet vagyis Vertex Shader 1.1 és Pixel Shader 1.4, ez volt az ATi első teljesen DirectX8 kompatíbilis (Vertek Sahder-t és Pixel Shader-t használó) megoldása. A 3D megjelenítést a következő ábra illusztrálja:

Verex egységek végzik az eltolást és bemenetként szolgálnak a pixel futószalagokban, a raszterizálás határozza meg a textúratérképet, a pixel futószalagok csinálják a pixelenkénti (per pixel) finomságokat akár több körben is.

Mi az a regiszter? Olyan tárolórekesz, amelyen a GPU közvetlenül tud műveletet végezni, lehet irható, olvasható vagy mindkettő egyszerre. Fontos megjegyezni, hogy egy átlagos memóriaolvasás egy-két nagyságrenddel több időt vesz igénybe mint egy regiszterekkel elvégzett művelet.
A vertex egység minden regisztere 4 darab lebegőpontos számot tartalmaz (kivétel az address register ami csak egy lebegőpontos szám, később még szó lesz róla), ezért hívják gyakran 4D vektornak (számnégyes). Nézzük a regiszterek viszonyát:

ne feledd Vertex Shader 1.1ről van szó!

Következő dolog amivel foglalkozni kell az utasítások. Általában SIMD (simgle instruction on multiple data) jellegű utasítások vannak, amikhez bemeneti (source) regiszterekben jönnek az adatok és egy kimeneti (destination) regiszterbe kerülnek, ami az első source regiszterben tárolódik az utasítás levégzése után. Például: add r0, r0, r1 összeadja az r0 és r1 regiszterekben lévő számokat majd az eredményt az r0-ba ítja.
Egy Vertex shader program maximum 128 utasítást tartalmazhat. Akit részletesebben érdekel, nézze meg a következő ábrán, a fontos hogy 4 csoportba oszthatóak: verzió, konstans definiálás (regiszterbe írás), általános utasítás (alap aritmetikai műveletek és regiszterbe írás/olvasás), macro urasítások (keményebb aritmetika és mátrixszorzások).

A táblázat átböngészése után feltűnhet, hogy sem, osztás sem kivonás nincs. Ezeket a műveleteket „nem szeretik” a hardveres aritmetikai egységek, helyettük inkább reciprokkal szoroznak vagy negálttal (-r módosítóval érik el ASM szinten) összeadnak, mert ez a két művelet (reciprok és inverz) nagyon gyorsan elvégezhető (egy-két bit átkapcsolásával).
A vertex egység kimeneteit (megint regiszterek) láthatod a következő táblázatban, ezek a raszterizáló bemenetei, és a két „fényvektor” (oD0 - diffúz, oD1 - specular) interpolálva a Pixel shader bemenete v0 és v1 színregiszterként.

Itt egy egyszerű vertex shader kódrész:

 
instruction
d
s1
s2
 
1. vs. 1.1       - vertex shader verzió
2. dp4
oPos.x,
v0,
c0
- c0-c3 konstansokban tárolt nézeti mártixal felszorzott v0 csomópont kerül a oPos kimeneti regiszterbe
3. dp4
oPos.y,
v0,
c1
4. dp4
oPos.z,
v0,
c2
5. dp4
oPos.w,
v0,
c3
6. mov
oD0,
c4,
 
-c4 konstansban tárolt szín kerül a oD0 kimeneti regiszterbe a pixel shader részére.
d - cél (destination) regiszter, s - forrás (source) regiszter

Megérkeztünk a Pixel futószalaghoz. A pixel futószalag párhuzamosan végez egy vektor műveletet a regiszterek első három komponensével (általában r, g, b vagy x, y, z megnevezéssel) és egy skalár műveletet a negyedikkel (a-alpha vagy w). (meg kell még néznem a co-issue-nak nevezett esetet amikor a skalár és vektor résszel más-más művelet hajtódik végre).

Kétféle utasítás van, az aritmetikai és a textúracímzési. Textúra címzés természetesen a textúratérképekre vonatkozik, melyek lehetnek egy-, kettő-, vagy háromdimenziósak. Textúra mintavételező (sampler) a textúra- vagy ideiglenes regiszterekben tárolt koordináták alapján vesz mintákat. A textúra koordináták módosíthatóak aritmetikai utasításokkal. Dependent read-nak (függő olvasás) nevezik az egymást követő pixelkoordináták módosítását az előzőek függvényében.
Pixel shader 1.4 program két fázisból áll, melyeket phase utasítás választ el. Mindkét fázisban 6 textúra művelet lehet, utána 8 aritmetikai. Az első fázis aritmetikai utasítása a másik fázis textúrakoordinátáit számolhatja ki, ezt nevezik dependent texturing-nak (függő textúrázás). +jellel kapcsolnak össze olyan utasításokat ahol más-más művelet hajtódik végre az első 3 koordinátával és a negyedikkel.

Természetesen itt is vannak módosítók a forrás- és célregiszterekhez, egyes vektorkomponensek kiválasztásához (.r, .g, .b, .a vagy .x, .y, .z, .w), aritmetikai utasítások bemeneteihez és kimeneteihez, postfix és még jópár nagyon hasznos mindenre, ami már túlmutat az általam megcélozott részletességen (azért iderakom).

pixel shader 1.4 forrás regiszter módosítók

pixel shader 1.4 utasítás módosítók

Nézzünk egy egyszerű de látványos pixel shader effektet:

Szépia hátas: először a kiindulási repülőgépes képen először egy általános képlet alapján Luminance-t számolunk, ezzel átmegy szürkeárnyalatosba majd egy 1dimenziós textúra alapján eltoljuk sepia tónusba, ezzel régi film hatás érhető el.

eredeti -> luminance -> sepia

 
instruction
d
s1
s2
s3
s4
 
1. ps. 1.4      
 
 
- pixel shader verzió
2. def
c0,
0.30f
0.59f
0.11f
1.0f
- konstans definiálása a Luminance számoláshoz
3. texld
ro,
t0,
 
 
 
- mintavétel az eredeti repülős textúrából, Vertex Shader számolja a t0-t
4. dp3
r0,
r0,
c0,
 
 
- Luminance kiszámolása
5. phase
 
 
 
 
 
- fázisváltás
6. texld
r5,
r0,
 
 
 
- mintavételezés az 1dimenziós Luminance to Sepia textúraátmenetből ami lehetne akár vertex shaderrel számolt lineáris (vagy bármilyen) függvény Dependent read
7. mov
r0,
r5,
      - r0-ba pakolt kimenet megy további feldolgozásra a (fog, blending, frame buffer)
d - cél (destination) regiszter, s - forrás (source) regiszter

Ez a példa messze áll a per pixel árnyékolástól, de azért bemutatta a függő olvasást. Hasonló módszerrel létrehozható elmosás (blur), élkiemelés (gradient) és más teljes képernyős képfeldolgozási hatások.

eredeti - nagyon finoman elmosott 5pixelből - erős élkiemelés 8as szorzóval

Spekulatív ON
Azért hoztam fel éppen ezt a két hatást, mert éppen ezek kombinálásával lehet élsimítást csinálni (megkeressük ez éleket gradienssel, ezzel lemaszkoljuk a frame bufferben lévő kimeneti képet texture kill utasítással, és a maszkban megmaradt pixeleken blur). Könnyen lehet hogy az S3 DeltaChrome éppen így fog élsimítani mert nem jelentették be hogy rágyúrtak volna az FSAAra de jelezték hogy nagyon gyors gradienst tudnak (ATi-nál 3 textúra mintavétel + 3 összeadás + 3 kisebb-egyenlő összehasonlítás).
Spekulatív OFF

Lássuk az árnyékolást

Per-pixel Diffúz fény forrástól távolodó csökkenéssel

képlet: Irgb = Cbase*(Ia + falloff(D.D).(N.L)) ahol:
Cbase - alapszín a textúra térképből
Ia - fényforrás erőssége - skalár
N - felület normálisa (éppen renderelt háromszögre merőleges egyenes) - vektor
L - fény terjedési irányvektora - vektor
D - fényforrás és a megvilágított felület távolsága amit a Vertex egység számol ki.
falloff() - fénysorrás gyengülési függvénye; hasonló az előző sepia 1D vektorához de itt nem
tényleges textúra van hanem, ahogy említettem, függvény

A felírt képlet és

Érdeklődőknek itt van hozzá a kód

 
instruction
d
s1
s2
 
1. ps. 1.4
 
 
 
- pixel hader verzió
2. texld
r1,
t0,
 
- N - amit a VS számolt ki előzőleg és t0ban adott át
3. texld
r2,
t1,
 
- L - fény iránya tangens térben
4. texcrd
r3.rgb,
t2,
 
- D - fényforrás távolsága VSből
5. dp3_sat
r1.rgb,
r1_bx2,
r2_bx2
- N.L levágva 0-1re (_sat), rgb-re számolva (.rgb), beméretezve 2*(komponens - 0,5) (_bx2)
6. dp3
r3.rgb,
r3,
r3 
- D.D - aritmetikai művelet amiből később még textúrát számol
7. phase
 
 
 
- fázisváltás
8. texld
r0,
t0,
 
- Cbase mintavételezése
9. texld
r3,
r3,
 
- falloff(D.D) függvény által generált "txtúrából" mintavételezés D.D szerint Dependent
10. mul_x2
r4.rgb,
r1,
r3
- falloff (N.N)* (N.L)
11. add
r4.rgb,
r4,
c7
- előző + Ia
12. mul
r0.rgb,
r0,
r4
- Cbase*(Ia+ (falloff*N.L))
a t0, t1, t2 textúraregiszterek amik a Vertex Shader kimenetei

Ebben a példában már látható a programozhatóság két nagy előnye, a módosíthatóság és az újrahasznosíthatóság. Egy erősebb fényű vagy más gyengülési karakterisztikájú fényforrást lehet ugyanezzel a kóddal csinálni az Ia vagy a falloff() módosításával; továbbá minden diffúz, csökkenő fényforráshoz használható a kód.

Az előző példában a felhasználó tudja módosítani a fényhatást, most lássunk egy olyat, ahol a program csinál hasonlót:

Specular világítás per-pixel exponenssel

konstans exponens, DX7el megvalósítható - változó exponenes, DX8as

Képlet: Irgb = Cbase(Ia + (N.L)) + g(N.H)^k
jelölések azonosak az előző példával és H - halfway vektor, g - fényességi faktor, k - spekuláris komponens.
Ebben az esetben már kicsit trükközni kell, mert az egyenlet utolsó tagja eléggé összetett. A vertex shaderből érkező normális (N) alfa koordinátájába kerül a spekuláris exponens. Egy előre kiszámolt 2D táblából jön az alap (N.H) és a kitevő (k). Néhány "áltextúrán" (valójában nem létezik hanem pixelenként számolja ki a Vertex Shader) szemléltethető hogyan működik az egész:

Nromals a bumpmapból, k a csomópontokból számolva érkezik egy 4D vektorban pixelenként.
Gloss mapot és Albedot szintén a Vertex Shader számolja közös 4D vektorba,
N.H × k a Pixel Shader első fázisában van és a második fázisban függő olvasnak belőle
.
Normals és k mapon is látható, hogy nem mindegy milyen finomsággal számol a Vretex Shader, milyen részletesség érhető el!

utolsó előtti sorban látható egy +os co-issue, amikor más művelet van az első 3 és más a 4. komponensen

Per-pixel Aniso Light

Finom mikrostruktúrával (hosszú vékony vonalak nagyjából egy irányba, közel egymáshoz) rendelkező felületek megvilágításához használják (kupa a képen). Aniso fényhez a következő képletek alapján számol a Vertex Shader:
Diffúz: , spekuláris: ,
ezeket egy-egy 2D textúrában tárolja (V.T, L.T) formában, ezt használja a pixel shader:

Irgb = Cbase . Ia + Cbase(N.L) + gAs
jelölések azonosak az előző példával és As - aniso spekuláris faktor, amit az (L.T,V.T) 2D mátrixba számolt ki a Vertex Shader. A pixel shader csak a spekuláris részt használja (a diffúzt nem), mert annak nagyobb a hatása.

 

Hogyan lehet, kiindulási textúra nélkül, "áltextúrákból" építkezni?

Faelefántok - pixel shader 2.0

Gondolom az előzőek alapján már nagyjából világos hogyan dolgoznak a Shaderek. Most bemutatok egy példát, amiben fa textúrát hoznak létre mindenféle nyalánksággal. Ez a fa textúra, sok más előre rögzített textúrával együtt, része az alap shader librarynak (shader könyvtár alaptextúrákkal, alapeljárásokkal).

A nem valós idejű elefántos példa Advanced RenderMan-al készült, felhasználtak 1D textúrát a világos/sötét váltáshoz, 2D textúrát spekuláris funkciókhoz, volume textúrát a zajhoz. Durván 2MB memóriaigény.
Jobb oldali valós idejű változathoz szükség volt: egy világos és egy sötét színre, gyűrűfrekvencia függvényre, zaj amplitúdóra, wobble (zizegő) frekvenciára és amplitúdóra.

Először vegyük a fatörzs keresztmetszetét. Látható hogy világos és sötét koncentrikus körök (évgyűrűk) váltják egymást. Ez lekezelhető két színnel és egy átváltási függvénnyel. Az így kapott gyűrűk nagyon egyenletesek, ezért kicsit zajosítani kell őket.

A metszettel készen vagyunk, de a túlzott egyenletességet a törzs hosszirányában is el kell tüntetni egy wobble (zizegés) hatással, amit megint egy zaj hozzáadásával valósítható meg. Az egész kap a végén egy igényes per pixel pong árnyékolást és készen is vagyunk (agyarakról ugyan megfeledkeztünk, de azok nem tartoznak szorosan a példához).

az egyenletes és a "bezizisített" fatörzs


a kész szajos, zizis faelefánt árnyékolatlanul és pong árnyékolva


a kód nem annyira veszélyes, igaz már Pixel Shader 2.0-s (dcl definiálásra és dp2add aritmetika és jó hosszú kód)

Ez a példa jól illusztrálja a shaderprogramok finomhangolhatóságát. Elég változtatni a két alapszínen vagy a színváltás frekvenciáján (évgyűrűk), vagy a zajosságon, máris változik a textúra. Ezek a változások lazán kezelhetőek kódból, vagy kimondottan textúrakészítésre használt programokról mint a Rnder Monkey vagy RenderMan, de akár Maya vagy 3D Studio plugineken keresztül is. A hagyományos textúrákkal ellentétben itt nem egy bitmap, hanem egy sokkal rugalmasabb shaderkód lesz a kimenet.

HLSL és társai

Az eddig látott Pixel Shader kódok nem túlzottan felhasználóbarátok, pedig még aránylag egyszerűek és a Vertex Shader kódok sokkal hosszabbak és hasonlóan átláthatatlanok. Szerencsére a chipgyártók gondoltak a 3D fejlesztőkre, méghozzá két lépésben.

Az első lépés magas szintű programozási nyelvek (eljáráshalmazok) létrehozása, melyek fordító (compiler) segítségével csinálnak Shader mikrokódot. Ennek nagy előnye, hogy az alap effektusok (pl. az előbb tárgyalt fa, de ugyanígy van márvány és sok más) előre meg vannak írva és paraméterezve, van debugger és kényelmes kezelőfelület.

Második lépésben jöttek az effektusgyártó célprogramok mint a Render Monkey, ShaderMan vagy a nagy és ismert 3D tervezőprogramok (Maya, 3D Studio stb.) pluginjei azoknak, akik nem értik vagy nem szeretik a magas szintű nyelveket. Ezekben általában háttérbe helyezték a kódot és mindenféle csúszkákon és egyéb beviteli mezőkön keresztül váltóztathatóak az előre beépített hatások. Persze ebben az esetben le kell mondani a teljesen egyedi saját tervezésű hatásokról.

Szerencsére jópár ilyen célprogramban megmaradt a HLSL vagy akár a shader kódos programozás lehetősége is. Ezek a programok leginkább a mai modern magas szintű programcsomagokhoz vagy a komolyabb webszerkesztőkhöz lehetne hasonlítani. Az alap hatások pár egérkattintással létrehozhatóak, paraméterezéssel finom-hangolhatóak, HLSL szinten új hatásokat lehet csinálni, és a kész eredményt optimalizálni lehet alacsony szintű shader kódban.

korábban tárgyal valós idejű fa textúra HLSL kódja


balra a HLSL által generált shader kód (37 ALU utasítás), jobbra a kézzel készített kód (35 ALU utasítás)


Render Monkey kezelőfelülete: balra effect lybrary, középen kimeneti kép és shader kód regiszterlistával,
jobbra hangolható paraméterek, alul fordító folyamatok; kimenete XML formátumú
És akkor most vegyük a DirectX9-et, vagyis a vertex és pixel shader 2.0-t.

Vertex Shader 2.0
Egyre komplexebb vertex shader programok lassan de biztosan kinőtték a 128 utasítást, ami egyre jobban szorított a programozókat. További gondok merülte fel az iterációk (ugyanaz a kódrész egymás után több adattal) és permutációk (majdnem ugyanaz a kód egy feltételhez kötve) kapcsán. Ezeket a problémák ciklusokkal (loop), szubrutinokkal és elágazásokkal oldották meg, akárcsak a magas szintű nyelvekben.

A módosításokkal szükség volt a regiszterek átszabására is:
256 darab csak olvasható float konstans (s[n])
12 darab írharó/olvasható ideiglenes regiszter (rn)
16 darab csak olvasható input regiszter (Vn)
és jönnek a forgalomirányítók:
16 darab csak olvasható integer konstans (In)
1 írharó/olvasható darab Address (A)
1 skalár Loop Counter (aL) a ciklusokhoz
16 darab egy bites Boolean (igaz/hamis) csak olvasható (B) az elágazások feltételeinek kiértékeléséhez

Ezek a konstansok az API által módosíthatóak, így lehet a shader programokat kívülről irányítani.

Az új vertex shader program már 256 utasítás hosszú lehet, amihez még nincs hozzászámolva a ciklusok ismétlődése (tehát jóval hosszabb - akár végtelen - is lehet). Szükség lett forgalomirányító utasításokra is: CALL, LOOP, ENDLOOP, JUMP, JNZ, LABEL, REPEAT, ENDREPEAT, RETURN.

Pixel Shader 2.0
A legjelentősebb váltózás az integer számokról floatba váltás, amivel jelentősen megnőtt az egyes hatások precizitása és természetesen a számolásigény is. Másik fejlesztés itt is a program hosszát érintette; most már 64 aritmetikai és 32 textúra utasítás lehetséges. Kibővítették az ALU utasításokat amiből a legérdekesebb a LOG és EXP, viszont fura hogy nincs egyből SIN és COS mint az nVidia-nál (van helyette sin/cos eljárás EXP alapján) . Jött egy új textúra utasítás is: TEXLDBIAS, ezzel per-pixel LoD (level of depth) határozható meg, ami hasznos durva spekuláris felületeknél vagy távolságfüggő elmosásnál.

sajnos csak openGL feltételhez kötött kódrészt találtam, de ez legalább mutatja,
miért szerette a HLSL-előtt sok programozó (John Carmack: "...more mature...") jobban az openGL magas szintű megközelítését

High Dynamic Range Rendering jön még, ott domborít veszettül a ps2.0
báj rudi, ha valakinek baja van írjon mail-t