Leírás

3D grafikával kapcsolatos bejegyzések és cikkek. A Quadron Virtual Particle nevű game engine fejlesztése.

Stuff

Anything related to game development.

Contact: darthasylum at gmail dot com

Nemlineáris cikkek

Mi ezeknek a célja?

Leginkább az, hogy magyarul is elérhető legyen programozási anyag, olvasmányos formában.

A cikkek kódja GitHub-ról is elérhető. (*)-al jelölöm azt a cikket ami nemrégiben frissült.

A színek a nehézséget próbálják jelezni, de az, hogy egy cikk piros nem azt jelenti, hogy csak a profiknak szól!

 

 

 

 

 

 

 

 

Hörcsög

The Asylum

3D grafikával kapcsolatos bejegyzések és cikkek. A Quadron Virtual Particle nevű game engine fejlesztése.

Friss topikok

Címkék

HTML

QuadronFX implementációs részletek

2013.11.03. 23:02 | darthasylum | Szólj hozzá!

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.

ch80_pic4.png

Egyszínű háttér kiszedése diagramokból

2013.10.26. 18:33 | darthasylum | Szólj hozzá!

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:

  • ha (r1, g1, b1) == (r2, g2, b2), akkor a = 1 és készen vagyunk
  • ha (r1, g1, b1) - (r2, g2, b2) == (A - D, B - E, C - F), akkor a = 0 és mindegy mi a szín

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.

 

Deferred rendering az engine-ben

2013.09.14. 19:54 | darthasylum | Szólj hozzá!

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:

  • custom: ami eddig volt, minden objektum a saját materialjával rajzolódik
  • forward: multipass lighting, minden aminek nincs saját effektje az engine-el rajzolódik és minden fény hat rá ami a közelében van
  • deferred: ugyanez, deferred lighting-al

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:

  • átlátszó objektumokat z szerint
  • minden mást shader szerint

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.

quadron8.jpg

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.

Miért nem szabad std::getline-t használni?

2013.07.22. 21:03 | darthasylum | Szólj hozzá!

É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...

Meggondolások kamerához

2013.06.06. 22:19 | darthasylum | Szólj hozzá!

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).

Címkék: kamera lock kvaternió gimbal

süti beállítások módosítása