OpenGL a nadstavbová knihovna GLU (20)

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

Sdílet

Dnešní díl seriálu o evaluátorech poskytovaných nadstavbovou knihovnou OpenGL GLU bude věnován postupu, pomocí kterého lze provádět teselaci jednoduchých (konvexních i nekonvexních) polygonů na jednotlivé trojúhelníky.

Obsah

1. Stručný návod na použití teselátorů
2. Vytvoření nového objektu pro teselaci
3. Registrace callback funkce volané v průběhu teselace
4. Specifikace všech vlastností teselátoru
5. Vytvoření a zobrazení teselovaných polygonů
6. Vymazání objektu pro teselaci
7. Demonstrační příklady
8. Obsah dalšího pokračování
9. Zkomprimovaná verze článku i s přílohami

1. Stručný návod na použití teselátorů

Grafická knihovna OpenGL umožňuje přímo vykreslovat pouze konvexní plošné polygony, což vychází z principu práce interpolátorů umístěných na grafickém akcelerátoru. Interpolátory se starají jak o vyplňování polygonů barvou (filling), tak i o interpolaci barvy (Gouraudovo stínování), interpolaci souřadnic v textuře a o některé základní operace se světly – vždy se jedná o poměrně přímočarou lineární, bilineární či trilineární interpolaci, kterou je jednoduché na grafickém akcelerátoru implementovat.

Bližší popis vykreslování konvexních polygonů a problémů, které mohou při této činnosti nastat, byl uveden v seriálu o grafické knihovně (viz předchozí díl tohoto seriálu, kde jsou uvedeny odkazy). Složitější polygony, které nejsou konvexní, je zapotřebí pro účely vykreslování rozdělit na polygony jednodušší, které již konvexní jsou. Dělení lze provádět až na úroveň trojúhelníků, u kterých je zaručeno, že jsou vždy konvexní a všechny jejich vrcholy leží v jedné ploše. Rozdělení obecných polygonů na trojúhelníky je možné provést buď programově, nebo pomocí takzvaných tesselátorů, jejichž nastavení si ukážeme v dalším textu.

předchozí části tohoto seriálu jsme si stručně popsali postup, který je nutné při použití teselátorů dodržet. Tento postup bude podrobněji popsán v následujících kapitolách:

  1. Vytvoření nového objektu pro tesselaci pomocí funkce gluNewTess().
  2. Registrace callback funkce či funkcí pomocí volání gluTessCallbac­k(). Tato funkce může být zavolána při provádění různých operací v průběhu teselace.
  3. Specifikace všech vlastností teselace zavoláním (i vícenásobným) funkce gluTessProper­ty().
  4. Vytvoření a zobrazení teselovaných polygonů složených z kontur, které musí být uzavřeny. Pokud se jedná o statické objekty, lze s výhodou využít display listů.
  5. Vymazání objektu pro teselaci zavoláním funkce gluDeleteTess().

2. Vytvoření nového objektu pro teselaci

Vytvoření nového objektu teselátoru se provádí zavoláním funkce gluNewTess(), která má následující hlavičku:

GLUtesselator* gluNewTess(
    void
);

Tato funkce pro svoji činnost nevyžaduje žádné parametry a vrací ukazatel na nově vytvořený paměťový objekt typu GLUtesselator. Pokud se alokace paměti pro tento objekt z nějaké příčiny nezdařila (nedostatek paměti, velká fragmentace paměti apod.), vrátí se hodnota 0 (resp. NULL). Vývojář aplikace by tedy měl vždy otestovat návratovou hodnotu této funkce a podle ní upravit běh programu, například vypsat zprávu o vzniklé chybě:

GLUtesselator *gluTessObject;

gluTessObject=gluNewTess();
if (!gluTessObject) {
    perror("alokace paměti pro teselátor se nezdařila");
}
else {
    // OK - pokračujeme v dalším zpracování
}

3. Registrace callback funkce volané v průběhu teselace

Jednou z nejobtížnějších činností při práci s teselátory je správná reakce na data, která teselátory při své práci produkují. Reakce na různé události vznikající při práci teselátorů se provádí pomocí tzv. callback funkcí, tj. funkcí volaných nezávisle (asynchronně) na běhu aplikace. Registrace callback funkcí se provádí pomocí příkazu gluTessCallbac­k(), který má hlavičku:

void gluTessCallback(
    GLUtesselator *gluTessObject,
    GLenum which,
    void (*fn)()
);

Význam jednotlivých parametrů této funkce je následující:

  1. V prvním parametru gluTessObject je předán ukazatel na objekt teselátoru, který byl v operační paměti počítače vytvořen pomocí funkce gluNewTess().
  2. Ve druhém parametru which je uvedeno, pro jakou událost je callback funkce vytvořena. Typ události je specifikován jednou z následujících symbolických konstant definovaných v souboru glu.h:
    GLU_TESS_BEGIN,
    GLU_TESS_BEGIN_DA­TA,
    GLU_TESS_EDGE_FLAG,
    GLU_TESS_EDGE_FLAG_DA­TA,
    GLU_TESS_VERTEX,
    GLU_TESS_VERTEX_DA­TA,
    GLU_TESS_END,
    GLU_TESS_END_DATA,
    GLU_TESS_COMBINE,
    GLU_TESS_COMBI­NE_DATA a konečně
    GLU_TESS_ERROR.
  3. V posledním parametru fn je předán ukazatel na callback funkci.

Počet a typ parametrů, které jsou jednotlivým callback funkcím předány, závisí na typu události, která callback funkci vyvolala. Například pro událost GLU_TESS_BEGIN se předává jeden parametr, v němž je uložen typ grafické primitivy, která se má začít vykreslovat. V některých případech je možné místo uživatelské callback funkce předat „normální“ funkci z OpenGL, například:

gluTessCallback(gluTessObject,
                GLU_TESS_VERTEX,
                glVertex3dv);

Pro některé jiné typy událostí je nutné vytvořit vlastní callback funkci a tu posléze zaregistrovat. Ukážeme se příklad registrace callback funkce pro událost, která vznikne ve chvíli, kdy teselátor začíná vytvářet další grafickou entitu (z minula víme, že výstupem z teselátoru jsou trojúhelníky, případně trsy a pruhy trojúhelníků). Samotná callback funkce je jednoduchá, skládá se pouze z jednoho příkazu, který začne vykreslování zadané entity:

//-----------------------------------------
// Callback funkce volaná při zahájení
// vykreslování grafické primitivy
//-----------------------------------------
void CALLBACK callbackBegin(GLenum which)
{
    glBegin(which);
}

Registrace výše uvedené callback funkce se provede příkazem:

gluTessCallback(gluTessObject,
                GLU_TESS_BEGIN,
                callbackBegin);

Podobnou činnost je nutné provést i při ukončení vykreslování nějaké grafické primitivy, protože OpenGL požaduje, aby příkazy glBegin() a glEnd() byly řádně spárované. Příslušná callback funkce je ještě jednodušší než předchozí callback funkce, protože příkaz glEnd()nevyžaduje žádné parametry:

//-----------------------------------------
// Callback funkce volaná při ukončení
// vykreslování grafické primitivy
//-----------------------------------------
void CALLBACK callbackEnd(void)
{
   glEnd();
}

Registrace této callback funkce se provede následujícím příkazem:

gluTessCallback(gluTessObject,
                GLU_TESS_END,
                callbackEnd);

Při teselaci polygonů mohou nastat různé chyby (například špatně uvedené kontury polygonů apod.), proto je vhodné, aby aplikace (alespoň při vývoji) na chyby nějakým způsobem reagovala. Nejjednodušší možností je vytvoření callback funkce, která na konzoli či do logovacího souboru vypíše vzniklou chybu a aplikaci ukončí (chybu by sice bylo možné vypsat i do GUI okna aplikace, nikde ale není zaručeno, že se při výskytu chyby stavový stroj OpenGL nachází ve stavu „schopném vykreslování“):

//---------------------------------------------
// Callback funkce volaná při výskytu chyby
//---------------------------------------------
void CALLBACK callbackError(GLenum errorCode)
{
   const GLubyte *s=gluErrorString(errorCode);
   fprintf(stderr, "chyba při teselaci: %s\n", s);
   exit(0);
}

Registrace této callback funkce je stejně snadná, jako tomu bylo u obou funkcí předchozích:

gluTessCallback(gluTessObject, GLU_TESS_ERROR, callbackError); 

4. Specifikace všech vlastností teselátoru

Vlastnosti teselátorů se zadávají pomocí funkce gluTessProper­ty(). Tato funkce má následující hlavičku:

void gluTessProperty(
    GLUtesselator *gluTessObject,
    GLenum   which,
    GLdouble value
);

V prvním parametru gluTessObject této funkce se předává ukazatel na objekt teselátoru vytvořený v operační paměti počítače funkcí gluNewTess(). Ve druhém parametru which je pomocí symbolické konstanty zadána některá z vlastností teselátorů, například GLU_TESS_WINDIN­G_RULE, GLU_TESS_BOUN­DARY_ONLY, neboGLU_TESS_TO­LERANCE. V posledním parametru value je předána hodnota vybrané vlastnosti.

Nejdůležitější vlastností teselátorů je způsob zjištění vnějších a vnitřních oblastí polygonů. Polygony se totiž skládají z kontur, které oddělují vnitřní a vnější hranici polygonu. Přesné určení plochy polygonu závisí na orientaci hran kontur (tj. na pořadí vrcholů kontury) a na nastavení vlastnosti GLU_TESS_WINDIN­G_RULE. Bližší informace bude uvedena v následující části tohoto seriálu.

5. Vytvoření a zobrazení teselovaných polygonů

Všechny výše uvedené funkce pouze teselátor připravovaly pro vlastní teselaci. Po této přípravě je již možné teselátoru dodávat data, tj. vrcholy jednotlivých kontur, ze kterých se polygon skládá. Začátek specifikace polygonu je určen funkcí gluTessBeginPo­lygon(), která má hlavičku:

void gluTessBeginPolygon(
    GLUtesselator *gluTessObject,
    void          *polygon_data
);

V prvním parametru této funkce je předán ukazatel na objekt teselátoru, druhý parametr odkazuje na libovolná data, která se mohou přenášet do callback funkcí.

Po určení začátku polygonu je možné zadávat jednotlivé kontury. Začátek kontury se specifikuje funkcí gluTessBeginCon­tour() s hlavičkou:

void gluTessBeginContour(
    GLUtesselator *gluTessObject
);

Význam parametru gluTessObject je stejný jako v předchozích funkcích.

Každá kontura se skládá z uzavřené polyčáry zadané svými vrcholy. Pro každý vrchol se zavolá funkce gluTessVertex():

void gluTessVertex(
    GLUtesselator *gluTessObject,
    GLdouble v[3],
    void *data
);

V prvním parametru je opět předán ukazatel na objekt teselátoru, ve druhém parametru jsou předány souřadnice vrcholu (jde o pole o třech prvcích typu GLdouble), třetí parametr může obsahovat libovolná data, která se předají callback funkcím.

Po zadání všech vrcholů jedné kontury se kontura uzavře zavoláním funkce gluTessEndCon­tour():

void gluTessEndContour(
    GLUtesselator *gluTessObject
);

Celý polygon se posléze uzavře (a následně vykreslí) funkcí gluTessEndPoly­gon() s hlavičkou:

void gluTessEndPolygon(
    GLUtesselator *gluTessObject
);

6. Vymazání objektu pro teselaci

Po ukončení práce teselátoru je důležité provést odstranění objektu, který reprezentuje teselátor v operační paměti počítače. Toto odstranění se provádí pomocí funkce gluDeleteTess(), jež má následující hlavičku:

void gluDeleteTess(
    GLUtesselator *gluTessObject
);

Po provedení této operace je objekt s teselátorem (spolu s dalšími pomocnými údaji, které však nejsou programátorovi aplikace dostupné) odstraněn z operační paměti počítače. To mimo jiné znamená, že ukazatel gluTessObject ukazuje na místo v paměti, do kterého již aplikace nemá povolen přístup! Po odstranění teselátoru je tedy vhodné vynulovat i hodnotu v ukazateli, protože se tím usnadní ladění při nekorektním přístupu:

gluDeleteTess(gluTessObject);
gluTessObject=NULL;

7. Demonstrační příklady

V dnešním demonstračním příkladu je ukázáno, jakým způsobem je možné použít teselátory pro teselaci polygonu, který se skládá pouze z jedné kontury ve tvaru osmiúhelníku. Po spuštění přeložené aplikace se na levé straně obrazovky zobrazí osmiúhelník vykreslený pomocí grafické primitivy GL_POLYGON. V pravé části je vykreslen tentýž tvar, nyní ale za pomoci teselátorů – původní polygon je rozdělen na trojúhelníky.

Aby byly zobrazené hrany lépe viditelné, je zapnuto vykreslování polygonů pouze pomocí hran. V reálných aplikacích se samozřejmě mnohem častěji používá vykreslování polygonů vyplněných konstantní barvou, vzorkem nebo texturou.

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

ict ve školství 24

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

V další části tohoto seriálu si popíšeme, jakým způsobem je možné měnit pravidla určující, která část kontury patří dovnitř polygonu a která leží vně.

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.

Autor článku

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