Hlavní navigace

OpenGL a nadstavbová knihovna GLU (21)

21. 12. 2004
Doba čtení: 8 minut

Sdílet

V dnešní části seriálu o nadstavbové grafické knihovně OpenGL GLU dokončíme část věnovanou teselátorům. Budeme se zabývat významem některých důležitých callback funkcí volaných při teselaci, pravidlům pro zjištění vnitřní části teselovaných polygonů a taktéž základními CSG operacemi, které lze nad polygony v průběhu teselace provádět.

Obsah

1. Protínání hran polygonu
2. Callback funkce volaná při výskytu chyby
3. Řízení běhu teselace
4. Pravidla pro zjištění vnitřní části polygonu
5. CSG operace nad polygony

    5.1 Sjednocení (union)
    5.2 Průnik (intersection)
    5.3 Rozdíl (difference)
6. Demonstrační příklad
7. Obsah dalšího pokračování
8. Seznam funkcí OpenGL, GLU a GLUT zmíněných v této části
9. Zkomprimovaná verze článku i s přílohami

1. Protínání hran polygonu

V některých případech je nutné pracovat s polygony, jejichž hrany se v nějakém obecném bodě protínají. Tuto situaci je nutné při teselaci řešit tím způsobem, že se správně zareaguje na událost, kterou teselátory generují ihned, jakmile narazí na protnutou hranu. Z dat, která teselátor v tomto případě poskytne, je možné získat souřadnice nového vrcholu, který se na místě protnutí nachází; tento vrchol se na chvíli stane platným vrcholem zpracovávaného polygonu a jako takový se musí zúčastnit vykreslování.

Při volání callback funkce během dosahování průsečíku však teselátor pošle přímo pouze souřadnice průsečíku – další činnost (výpočet barev, souřadnic do textury apod.) je ponechána na programátorovi aplikace, který by měl převzít argumenty od teselátoru a z těchto argumentů souřadnice průsečíků vypočítat. Průsečík je v tomto případě definován jako lineární kombinace čtyř vrcholů (bodů), jejichž souřadnice jsou callback funkci předány. Váha každého vrcholu leží v rozmezí 0–1, přičemž součet všech čtyř vah musí být roven 1. Pokud má nějaký předaný vrchol váhu rovnou nule, není při výpočtech použit.

Programátor tedy při zavolání callback funkce převezme všechny argumenty, ve kterých jsou (nepřímo, přes ukazatele) obsaženy souřadnice čtyř vrcholů spolu s jejich váhami. Souřadnice či další údaje podrobí lineární interpolaci a výsledný bod vrátí teselátoru pro další zpracování. Příslušná callback funkce je registrována zavoláním následujícího kódu:

gluTessCallback(gluTesselator,
                GLU_TESS_COMBINE,
                combineFunc);

kde gluTesselator představuje objekt teselátoru alokovaný v operační paměti počítače, GLU_TESS_COMBINE specifikuje typ callback funkce a combineFunc je ukazatel na vývojářem aplikace vytvořenou callback funkci, která musí mít hlavičku:

void combineFunc(GLdouble coords[3],
                 void     *vertex_data[4],
                 GLfloat  weight[4],
                 void     **outData);

V nejjednodušším případě je možné souřadnice získat z prvního argumentu callback funkce, v některých případech je však zapotřebí pomocí lineární kombinace dopočítat například souřadnice do textury, normálový vektor či barvu nově vytvořeného vrcholu – tyto činnosti totiž teselátor automaticky neprovádí.

Aplikace musí na volání této callback funkce reagovat tím způsobem, že přes argument outData vrátí nově vypočtená data. Pokud tomu tak není (tj. vrátí se NULL) nebo callback funkce není vůbec zaregistrována, je to považováno za chybu a volá se chybová callback funkce s parametrem GLU_TESS_NEED_COM­BINE_CALLBACK – viz též následující kapitolu, ve které se chybovou callback funkcí zabývám podrobněji.

Pokud není v aplikaci zapotřebí interpolovat žádné další hodnoty (kromě polohy samotného průsečíku), je možné celou callback funkci vytvořit například následovně:

//-----------------------------------------------
// Tato funkce je zavolána při výskytu
// průsečíků dvou hran polygonu
//-----------------------------------------------
void CALLBACK callbackCombine(GLdouble coords[3],
                              GLdouble *data[4],
                              GLfloat  weight[4],
                              GLdouble **dataOut)
{
   GLdouble *vertex;
   vertex = (GLdouble*)malloc(3*sizeof(GLdouble));

   vertex[0] = coords[0];
   vertex[1] = coords[1];
   vertex[2] = coords[2];
   *dataOut = vertex;
}

Z výše uvedené funkce je patrné, že pro nový bod je nejprve alokována paměť (funkce malloc) a posléze jsou do této oblasti paměti nakopírovány hodnoty jednotlivých souřadnic průsečíku. Ukazatel na nově vytvořenou paměť je vrácen v argumentu dataOut – ten je sám ukazatelem, neboť C-čko neumožňuje předávání parametrů odkazem.

2. Callback funkce volaná při výskytu chyby

Při zavolání callback funkce při výskytu chyby se do této funkce předá jedna hodnota typu int, kterou je chyba blíže specifikována. Předanou hodnotou je vždy jedna ze symbolických konstant definovaných v hlavičkovém souboru gl/glu.h. Význam jednotlivých symbolických konstant se specifikací chyby je následující:

  • GLU_TESS_MISSIN­G_BEGIN_POLYGON – chybí volání funkcegluTessBe­ginPolygon() před zadáním jednotlivých kontur. Tato chyba se automaticky napraví tak, že se funkce gluTessBeginPo­lygon() zavolá s implicitními parametry.
  • GLU_TESS_MISSIN­G_END_POLYGON – chybí volání funkce gluTessEndPoly­gon() před smazáním objektu s teselátorem. Systém opět tuto chybu napraví zavoláním chybějící funkce.
  • GLU_TESS_MISSIN­G_BEGIN_CONTO­UR – chybí volání funkcegluTessBe­ginContour() a přitom byla dosažena funkce gluTessEndCon­tour() nebo gluTessEndPoly­gon().
  • GLU_TESS_MISSIN­G_END_CONTOUR – chybí volání funkce gluTessEndCon­tour() a přitom byla zavolána nová funkce gluTessBeginCon­tour() nebo naopak několikrát funkcegluTessEn­dPolygon() na špatném místě programu.
  • GLU_TESS_COOR­D_TOO_LARGE – všechny souřadnice vrcholů polygonů musí být tak malé, aby nepřetekla výsledná hodnota operace, která tyto souřadnice mezi sebou násobí. Pokud by přetečení mělo nastat, je zavolána chyba s touto symbolickou konstantou a výsledná hodnota je oříznuta, což může způsobit chybnou práci teselátoru.
  • GLU_TESS_NEED_COM­BINE_CALLBACK – tato chyba nastane v případě, že není registrována callback funkce pro výpočet bodů na místě protnutí dvou hran, a přitom je nalezena hrana, která další hranu protíná v jiném místě, než je její vrchol. V případě výskytu této chyby neproběhne teselace korektně, protože nejsou vypočteny všechny vlastnosti nově vytvořeného bodu v místě průsečíku hran – viz předcházející kapitolu.

3. Řízení běhu teselace

Celou teselaci je možné řídit přímo ze zavolaných callback funkcí. Nikde totiž není předepsáno, že callback funkce, která je zavolána pro začátek vykreslování trojúhelníků (GLU_TESS_BEGIN), musí opravdu zavolat funkci glBegin(). Je například možné všechna data ukládat do display listů pro pozdější vykreslení, nebo lze použít i uživatelsky vytvářené struktury (pole, seznamy, soubory apod.).

V demonstračním příkladu uvedeném níže je ukázáno, že se po zavolání callback funkce dají jednoduše vypisovat předávané parametry, takže vývojář aplikace může i bez dalších ladicích prostředků vidět průběh teselace na konzoli (nebo může provést přesměrování standardního výstupu do souboru apod.; zde OpenGL použití těchto prostředků v žádném případě nebrání).

Následuje příklad callback funkce, která je volána pro každý vytvořený vrchol:

//-----------------------------------------------
// Tato funkce je zavolána při vytvoření každého
// nového vrcholu teselovaného polygonu
//-----------------------------------------------
void CALLBACK callbackVertex(GLdouble coords[3])
{
    printf("glVertex3d(%5.1f, %5.1f, %5.1f)\n",
          (float)coords[0],
          (float)coords[1],
          (float)coords[2]);
    glVertex3dv(coords);
}

Jak je z výpisu této funkce patrné, je možné s přicházejícími údaji pracovat libovolným způsobem. Nejjednodušší je zavolání funkce glVertex3dv(), která do vykreslovacího řetězce pošle údaje o novém vrcholu. Tato funkce jako svůj parametr akceptuje ukazatel na pole, takže není nutné z předaných údajů parametry složitě extrahovat např. do pomocných proměnných.

4. Pravidla pro zjištění vnitřní části polygonu

Teselované polygony mohou nabývat prakticky libovolných tvarů. Pokud se jedná o polygony, u nichž dochází k vzájemnému protínání hran, naskýtá se otázka, jakým způsobem je možné zjistit, která část polygonu je vnitřní a která vnější. To je řešeno pomocí pravidel, která lze před teselací upravit. Implicitně je nastaven režim, při němž je vnější kontura orientována ve směru hodinových ručiček a otvory mají konturu orientovanou opačným směrem. Toto chování je však možné změnit, například lze ignorovat všechny otvory a vytvořit jednodušší polygon složený ze své hranice. Některé nejčastěji používané způsoby nastavení pravidel budou popsány v následující kapitole.

5. CSG operace nad polygony

Vhodným nastavením vlastností teselátorů lze docílit CSG operací prováděných nad polygony. Mezi základní CSG operace patří sjednocení, průnik a rozdíl. Veškeré nastavení je platné pouze pro polygony, ve kterých je vnější hranice/kontura orientována ve směru hodinových ručiček (CCW) a vnitřní hranice (tj. otvory) jsou orientovány proti směru hodinových ručiček (CW).

5.1 Sjednocení (union)

Operace sjednocení lze provést tím způsobem, že se nastaví vlastnost GLU_TESS_WINDIN­G_NONZERO nebo GLU_TESS_WINDIN­G_POSITIVE, což lze provést před vlastní teselací funkcí gluTessProperty() následujícím způsobem:

gluTessProperty(gluTess,
                GLU_TESS_WINDING_RULE,
                GLU_TESS_WINDING_NONZERO);

případně:

gluTessProperty(gluTess,
                GLU_TESS_WINDING_RULE,
                GLU_TESS_WINDING_POSITIVE);

5.2 Průnik (intersection)

Průnik dvou polygonů se provádí složitějším způsobem. Nejdříve je nutné pomocí vhodně napsaných callback funkcí vytvořit nový polygon z kontur obou polygonů, pro které se má operace průniku provést. Při vytváření nového polygonu je zapotřebí nastavit vlastnosti teselace následovně:

gluTessProperty(gluTess,
                GLU_TESS_WINDING_RULE,
                GLU_TESS_WINDING_ABS_GEQ_TWO);

Na nově vzniklý polygon je možné aplikovat zobrazení hranice, při kterém se neprovádí převod na trojúhelníky, ale na lomenou čáru, čímž se průnik zvýrazní. Při provádění této operace není nutné, aby byly vnější a vnitřní kontury orientovány v opačném směru – ve skutečnosti se orientace kontur ignoruje.

5.3 Rozdíl (difference)

Operaci rozdílu je také nutné provádět ve dvou krocích. V prvním kroku se do teselátoru převedou oba polygony tak, že u druhého polygonu (kterým se rozdíl provádí) jsou změněny orientace vnějších i vnitřních kontur. Z výsledku vzniklého první teselací je možné rozdílový polygon získat tak, že se provede druhá teselace s vlastností GLU_TESS_WINDIN­G_RULE nastavenou naGLU_TESS_WIN­DING_POSITIVE. Popřípadě lze zobrazit pouze hranice výsledného polygonu, podobně jako v předchozím případě.

6. Demonstrační příklad

Po překladu a spuštění demonstračního příkladu se vykreslí složitější polygon s pěti vrcholy a s protínajícími se hranami. Na levé straně okna jsou pro vykreslení použity přímo funkce OpenGL, na pravé straně je polygon nejdříve teselován a teprve poté vykreslen. Pomocí mezerníku je možné měnit pravidla pro zjišťování vnější a vnitřní části polygonu, čímž se také liší výsledek teselace, tj. počet a tvar vytvořených trojúhelníků.

Všechny callback funkce při své práci provádí taktéž výpis na konzoli, takže je možné detailně prozkoumat, jakým způsobem teselátory pracují.

Zdrojový kód tohoto demonstračního příkladu je dostupný zde, jeho HTML verze se zvýrazněním syntaxe zde.

Obrázek 1: Screenshot demonstračního příkladu
Obrázek 1: Screenshot demonstračního příkladu

root_podpora

7. Obsah dalšího pokračování

Dnešním dílem jsme ukončili popis vlastností a funkcí poskytovaných nadstavbovou knihovnou OpenGL GLU. Další díl tedy bude věnován soupisu všech popsaných témat a funkcí spolu s odkazy na jednotlivé díly.

8. Seznam funkcí OpenGL, GLU a GLUT zmíněných v této části

gluTessBeginCon­tour()
gluTessBeginPo­lygon()
gluTessCallback()
gluTessEndContour()
gluTessEndPolygon()
gluTessProperty()
glVertex3dv()
 

9. Zkomprimovaná verze článku i s přílohami

Zkomprimovaná verze tohoto článku i s přílohami a demonstračními příklady je uložena zde.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.