2013. április 8., hétfő

HLSL #1 Alapok

Egy tetszőleges grafika megjelenítése a képernyőn a mai programokkal szinte gyerekjáték. Legyen szó egy képről, vektorgrafikáról vagy 3D modellről, keretrendszerek tucatjai állnak rendelkezésre, amelyek a probléma nagy részének a megoldását a háttérben elvégzik.

Mindazonáltal, ha valami speciálisat - és főleg egyedit - kell a képernyőre kivarázsolni, akkor a legtöbb esetben még mindig a hardver alapszintjére kell visszanyúlni. A shader programok segítségével, és némi fantáziával egyszerű elemekből felépített grafikát is fantasztikusan fel lehet turbózni. Ehhez persze türelem, és rengeteg tapasztalat kell, mint minden máshoz.

A blogban található shader-ek elkészítéséhez az nvdia ingyenesen letölthető fxcomposer programjának 2.5-ös verziója ajánlott, de bármely más shader editor is tökéletesen megfelel. A Composer használatához érdemes rögtön a manual-t is letölteni.

Installálás után első lépésben hozz létre egy új projektet (File/New).
A jelenleg teljesen üres projekthez minimum kell egy shader és egy modell.
Az első shader létrehozása: Create/Effect
A megjelenő menüben válaszd a HLSL FX-et, majd a Next gomb megnyomásával már előre beállított effektek közül lehet válogatni.
Egyenlőre válaszd a "empty" lehetőséget. File névnek tetszőlegesen találj ki valamit, pl: "01_basic.fx", majd kattints a next gombra.
A megjelenő panelen láthatod a létrehozásra kerülő effect nevét. Ezzel el is készült a shader alapja.

Alapbeállítás esetén a bal felső "Materials" ablakban egy fehér kör látható. Ha mégsem, akkor a View/Materials ki/be kapcsolásával ez tetszőlegesen állíthatod.

A modell hozzáadás lényegesen egyszerűbb. Create/Plane
A jobb alsó sarokban, a "Render" ablakban láthatónak kell lennie az imént hozzáadott plane.

A fehér kört drag-drop használatával húzd rá a plane-re, aminek hatására az fehérre változik. Ezzel a plane mostantól ezt a shadert használja, bármi változtatás esetén a plane követni fogja azt.

A shader kód középen látható, a kommentek kiszedése után így kell kinéznie:
float4x4 WorldViewProj : WorldViewProjection;

float4 mainVS(float3 pos : POSITION) : POSITION{
 return mul(float4(pos.xyz, 1.0), WorldViewProj);
}

float4 mainPS() : COLOR {
 return float4(1.0, 1.0, 1.0, 1.0);
}

technique technique0 {
 pass p0 {
  CullMode = None;
  VertexShader = compile vs_3_0 mainVS();
  PixelShader = compile ps_3_0 mainPS();
 }
}

A fenti kód tartalmaz mindent, ami egy shader minimális működéséhez kell.

A modell térbeli elhelyezését, transzformációit pontosan tudnia kell a programnak, erre szolgál a
WorldViewProjection. Ez tulajdonképp 3 mátrix szorzata.
WorldViewProjection = World * View * Projection
A World határozza meg a modell eltolását, elforgatását, nagyítását.
A View mondja meg, hogy kamera hol található a térben és épp merre néz
A Projection határozza meg a látószöget, a torzítás valamint a legközelebbi és a legtávolabbi látható távolságot.

Ezt a paramétert az FXComposer automatikusan adja a shader részére. A Saját programodban ezt neked kell kiszámolnod. Ahhoz, hogy az FXComposer tudja, hogy neki ezt a paramétert adni kell, a programban ez muszáj jelezni.
float4x4 WorldViewProj : WorldViewProjection;
A float 4x4 egy mátrix-ot definiál, a WoldViewProj pedig tetszőleges változó elnevezés
Az ezt követő kettőspont és a WolrdViewProjection úgynevezett SEMANTICS.
Ez határozza meg, hogy egy adott változó/paraméter milyen koncepció alapján kerül felhasználásra.
Jelen esetben a composer ebből tudja, hogy ennek a változónak kell átadni a már előre kiszámított mátrixot.
Fontos megjegyezni, hogy ez a SEMANTICS csak az fxcomposer számára érthető.
Ha ez most elsőre nem világos, ne ragadj le itt, a legtöbb példában  elegendő lesz, hogy ott van. Ettől függetlenül érdemes a neten pontosan utánaolvasni.

A shader két függvényből áll.
VertexShader: amely minden számítást egyszer végez el a modell összes vertexén.
PixelShader: amely minden pixelen, ahol  a model látható lefut.
Ebből következik, hogy mindaz, amit ki lehet számolni a VertexShader-ben, célszerű ott megtenni, hiszen az jóval kevesebbszer fog valószínűleg lefutni, mint a pixelshader.

A VertexShader a modell pozícióját várja input paraméterként, amit a POSTION SEMATICS jelöl.
float3 pos : POSITION
És a shader kimenete a model egy vertexének a pozíciója. Ezt szintén a POSTION SEMATICS jelöli.
float4 mainVS(float3 pos : POSITION) : POSITION
A függvény kiszámítja a vertex pontos helyzetét, majd átadja PixelShader-nek.

A PixelShader visszatérési értéke az a szín, amit a képernyőn meg kell jeleníteni. Ez a COLOR SEMATICS jelöli
float4 mainPS() : COLOR  
A függvény pedig jelen esetben egy teljesen fehér értékkel tér vissza.
return float4(1.0, 1.0, 1.0, 1.0);
Az RGB értékek 0-1 között tetszőlegesek lehetnek. Az utolsó paraméter az Alpha áttetszőség értéke.
Ezek a paraméter értékek tetszőlegesen változtathatóak.
pl: return float4(1.0, 0.0, 0.0, 1.0);
Ebben az esetben a plane piros színű lesz.
Minden változtatás után a programot le kell fordítani. Addig nem látható a hatása. Erre szolgál a Compille button vagy a Build/Compille utasítás.

Megjegyzés: Az utolsó paraméter (Alpha) értéke szintén tetszőleges lehet, de ez jelen esetben nem befolyásolja a plane áttetszőségét. Ahhoz, hogy ez megtörténjen további beállításokra van szükség.

A shader következő eleme a technique.
Egy shader tetszőleges számú technique-t tartalmazhat. Ez határozza meg a shader lefutáshoz szükséges opciókat. Pl. az áttetszőséget is itt lehet beállítani.

Minden techniqe tetszőleges számú pass elemet tartalmaz.
A pass határozza meg, hogy melyik VertexShader és melyik PixelShader függvényeket kell lefuttatni.

A CullMode = None; hatására a kirajzolandó plane minden irányból látható.
A plane kirajzolása vagy óramutató járásával megegyezik vagy ellentétes.
A render ablakban az ALT+bal egérgomb használatával lehet az aktuális nézetet változtatni.
Jelen esetben a plane mindkét oldala látható (no culling)
A CullMode felveheti a "CW" és a "CCW" értékeket.
CullMode = CW; (ne felejtsd el lefordítani a shadert)
Ebben az esetben a plane egyik irányból nem látható.
Valós helyzetben célszerű CW vagy CCW értékre állítani, mert egy adott polygon nagy valószínűséggel úgyis csak az egyik irányból lesz látható.

A megjelenítendő szín kódból történő változtatása elég lassú folyamat. Amennyiben a plane-t zöld színnel szeretnénk kirajzolni, a megfelelő sort át kéne írni, majd újrafordítani az egész kódot.
Ennek egyszerűsítésére az alábbi változtatások szükségesek.
Add hozzá a következő sorokat a program elejéhez.
float4 customColor :Color
<
 string UIName = "Diffuse Color";
> = {1.0, 1.0, 0.0, 1.0};

Illetve a PixelShader visszatérési értékét írd át az alábbira, majd fordítsd le:
return customColor;

A Jobb felső sarokban (default nézet) látható a properties ablak.
Az ablakban immáron látható egy Diffuse Color elem, ami egy színkiválasztót is tartalmaz. Válassz ki egy tetszőleges színt. Látható, hogy a render ablakban a plane realtime követi.

A Color SEMATICS jelzi az fxcomposer számára, hogy ez a változó egy színt tartalmaz.
A kacsacsőrökben elhelyezett string UIName = "Diffuse Color";  úgynevezett Annotation.
Ez szintén csak az fxComposer számára fontos. Jelen esetben megmondja, hogy a User Interface Panelen milyen névvel szerepeljen ez a változó.
A későbbiekben még számos Annotation használatára lesz példa, egyenlőre bőven elég, hogy a változót értelmesen olvasható névvel el lehet látni.

A leírásban található idegen szavak - főleg a sematics és az annotation - bővebb leírására érdemes a neten rákeresni. Gyakorlásképpen hozz létre még egy Color változót és a PixelShader visszatérési értéke legyen a kettőnek az összege.

Az alábbiakban látható a megoldás:
float4x4 WorldViewProj : WorldViewProjection;

float4 customColor :Color
<
 string UIName = "Diffuse Color";
> = {1.0, 1.0, 0.0, 1.0};

float4 newColor :Color
<
 string UIName = "Another Color";
> = {1.0, 1.0, 0.0, 1.0};

float4 mainVS(float3 pos : POSITION) : POSITION{
 return mul(float4(pos.xyz, 1.0), WorldViewProj);
}

float4 mainPS() : COLOR {
 return customColor + newColor;
}

technique technique0 {
 pass p0 {
  CullMode = none;
  VertexShader = compile vs_3_0 mainVS();
  PixelShader = compile ps_3_0 mainPS();
 }
}

Nincsenek megjegyzések:

Megjegyzés küldése