Az elmúlt hetek (hónapok?) témája a shader fordító refaktorálása volt. Az eredeti tervvel ellentétben sokkal nagyobb munka lett, de azt hiszem megérte. Javaslom a biztonsági övek bekapcsolását.
Cikk itt.
Az elmúlt hetek (hónapok?) témája a shader fordító refaktorálása volt. Az eredeti tervvel ellentétben sokkal nagyobb munka lett, de azt hiszem megérte. Javaslom a biztonsági övek bekapcsolását.
Cikk itt.
Előfeltétel, hogy legalább két háttérszínnel ki tudjad menteni a diagramot (fehér és fekete).
Meghökkentő az a tény, hogy 2013-ban nincs egy olyan normális UML szerkesztő, ami átlátszó háttérrel ki tudná menteni a diagramot. Több hétig leveleztem egy Microsoft-os emberrel, hogy hogyan lehet olyan Visual Studio plugint írni, amivel lecserélem azt a k*baszott háttérszínt. Nem jutottunk semmire. Ugyanez az Enterprise Architect-el is, csak az még watermarkot is berak (éljen).
Úgy döntöttem, hogy elkerülendő a további szopást kidolgozok egy módszert, amivel legalább az említett feltétel mellett kihozható az eredeti szín és alfa. Nem újkeletű egyébként a dolog, a GIMP pl. tud ilyen color to alpha cuccost, de nem igazán felel meg a célnak.
Na de mi a probléma, és miért pont fekete és fehér kell legyen a háttér? Nézzük meg először is hogyan működik az alpha blending:
(A, B, C) * (1 - a) + (x, y, z) * a = (r1, g1, b1)
Ahol (r1, g,1, b1) az ismert kép, (A, B, C) pedig a szintén ismert háttér. Meghatározandó (x, y, z, a). Világos, hogy az egyenletrendszer alulhatározott, kellene még legalább egy egyenlet. Az egyszerűség kedvéért legyen ugyanez csak más háttérrel:
(D, E, F) * (1 - a) + (x, y, z) * a = (r2, g2, b2)
Vadul kivonva a felsőből az alsót:
(A-D, B-E, C-F) * (1 - a) = (r1-r2, g1-g2, b1-b2)
És itt kezdődnek a problémák...ugyanis az alfára most kaptunk egy túlhatározott rendszert, ráadásul vannak olyan esetek, amikor mindhárom alfa különböző. Azonban néhány észrevételt lehet tenni:
A második észrevétel megindokolja, hogy miért célszerű fehéret és feketét választani háttérnek. Ugyanis ha más választanánk, akkor egyéb színekkel is előállhat az egyenlőség, és akkor nem mondható meg 100%-al, hogy az mindkettőben a háttér. Sőt, az nem elég, ha ellenőrzöl a háttérszínre, ugyanis:
a = ((A - D) - (r1 - r2)) / (A - D)
illetve
x = (r1 - A * (1 - a)) / a
tehát x olyan színekre is szingularitásba kerülhetne, amik nem a háttérszínek. Feltehető tehát, hogy (A, B, C) = (255, 255, 255) és (D, E, F) = (0, 0, 0). Ekkor egy picit egyszerűsödik a dolog:
(x, y, z) * a = (r1, g1, b1)
(255, 255, 255) * (1 - a) + (x, y, z) * a = (r2, g2, b2)
Most egyéb problémák jönnek. Ha alfa már ismert, akkor az első egyenletből kívánkozik kiszámolni a keresendő színt, viszont ha az alfa elég kicsi, akkor egy nem fehér színből fehéret fog csinálni (ami világos, hogy rossz). A második egyenlettel szintén hasonlóak a problémák. Továbbá az alfa még mindig nem egyértelmű. A következőt mondom: a valid alfák közül válasszuk ki a legnagyobbat. Ezzel ugyanis minimalizálni lehet a hibalehetőséget.
Az eredmény egyelőre elég jó. Nem vezettem le az egész elméletet, lehetnek benne hibák. Kód itt.
Az elmúlt néhány hónapban a forward és deferred rendering integrálásával foglalkoztam, az engine így most három különböző pipeline-t tud:
Ez egy elég hosszadalmas iteratív folyamat volt, mivel a lehető legkisebb memóriafoglalással kellett megoldani, hogy a fények tudják a közelükben levő objektumokat (árnyék miatt), illetve az objektumok is tudják a rájuk ható fényeket (átlátszóak miatt). Ezeket a bejegyzéseket ráadásul több szempont szerint is rendezni kell:
A megjelenítő shaderek több shader kombinációjából állhatnak össze a fény típusa szerint (directional, point, spot), a normálok kiszámítása szerint (normal mapping), illetve az árnyék típusa szerint (pcf, variance). Ez így igen gáz, a következő feladatok közé tartozik a shader nyelvet úgy bővíteni, hogy könnyen lehessen kvázi-übershadert írni.
Megjegyezném, hogy a shadereknek óriási overheadje van iPad-en (eleve árnyék nélkül és csak a forward rendererrel), és az optimalizálás sem nagyon segít.
Természetesen számítani lehet majd valamikor demóra, de ez a shader nyelv bővítés valószínűleg soká tart majd. Amint a fenti képen is látható, a Doom 3 modelleket most már majdhogynem tökéletesen jeleníti meg az engine, a kontrasztvesztés a gamma korrekció miatt van (ugyanis a Doom 3 egyáltalán nem használja).
A demó desktopon ~400 fps, iPad 2-n viszont csak 15... Amíg nincs kész a teljes implementáció, addig a mobil optimalizációt félrerakom.
A Doom 3 modellek megjelenítéséről, illetve a lighting konkrét megvalósításáról fogok írni cikket.
Én is hajlamos vagyok elfelejteni ezt, pedig egyszer már rájöttem, sőt meg is írtam az engineben a helyes módszert. A probléma az, hogy Windows-on más a sorvégejel (CR/LF) mint Unix alapú rendszereken (LF). Azaz ha az egyik platformon csinálsz egy fájlt, az a másik platformon tuti rosszul fog működni, az emlegetett függvény ugyanis mindig az adott platformnak megfelelően értelmezi (vagy nem...).
Nekem a hiba úgy jött elő, hogy zlib-el betömörítettem egy txt fájlt, majd kitömörítés után std::stringstream-ből használva az std::getline marhaságokat adott vissza (bennehagyta a CR-t). Átírva az engines getline-ra, a probléma megszűnt.
std::istream& qEngineHelper::GetLine(
std::istream& in, qstring& out)
{
char c = 0;
out = "";
while( in.get(c).good() )
{
// win
if( c == '\r' )
{
c = in.peek();
if( in.good() )
{
if( c == '\n' )
in.ignore();
}
break;
}
// unix
else if( c == '\n' )
break;
out.append(1, c);
}
return in;
}
Szóval erre érdemes odafigyelni...
Kicsit gondban voltam a forgatások animációjával, ugyanis mindenki azt írja, hogy hát azt bizony csakis kvaternióval szabad blablabla. Mivel a dolog k*rvára nem működik a gyakorlatban, áttekintettem, hogy miért nincs szükség kvaterniókra.
Gimbal lock: mindenki tudja mi az a gimbal, ha nem akkor megnézed wikipédián. A lényeg, hogy az FPS, TPS vagy csak egy sima modelviewer kamera is gimbal lockos szándékosan. A hatása az, hogy ha pont fel vagy le nézel, akkor a kamera a view space beli z tengely körül forog, azaz valóban elvesztettél egy szabadságfokot (yaw helyett is rollol).
Kvaternió: egy ilyen állat csak olyan elforgatást tud reprezentálni, ami nem gimbal lock-os. Azaz ha kvaternióval próbálod interpolálni az amúgy gimbal lock-os kamerád szögeit, akkor eszméletlenül idióta eredményeket fogsz kapni (pl. elkezd sinus görbéket bejárni). Egy kvaternióval implementált kamera mindig úgy fordul, ahogy alapesetben: ha felfele nézel, akkor is a yaw jobbra-balra fog fordulni. Ami egy FPS-ben nagyon hülyén néz ki, azt hinnéd hogy elrontottál valamit, pedig nem.
Mátrix: forgatási mátrixot nem lehet, illetve nem úgy kell interpolálni, hogy komponensenként interpolálsz (más mátrixokkal működhet a dolog). De ez az ami abszolút nem éri meg, még ha jól is csinálod.
Euler szögek: bármily meglepő, ez a legjobb megoldás, némi meggondolással. Általában a legrövidebb úton akarsz eljutni a másik pontba. Kis számolás után erre az alábbi függvényt firkáltam:
float CurveAngle(float current, float dest, float step)
{
if (dest - current > _Q_PI)
dest = dest - _Q_2PI;
else if (current - dest > _Q_PI)
current = current - _Q_2PI;
current += (dest - current) * step;
return current;
}
Elég ezt meghívni mindhárom szögre és vidáman interpolál, úgy ahogy szeretnéd. Egy kisebb meggondolás az, hogy ha már összegányoltad kvaternióval a dolgot, mint én (amit most egyébként ki is gyomláltam), akkor sürgősen írd át, mert kvaternióból visszaszámolva pontatlan lehet (nekem speciel az invSqrt() miatt volt az).