English
Welcome
Kecy
Programování
3D - Engine
Guestbook
Odkazy
Downloady
O autorovi
Napiš mi
Mailform
box_cz 3D - Engine - OpenGL 01 - začínáme s OpenGL box_cz

Tak a jsem tu znova. Omlouvám se za zpoždění, ale vlezla mi do toho jedna dokonalá slečna ... Teď už spolu (bohužel pro mně a naštěstí pro vás) nejsme, takže je tu další (první) díl o OpenGL! Dnes si vysvětlíme nějaké základní věci o OpenGL. Předně - OpenGL znamená open graphics library - otevřená grafická knihovna. Slouží jako rozhraní mezi programem a 3D-kartou (nebo referenčním (software) rasterizérem, pokud počítač nemá kompatibilní 3d-kartu) Hodně lidí se ptá, jestli je lepší OpenGL, nebo Direct-X. Já preferuji OpenGL kvůli jeho rozhraní. OGL má client-server rozhraní, kde nejsložitější typ je float. Direct-X mají hromadu struktur, jejichž jména se navíc s verzemi mění, což mi přijde dost špatné. No ale začneme, není na tom nic složitého. Budu počítat s tím, že jste si už přečetli předchozí seriál o software renderingu a 3d a vektorům dobře rozumíte ... Začneme s inicializací OpenGL:


static HDC _hDC; // grafický kontext okna
static HGLRC _hRC; // open-gl kontext
static HWND _hWnd; // windows handle okna
static int _b_Fullscreen; // jsme fullscreen ?

static int n_width;
static int n_height; // rozměry obrazovky

static int n_status; // jak jsme na tom

int Init_GL(HWND hWnd, int n_New_Width, int n_New_Height, int n_Bpp, int b_Fullscreen)
{
    PIXELFORMATDESCRIPTOR pixelformat;
    DEVMODE dmScreenSettings;
    GLuint PixelFormat;

    n_status = 0;

    if(b_Fullscreen) {
        memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
        dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
        dmScreenSettings.dmSize = sizeof(dmScreenSettings);
        dmScreenSettings.dmPelsWidth = n_New_Width;
        dmScreenSettings.dmPelsHeight = n_New_Height;
        dmScreenSettings.dmBitsPerPel = n_Bpp;
        // nastavi rozliseni (pokud fullscreen)

        if(ChangeDisplaySettings(&dmScreenSettings, 4) != DISP_CHANGE_SUCCESSFUL) {
            if(MessageBox(NULL, "Unable to set fullscreen mode.\n"
               "Use windowed mode instead?", "Fullscreen",
               MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
                _b_Fullscreen = 0;
            else
                return 0;
        }
        // zkusi prepnout, pokud to nejde, zepta
        // se a pak skonci nebo zustane v okne

        ShowCursor(FALSE);
    }
    // pokud má být fullscreen, přepne rozlišení

    memset(&pixelformat, 0, sizeof(PIXELFORMATDESCRIPTOR));
    pixelformat.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pixelformat.nVersion = 1;
    pixelformat.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pixelformat.dwLayerMask = PFD_MAIN_PLANE;
    pixelformat.iPixelType = PFD_TYPE_RGBA;
    pixelformat.cColorBits = n_Bpp;
    pixelformat.cDepthBits = n_Bpp;
    pixelformat.cAccumBits = 0;
    pixelformat.cStencilBits = 0;
    // nastavi pixel format descriptor, tzn. kolik chceme
    // bitů na pixel, které chceme buffery a tak porůznu

    if(!(_hDC = GetDC(hWnd))) {
        MessageBox(NULL, "Unable to create a GL device context.", "GL_Drv", MB_OK);
        return 0;
    }
    if(!(PixelFormat = ChoosePixelFormat(_hDC, &pixelformat))) {
        MessageBox(NULL, "Unable to find a suitable pixelformat.", "GL_Drv", MB_OK);
        return 0;
    }
    if(!SetPixelFormat(_hDC, PixelFormat, &pixelformat)) {
        MessageBox(NULL, "Unable to set the pixelformat.", "GL_Drv", MB_OK);
        return 0;
    }
    if(!(_hRC = wglCreateContext(_hDC))) {
        MessageBox(NULL, "Unable to create a GL rendering context.",
            "GL_Drv", MB_OK);
        return 0;
    }
    if(!wglMakeCurrent(_hDC, _hRC)) {
        MessageBox(NULL, "Unable to activate the GL rendering context.",
            "GL_Drv", MB_OK);
        return 0;
    }
    // vytvoří OpenGl kontext, nastaví pixel formát a nastaví
    // náš kontext jako běžící

    glViewport(0, 0, n_New_Width, n_New_Height);
    // nastavi naše okno, které má využitou celou plochu

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(90, (GLfloat)n_New_Width / (GLfloat)n_New_Height, 0.5, 900);
    // vynuluje matice a nastvi projekci ...

    n_width = n_New_Width;
    n_height = n_New_Height;
    n_status = 1;
    _b_Fullscreen = b_Fullscreen;
    // nastaví proměnné ...

    glDepthFunc(GL_LEQUAL);
    glCullFace(GL_FRONT);
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_FLAT);
    // nastaví nějaké věci

    return 1;
}

Asi to vypadá hrozivě, že? Ale nebojte se! Tohle budete kopírovat z projektu do projektu, stejně jako kód pro vytvoření okna - je to pokaždé stejné. Ale teď k tomu co se tam děje ...

Úplně první segment kódu volá API funkci ChangeDisplaySettings(), určenou pro přepínání módu našeho monitoru. Tím si změníme rozlišení, pokud chceme. Dál už přichází to zajímavé - nastavíme si pixelformat. Chceme po něm PFD_DRAW_TO_WINDOW (budeme kreslit do našeho okna), PFD_SUPPORT_OPENGL (chceme hardware podporu open-gl, pokud máme) a PFD_DOUBLEBUFFER (chceme double-buffer, tzn. obraz se kreslí do jednoho bufferu a druhý se zobrazuje. když je obraz hotový, buffery se přehodí (jenom pointery, nic se nekopíruje) - takže nevidíme jak se postupně kreslí věci, které po grafické kartě chceme). PFD_TYPE_RGBA znamená že budeme chtít pixely s RGB (red-green-blue barvou) a A (alfa-kanálem - o tom si povíme víc dál) Nastavíme si počet bitů na barvu, na depth buffer (z-buffer, "depth" = hloubka), akumulátor nechceme takže dáme nulu (akumulátor je speciální buffer, do kterého můžeme přičíst obsah bufferu, který kreslíme. je to dobré k tomu, že když máme scénu, osvětlenou více světly než zvládne karta (většinou 8) tak ji nakreslíme jen s několika světly a uložíme do akumulátoru. potom ji vykreslíme s jinými světly a znovu uložíme. takhle dosáhneme možnosti "nekonečného" počtu světel ve scéně) Stencil buffer taky nechceme. Stencil je buffer, do kterého se dají ukládat id polygonů a podobně. Většinou se používá na výpočet stínů (viz. seriál o software renderingu), ale dá se efektivně použít také na různé maskování, třeba při kreslení zrcadel, kdy chcete aby obraz v zrcadle byl jen po polygonech zrcadla. To jednoduše nakreslíme polygony zrcadla do stencil bufferu, nastavíme příslušnou kontrolu a můžeme kreslit obsah zrcadla. Ale o tom někdy jindy, zkusíme si se stencilem ještě pěkných pár triků ...

Dál zavoláme několik API funkcí, obdržíme svoje kontexty, nastavíme pixel-formát a aktivujeme náš kontext. (pokud si uděláte víc kontextů pro víc oken, budete mezi nimi pořád přepínat podle toho, do kterého okna budete chtít kreslit)

Zavoláme si glViewport(), kterému řekneme že chceme kreslit do celé plochy okna. Potom vynulujeme všechny matice, nastavíme perspektivní korekci a uložíme stavové proměnné.

Potom se zavolá pár příkazů, kterým teď nerozumíme. Ale to přijde ... zatím pro kompletnost ještě kód jak to udělat, abysme OpenGL ukončili a kód jak překlopit buffer (technický slang, budeme to volat když budeme myslet že máme nakresleno a budeme chtít náš výtvor ukázat)


int Terminate_GL()
{
    int i = 1;

    n_status = 0;

    if(_b_Fullscreen) {
        ChangeDisplaySettings(NULL, 0);
        ShowCursor(TRUE);
    }
    // pokud bylo fullscreen, vrati zpatky

    if(_hRC) {
        if(!wglMakeCurrent(NULL, NULL)) {
            MessageBox(NULL, "Release of DC and RC failed.", "GL_Drv", MB_OK);
            i = 0;
        }
        if(!wglDeleteContext(_hRC)) {
            MessageBox(NULL, "Release rendering context failed.", "GL_Drv", MB_OK);
            _hRC = NULL;
            i = 0;
        }
    }

    if(_hDC && !ReleaseDC(_hWnd, _hDC)) {
        _hDC = NULL;
        i = 0;
    }

    return i;
}

void Blit()
{
    SwapBuffers(_hDC);
}

To by bylo. Nebudu to ani vysvětlovat, stejně to vždycky jen zkopírujete ...

Jak už jsem říkal, OpenGL je client-server rozhraní. My jsme klient a grafická karta (její driver) je server. My máme skupinu funkcí, kterou server řídíme. Mezi ty základní patří:

  • void glEnable(enum x) - povol vlastnost x
  • bool glIsEnabled(enum x) - zjistí jestli je vlastnost x povolená
  • void glDisable(enum x) - zakaž vlastnost x
  • void glGetIntegerv(enum name, int *p_val) - zjisti hodnotu proměnné se jménem name. int hodnota se uloží do p_val
  • void glGetFloatv(enum name, float *p_val) - zjisti hodnotu proměnné se jménem name. float hodnota se uloží do p_val
  • unsigned char *glGetString(enum name) - zjisti hodnotu textové proměnné se jménem namet.
  • enum glGetError() - pokud jsme způsobili chybu(y), postupně nám vrátí všechny jejic kódy. Pokud vrátí GL_NO_ERROR, jsme v suchu.

To bylo pár funkcí. Můžeme třeba volat glGetString(GL_RENDERER), kdy vrácená hodnota bude odpovídat jménu grafické karty, která se právě používá. Dobré, že? Takhle můžeme zjistit verzi OpenGL:


void ZjistiVerzi_OpenGL()
{
    const char *p_s_version;
    int n_major, n_minor;

    p_s_version = (const char*)glGetString(GL_VERSION);

    if(sscanf(p_s_version, "%d.%d", &n_major, &n_minor) == 2)
        // verze je n_major.n_minor, třeba 2.0
    else
        // někde nastala chyba, nevíme jakou máme verzi
}

Asi by bylo dobré říct, co OpenGL umí. Máme tu možnost kreslení různých geometrických tvarů, můžeme je různě barvit, texturovat a stínovat. Máme tu matice, kterými můžeme transformovat geometrii (GL_MODELVIEW matice) nebo souřadnice textur (GL_TEXTURE). Máme vestavěný Z-Buffer. Máme vestavěnou podporu světel. Máme vestavěné míchání barev (blending), takže to co kreslíme nemusí prostě přepsat to, co tam bylo předtím, ale může se k tomu třeba příčíst. Máme alpha-test (podobné jako z-buffer; testuje hodnotu složky A proti určité úrovni kterou můžete nastavit a pokud je [nižší/vyšší/stejná/jiná/nikdy/vždycky - můžete si vybrat] tak pixel zapíše) OpenGL taky má podporu pro kouř, a to v té podobě že karta pro každý vrchol buď spočítá hodnotu "zakouřenosti" v závislosti na parametrech kouře, nebo hodnoty zadáte sami a potom pro každý pixel podle interpolované hodnoty "zakouřenosti" v daném pixelu míchá mezi barvou pixelu a barvou kouře. Potom je tu ještě scissor - "nůžkový" test, kdy můžete nastavit část obrazovky do které budete kreslit jako horní a spodní roh - a nikam jinam se nekreslí.

My chceme kreslit! ... mno dobře. OpenGL umí hlavně kreslit. Můžeme kreslit body, čáry, multičáry (čára, složená z více čar), smyčky (jako multičára, ale navíc vede ještě jedna čára od posledního bodu k prvnímu, takže smyčku uzavírá), trojůhelníky, řetězy trojůhelníků (triangle strips, užitečné, protože se hodně vrcholů použije dvakrát a karta jich tudíž nemusí tolik transformovat), "kytice" trojúhelníků (triangle fans), čtyřúhelníky (konvexní! jinak se budou kreslit podivně, stejně jako v našem minulém engine), řetězy čtyřúhelníků (quad strips) a polygony (n-úhelníky, zase jen konvexní). Pro body můžete nastavit velikost, pro čáry tloušťku (tlusté čáry DirectX neumí) a pro všechny plošné útvary (trojúhelníky / čtyřúhelníky / polygony) můžete nastavit, zda se budou kreslit jako vrcholy, jako čáry nebo s vyplněnou plochou. (je možnost nastavit to pro přední a pro zadní stranu polygonu zvlášť!, takže zepředu se můžou polygony kreslit plné a zezadu jako čáry) OpenGL samozřejmě má zabudovaný backface culling, můžete dokonce nastavit jestli máte vrcholy po směru hodinových ručiček, nebo proti směru!

Kreslení probíhá tak, že zavoláte funkci glBegin(x), kde x je jedna z konstant, odpovídající tomu, co chcete kreslit. Potom nastavujete barvy (glColor{3,4}{b,s,i,f,d} - teď se asi divíte co to je za binec. Znamená to, že můžete zadat barvu o třech (RGB) nebo čtyřech (RGBA) souřadnicích a buď jako bajty, shorty, integery, floaty nebo doubly. My budeme používat floaty a většinou budeme nastavovat RGB barvu, takže budeme volat glColor3f(float r, float g, float b) - chytré, že?) Bílá se nastaví jako glColor3b(255, 255, 255), nebo glColor3s(65535, 65535, 65535) nebo glColor3i(4294901760, 4294901760, 4294901760) nebo glColor3f(1, 1, 1). OpenGL počítá s barvami jako s floaty, takže při tom zůstaneme a budeme zadávat floaty. Jednak je to pohodlné a jednak karta nemusí dělat konverze. Dál můžeme udat normálu (glNormal{3}{b,s,i,f,d}), souřadnice textury (glTexCoord{1,2,3,4}{s,i,f,d}), nebo souřadnice kouře (glFogCoord{f,d}). Nakonec zadáme pozici a tím vrchol pošleme kartě. Pozice se zadává funkcí (glVertex{2,3,4}{s,i,f,d}). Divíte se co je čtyřrozměný vertex? O tom si popovídáme jindy, jen nastíním že normálně je čtvrtá souřadnice 1. Je to stejná souřadnice, jaká je v maticích - pro první tři sloupce, jež symbolizují vektory - směry je nula a pro čtvrtý sloupec, kde je uložená pozice je to jedna. Pokud zadáte vrchol se souřadnicí w = 0 (tak se ona souřadnice jmenuje), bude to vrchol v nekonečnu. Zatím asi nevidíte praktické využití, ale opravdu tu je. Nekonečné souřadnice je jedna z věcí, které DirectX neumí. Naproti tomu pokud zadáváte 2D vertex, je to jako kdybyste zadali 3D vertex se z = 1.

Teď už bysme skoro mohli kreslit, ale ... ještě souřadnice. OpenGL má souřadnicový systém, že když jsou všechny matice jednotkové, je uprostřed obrazovky souřadnice (2D) [0, 0], v levém spodním rohu je [-1,-1] a v pravém horním rohu je [1,1], takže osa y jde prostředkem obrazovky nahoru, x jde doprava a z kolmo k povrchu obrazovky, dovnitř. Když nastavujete perspektivní korekci, nastaví se klipovací pyramida, podobně jako v našem engine. Jenže tady má pyramida šest rovin, čtyři jako my po stranách obrazu a potom jednu blízko nule (ta uřezává polygony, které jsou moc blízko) a potom jednu daleko, která uřezává polygony, které jsou za určenou hranicí. My si určíme obě hranice, ve worldspace jednotkách a hodnoty, které se budou ukládat do z-bufferu budou v rozmezí 0 - 1. Mimo klipovací pyramidy máme ještě několik uživatelských klipovacích rovin, které můžeme vesele používat.

No ale teď už se můžeme vrhnout na věc a něco si nakreslit ...

Napřed budeme chtít nastavit matice. Pro matice máme tyhle funkce:

  • void glMatrixMode(enum x) - řekne že budeme pracovat s maticí x (buď GL_MODELVIEW, GL_PROJECTION - není matice jak je známe, ale udává parametry projekce, GL_TEXTURE - matice, kterou se transformují souřadnice textur (takhle jsou dělané všechny ty otáčející se a pobíhající textury v Quake3))
  • void glLoadIdentity() - nahrajeme jednotkovou matici
  • void glLoadMatrix{f,d}(T m[16]) - nahrajeme matici, odpovídá našim maticím ze starého engine; DirectX má prohozené řádky a sloupce
  • void glMultMatrix{f,d}(T m[16]) - stávající matici vynásobíme jinou maticí
  • void glRotate{f,d}(T angle, T x, T y, T z) - stávající matici vynásobíme maticí rotace o angle stupňů (ne radiánů!) okolo osy [xxz]
  • void glTranslate{f,d}(T x, T y, T z) - stávající matici vynásobíme maticí posuvu o vektor [xyz]
  • void glScale{f,d}(T x, T y, T z) - stávající matici vynásobíme maticí zvětšení vektorem [xyz] (každý rozměr se zvětší tolikrát, kolik je hodnota odpovídající složky vektoru)
  • void glFrustum(double l, double r, double b, double t, double n, double f) - pro matici projekce; nastavujeme klipovací pyramidu; l = left, r = right, t = top, b = bottom, tzn. levá, pravá, horní, dolní vzdálenost klipovací roviny v bodě [0 0 1] - stejné jako u naší pyramidy. Navíc je tu n = near = blízká klipovací rovina a f = far = vzdálená klipovací rovina
  • void glPushMatrix() - pro každý typ matice je tu zásobník matic, do kterého můžeme matici vložit, potom ji vynásobit nějakou jinou, nakreslit odpovídající geometrii a potom zase obnovit původní. Minimální počet matic, které můžeme uložit je 32 (tzn. můžeme 32krát po sobě volat glPushMatrix(), přesnou hodnotu zjistíme voláním glGetIntegerv() s parametrem GL_MAX_MODELVIEW_STACK_DEPTH, GL_MAX_PROJECTION_STACK_DEPTH nebo GL_MAX_TEXTURE_STACK_DEPTH, podle toho který typ matic chceme ukáldat)
  • void glPopMatrix() - opačná funkce, vybíráme matici ze zásobníku
  • void gluLookAt(float pos_x, float pos_y, float pos_z, float tar_x, float f_tar_y, float f_tar_z, float f_up_x, float f_up_y, float f_up_z) - nastavíme kameru pomocí pozice f_pos_*, pozice cíle f_tar_* a vektoru nahoru f_up_*

Tak. Pokud chceme kreslit 2D grafiku, prostě jen vynulujeme matici projekce a modelview matici a máme 2D souřadnice tak, jak jsme si říkali ... Můžeme to hned zkusít:


void Kresli()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // nahrajeme matice ...

    glBegin(GL_TRIANGLES);
    // budeme kreslit trojúhelníky ...

    glColor3f(1, 0, 0); // barva ...
    glVertex2f(-.5f, -.5f); // ... a pozice

    glColor3f(0, 1, 0);
    glVertex2f(   0,  .5f);

    glColor3f(0, 0, 1);
    glVertex2f( .5f, -.5f);

    glEnd();
    // konec trojúhelníků
}

Vypadá to jednoduše, že? Je to jednoduché! Tenhle kousek kódu nakreslí trojúhelník s červeným, modrým a zeleným vrcholem. Pokud je vše nastavené správně, výsledek bude vypadat takhle:

Náš první trojúhelník s OpenGL

... ale mluvil jsem o tom, že OpenGL umí hromadu různých zvěřáren. Tady je několik funkcí, ovlivňujících rasterizér:

  • void glFrontFace(enum mode) - nastaví pořadí vrcholů v trojúhelníku. možné hodnoty jsou GL_CW (clock-wise = podle hodinových ručiček) a GL_CCW (counter-clock-wise = proti hodinovým ručičkám)
  • void glPolygonMode(enum face, enum mode) - nastaví způsob kreslení plošných útvarů. face je GL_FRONT (přední, přivrácené polygony), GL_BACK (back-facy, odvrácené polygony), GL_FRONT_AND_BACK (obě strany) a říká, který mód kreslení budeme měnit, mode je buď GL_FILL (vyplnit, normální), GL_LINE (kreslit jen čáry na hranách) nebo GL_POINT (kreslit jen body ve vrcholech)
  • void glShadeModel(enum mode) - nastaví stínovací model možnosti jsou GL_FLAT (plochý - hodnoty, se berou z jednoho vertexu) nebo GL_SMOOTH (hladký - hodnoty se interpolují. tenhle mód musíte mít nastavený abyste dostali takový obrázek jako je ten s trojúhelníkem. při GL_FLAT dostanete "jen" modrý trojúhelník)
  • void glLineWidth(int width) - nastaví šířku čáry, v pixelech
  • void glPointSize(int size) - nastaví velikost bodů v pixelech
  • void glCullFace(enum backface) - nastaví backface culling, backface může být GL_FRONT (přední, přivrácené polygony), GL_BACK (back-facy, odvrácené polygony), GL_FRONT_AND_BACK (obě strany) a říká, které polygony se zahodí. Aby tohle fungovalo, musíte zavolat glEnable(GL_CULL_FACE) a povolit backface culling. Pokud naopak nechcete nic zahazovat, nastavíte buď GL_FRONT_AND_BACK, nebo zavoláte glDisable(GL_CULL_FACE) a zakážete backface culling.
  • void glColorMask(bool r, bool g, bool b, bool a) - nastaví masku zápisu barev, tzn. když zavoláte glColorMask(false, true, true, true), tak se nebude zapisovat červený kanál a bude se prostě nechávat hodnota červené, která byla v obraze předtím. !pozor! Když budete mazat obraz, tak se vypnuté kanály ani nesmažou! Musíte znova zapnout!
  • void glClear(int mask) - vyčistí obrazový buffer, mask se nastaví na GL_COLOR_BUFFER_BIT, jelikož chceme smazat barvy. Když mažeme třeba ještě z-buffer, budeme volat glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) a odbyjeme si to najednou. Ale můžeme to klidně rozdělit, pokud je potřeba.
  • void glClear(double r, double g, double b, double a) - nastaví barvu, kterou se bude mazat obraz. obdobně je tu i glClearDepth(doube z)

Teď se ještě malinko rozepíšu o tom, jak se kreslí všechny ty zajímavé věci, které OpenGL umí kreslit.

co kreslímeco zadáme jako parametr glBegin()
bodyGL_POINTS
čáryGL_LINES
multičáruGL_LINE_STRIP
smyčkuGL_LINE_LOOP
trojúhelníkyGL_TRIANGLES
řetěz trojúhelníkůGL_TRIANGLE_STRIP
kytici trojúhelníkůGL_TRIANGLE_FAN
čtyřúhelníkyGL_QUADS
řetěz čtyřúhelníkůGL_QUAD_STRIP
polgonGL_POLYGON

Možná jste si všimli, že používám množná čísla - pokud kreslíte body, čáry nebo trojúhelníky, můžete jich mezi glBegin() a glEnd() nasypak kolik se vám zlíbí. Pokud kreslíte řetězy / smyčky / kytice / polygony, můžete nakreslit jen jeden řetěz / smyčku / kytici / polygon, potom musíte zavolat glEnd(), čímž řeknete že už toho víc nebude a až potom můžete pomocí glBegin() začít další útvar.

Ještě si ukážeme v jakém pořadí se zadávají vrcholy pro řetězy troj/čtyř-úhelníků a pro kytice trojůhelníků:

co kreslímev jakém pořadí jdou vrcholy
řetěz trojúhelníkůGL_TRIANGLE_STRIP
GL_TRIANGLE_STRIP
kytici trojúhelníkůGL_TRIANGLE_FAN
GL_TRIANGLE_FAN
řetěz čtyřúhelníkůGL_QUAD_STRIP
GL_QUAD_STRIP

No a teď už si můžete stáhnout jednoduchý sampl i se zdrojáky: (měl bych ještě říct, že abyste mohli zkompilovat předchozí kód, musíte přidat knihovnu opengl32.lib k seznamu knihoven pro linkování (project-settings-link) a includovat si <gl/gl.h>)


gl-engine 01

OpenGL Engine 01

To by bylo ... Ale co by to bylo za seriál o 3D-enginech, kdybysme se nedostali ke 3D!? Ještě si napíšeme jeden examplesák, kde nastavíme matici projekce a budeme poletovat okolo nějakého objektu ...

V Předchozím example jsme jen vynulovali matice. To ale na 3D nestačí. Musíme nastavit klipovací pyramidu. K tomu můžeme použít jednoduše gluPerspective(), kterému předáme zorný úhel, poměr stran obrazu a blízkou a dalekou klipovací rovinu (jen vzdálensoti). Ale protože kvůli téhle funkci musíte přilinkovat celou knihovnu glu32.lib a tahat sebou glu32.dll a inkludovat <gl/glu.h>, nevyplatí se to. Potom to dopadne že hra je na sedmi dvd a když se nainstluje, zabere na disku padesát giga (jak už to tak poslední dobou bývá :o)). Tenhle kousek kódu použijeme místo gluPerspective():


    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    const float Pi = 3.1415926535897932384626433832795028841971f;
    const float f_z_near = .3f;
    const float f_z_far = 1000.0f;
    const float f_fov = 90.0f;
    const float f_aspect = (float)n_width / (float)n_height;

    float f_w, f_h;

    f_h = tan(f_fov * .5f / 180 * Pi) * f_z_near * .5f;
    f_w = f_h * f_aspect;
    // spočítáme polovinu šířky (* .5f) klipovací pyramidy
    // v rovině x-y, položené na z = f_z_near

    glFrustum(-f_w, f_w, -f_h, f_h, f_z_near, f_z_far);

Máme tam několik konstant, udávající pí, blízkou a dalekou klipovací rovinu, zorný úhel a poměr stran. Na začátku si nastavíme projekční matici jako aktivní a vynulujeme si ji na jednotkovou. Potom spočítáme polovinu vzdálenosti protilehlých klipovacích rovin v blízké rovině (rovina, rovnoběžná s x-y rovinou = s obrazovkou a vzdálená f_z_near) jednak vertikálně a podle poměru stran odvodíme i horizontálně. Potom už to jen nasypeme do glFrustum(). OpenGL nabízí ještě ortogonální projekci, kód je úplně stejný, jen glFrustum() zaměníte za glOrtho(). Tahle projekce není "pyramidová", ale "krychlová", použijete ji když chcete napsat okno jako má třeba 3ds-max, tedy pohled z určité roviny. Jinak pokud jste si všimli, kód je náramně poobný tomu co máme ve staré klipovací pyramidě v software-rendering enginu. Protože to určitě nechcete hledat, přikládám pro nahlédnutí: (pro ty zmatené - následující kód nikam nepatří, je tu jen pro srovnání)


void SetFOV(float n_Width, float n_Height, float f_z_delta, Pyramid *p_pyramid)
{
    float x_right, y_bottom, x_left, y_top;

    x_left = -n_Width / (2 * f_z_delta);
    y_top =  n_Height / (2 * f_z_delta);
    x_right =  n_Width / (2 * f_z_delta);
    y_bottom = -n_Height / (2 * f_z_delta);
    // spočítá souřadnice průsečíků pyramidy s osami X a Y

    SetPlane(&p_pyramid->p0, 0, 0, 0, x_right, 0, 1, x_right, 1, 1);
    SetPlane(&p_pyramid->p1, 0, 0, 0, x_left, 1, 1, x_left, 0, 1);
    SetPlane(&p_pyramid->p2, 0, 0, 0, 0, y_bottom, 1, 1, y_bottom, 1);
    SetPlane(&p_pyramid->p3, 0, 0, 0, 1, y_top, 1, 0, y_top, 1);
    // spočítá rovnice jednotlivých rovin
}

// a volali jsme to takhle:

z_delta = (float)fabs((float)n_Width / ((float)tan(90 * (PI / 180) / 2) * 2));
// perspektivni zkresleni pro úhel 90°

SetFOV((float)n_Width, (float)n_Height, z_delta, &pyramid);
// klipovaci pyramida (je o trochu menší kvůli nepřesnostem)

Teď už máme perspektivu a klipovací pyramidu. Ale ještě odnikud nekoukáme. Tím že nastavíme mód matice na modelview a vynulujeme ji, docílíme toho že koukáme z [0 0 0] směrem po z+ (po vektoru [0 0 1]). Buďto můžeme invertovat svoji matici kamery a nahrát ji pomocí glLoadMatrixf(), nebo můžeme použít glTranslatef() a glRotatef() a pohled někam našoupat, nebo můžeme zavolat gluLookAt() a nechat si matici spočítat z pozice kamery, cíle pohledu a vektoru nahoru, jak už jsme to dělali nesčetněkrát v předchozím seriálu. Dnes budeme zobrazovat jen rotující bramboru, takže bude stačit poposunout se kousek od ní - pomocí glTranslatef().

Teď si tak ještě říkám - co takhle užít si HW podporu z-bufferu? Z-buffer se povoluje voláním glEnable(GL_DEPTH_TEST). Z-buffer má možnost nastavení jak porovnávat. To je pomocí void glDepthFunc(enum func) kde func může být GL_ALWAYS (kresli vždycky - stejné jako když máte z-buffer vypnutý), GL_NEVER (nekresli nic - použití se naskytne později u stencilu), GL_LEQUAL (kresli když je z menší nebo stejné jako hodnota v z-bufferu - výchozí nastavení), GL_LESS (kresli když je z menší, než v bufferu), GL_GEQUAL (větší nebo stejné), GL_GREATER (větší), GL_EQUAL (jen když je stejné - použitelné na fleky po zdech), GL_NOTEQUAL (když není stejné - nepoužitelné :o)). Z-buffer musíme taky vyčistit, to se dělá pomocí glClear(GL_DEPTH_BUFFER_BIT). Můžeme taky vypnout zapisování do z-bufferu (hodí se pro průhledné povrchy) pomocí glDepthMask(bool write).

Takže jak to bude vypadat teď?


void Render()
{
    float f_time, f_delta_time;

    // spočítáme časování ... (f_delta_time je derivace času, f_time je čas v sekundách)

    glClearColor(1, 1, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // smažeme buffery, chceme bílé pozadí

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // nastavíme matici projekce a uděláme jednotkovou

    const float Pi = 3.1415926535897932384626433832795028841971f;
    const float f_z_near = .3f;
    const float f_z_far = 1000.0f;
    const float f_fov = 90.0f;
    const float f_aspect = (float)n_width / (float)n_height;

    float f_w, f_h;

    f_h = tan(f_fov * .5f / 180 * Pi) * f_z_near * .5f;
    f_w = f_h * f_aspect;
    // spočítáme polovinu šířky (* .5f) klipovací pyramidy
    // v rovině x-y, položené na z = f_z_near

    glFrustum(-f_w, f_w, -f_h, f_h, f_z_near, f_z_far);
    // nastavíme klipovací pyramidu a spočítáme matici projekce

    glTranslatef(0, 0, -35);
    // posuneme se kousek dozadu

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // nastavíme matici na modelview a uděláme jednotkovou

    glRotatef(f_time, 0, 1, 0);
    glRotatef(sinf(f_time / 20 + 1) * 180, 1, 0, 0);
    glRotatef(cosf(f_time / 30) * 180, 0, 0, 1);
    // má to být rotující brambora, takže ji necháme rotovat ...

    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);

    Draw_Potato(f_time / 40);
    // kreslíme bramboru

    Blit();
    // překlopíme buffer, takže uživatel uvidí brambru rotovat na monitoru
}

Na začátku uděláme pár operací a spočítáme si čas, aby nám brambora rotovala pěkně plynule. Potom smažeme buffery, nastavili jsme si příjemné bílé pozadí ... Potom spočítáme matici projekce a poodsuneme se 35 jednotek od bodu [0 0 0] dozadu, takže na bramboru pěkně uvidíme. Potom přepneme na modelview, narotujeme matici brambory, zapneme backface culling a z-buffer, nakreslíme bramboru a překlopíme buffery.

Asi čekáte že budeme rotovat kostkou jako u všech seriálů o OpenGL. To byste podcenili swini! Budeme rotovat jakýmsi 3D fraktálem co o něm teď nevím jak se jmenuje ... dost možná je to něčí "houba", ale nejsem si jistej. (stejně - je už na první pohled vidět, že programátoři pečují o svoje zdraví a jsou vyloženě vysazení na stravu s vysokým obsahem zeleniny - a pak že se nestarají o svůj zevnějšek !! :o)) Kód je dost jednoduchej a už byste měli být po přečtení předchozího seriálu ve 3D dost zběhlí, takže ho nebudu vysvětlovat. Kreslíme jednak trojúhelníky a jednak trojúhelníky nastavené na kreslení hran, takže máme hrany pěkně vytažené. Stahujte druhý example, mějte se hezky a někdy se zase sejdeme u OpenGL:


gl-engine 02

OpenGL brambíro-rotátor 02

-tHE SWINe-


zpět


Valid HTML 4.01!
Valid HTML 4.01