OpenGL a nadstavbová knihovna GLU (12)

19. 10. 2004
Doba čtení: 9 minut

Sdílet

Dnešní pokračování seriálu o nadstavbové grafické knihovně GLU je věnováno podrobnému popisu funkce gluNurbsCurve(), vlivu stupňů NURB křivek na jejich tvar a významu uzlového vektoru. Také si ukážeme, jakým způsobem je možné pomocí NURB křivek zobrazovat kuželosečky, zejména části kružnice a elipsy.

Obsah

1. Podrobný popis funkce gluNurbsCurve()
2. Stupeň vytvořené NURB křivky
3. NURB křivky vyšších stupňů
4. Význam uzlového vektoru
5. Vykreslování kuželoseček pomocí NURB křivek
6. Demonstrační příklady
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. Podrobný popis funkce gluNurbsCurve()

Již v předchozí části tohoto seriálu jsme si podrobně uvedli, jakým způsobem se postupuje při práci s NURB křivkami vykreslovanými pomocí funkcí z knihovny GLU. Celý postup lze shrnout do sedmi bodů:

  1. vytvoření objektu s NURB křivkou pomocí funkce gluNewNurbsRen­derer()
  2. nastavení atributů NURB křivky (i několikerým) voláním funkce gluNurbsProper­ty()
  3. registrace callback funkce volané při výskytu chyby – gluNurbsCallbac­k()
  4. specifikace začátku vytváření NURB křivky zavoláním funkce gluBeginCurve()
  5. předání souřadnic jednotlivých řídících bodů, uzlového vektoru apod. opakovaným voláním funkce gluNurbsCurve()
  6. specifikace ukončení vytváření NURB křivky pomocí funkce gluEndCurve()
  7. zrušení objektu, jež reprezentuje NURB křivku v operační paměti počítače, zavoláním funkce gluDeleteNurbsRen­derer()

Jedinou funkcí, která minule nebyla dostatečně popsána, je funkce gluNurbsCurve(). Jedná se totiž o jednu z nejsložitějších funkcí z knihovny GLU, alespoň co se týče struktury a významu jejích parametrů. FunkcegluNurbsCur­ve() má hlavičku:

void gluNurbsCurve(
    GLUnurbs * nurb,
    GLint      knotCount,
    GLfloat  * knots,
    GLint      stride,
    GLfloat  * control,
    GLint      order,
    GLenum     type
);

kde jednotlivé parametry mají následující význam:

  • V prvním parametru nurb je předán ukazatel na objekt NURB křivky, jenž musí být v operační paměti počítače předem vytvořen vyvoláním příkazugluNew­NurbsRenderer(). Pokud je předán neplatný ukazatel (například ukazatel na již zrušený objekt), křivka se nevytvoří a současně je generována chyba.
  • Ve druhém parametru knotCount je předán počet položek v uzlovém vektoru (knot vector). Počet položek však nemůže být zvolen libovolně, protože závisí na stupni křivky a na počtu řídících bodů. Pokud se například má pracovat s kubickou křivkou, která je třetího stupně a má čtyři řídící body (což je pro kubiku minimum), bude mít uzlový vektor osm položek. Všimněte si, že nikde není specifikován počet řídících bodů – ten je určen právě ze stupně křivky a počtu položek v uzlovém vektoru.
  • Třetí parametr knots obsahuje ukazatel na oblast paměti, ve které jsou uloženy položky uzlového vektoru. Většinou se položky ukládají do pole, takže je možné do tohoto parametru uložit adresu prvního prvku pole. Je však důležité, aby pole bylo typu GLfloat[] nebo float[], v opačném případě se složky uzlového vektoru vyhodnotí špatně.
  • Do čtvrtého parametru stride je nutné uložit hodnotu, která udává počet položek typu GLfloat, které se nacházejí mezi dvojicí informací o souřadnicích řídících bodů. Idea je taková, že řídící body mohou být uloženy spolu s dalšími informacemi v poli, proto je nutné znát rozdíl adres mezi dvěma po sobě jdoucími položkami (resp. mezi počátečními adresami po sobě jdoucích položek). Vzhledem k tomu, že se rozdíl udává v počtech položek typu GLfloat a ne přímo v bytech, musí být položky pole v paměti zarovnány alespoň na hodnotu sizeof(GLfloat). Prakticky všechny C-čkovské překladače toto zarovnání podporují, stačí ho při kompilaci povolit.
  • Pátým parametrem control je předán ukazatel na pole se souřadnicemi řídících bodů. Prostor, ve kterém jsou řídící body zadány, je specifikován v posledním parametru type.
  • V šestém parametru order je uložen stupeň vytvářené křivky zvýšený o jedničku. Jak jsem již psal u výkladu parametru knots, je důležité, aby byly korektně sladěny počty položek uzlového vektoru, počet řídících bodů a stupeň křivky.
  • V posledním parametru type je uvedeno, jakým způsobem se mají zadané řídící body interpretovat. Podobně jako u evaluátorů je možné, aby řídící body určovaly významné oblasti na křivce ve světovém prostoru. Také je však možné specifikovat souřadnice do textury, normálu řídících bodů nebo jejich barvu. Souřadnice je zapotřebí zadat vždy, ostatní položky jsou volitelné. Příklad na zadání více položek u řídících bodů je ukázán v dalším odstavci.

Pro některé aplikace může být významná skutečnost, že řídící body NURB křivky nemusejí být zadány pouze ve světovém prostoru (podobně jako souřadnice vrcholů vykreslovaných těles), ale i v „dalších prostorech“: texturovacím, barvovém (RGBA) a prostoru, ve kterém jsou specifikovány normálové vektory jednotlivých řídících bodů. Význam specifikace souřadnic v těchto prostorech jsem již vysvětloval při popisu evaluátorů OpenGL – zejména při práci s NURB plochami se musí pro každý řídící bod korektně namapovat i jeho texturovací souřadnice, barva a normála.

Nejjednodušším tvarem specifikujícím NURB křivku je sekvence příkazů, kterými se zadají pouze její řídící body ve světovém prostoru bez dalších informací. Tato sekvence příkazů vypadá následovně:

gluBeginCurve(nurbObj);
    gluNurbsCurve(nurbObj, .., GL_MAP1_VERTEX_3);
gluEndCurve(nurbObj);

případně takto:

gluBeginCurve(nurbObj);
    gluNurbsCurve(nurbObj, .., GL_MAP1_VERTEX_4);
gluEndCurve(nurbObj);

Složitější je situace v případě, že se kromě souřadnic řídících bodů musí zadat i jejich některé další vlastnosti. V následující sekvenci příkazů je ukázáno, jakým způsobem se specifikují polohy řídících bodů spolu s jejich barvami (ty jsou, jak již víme, zadány ve čtyřrozměrném barvovém prostoru RGBA):

gluBeginCurve(nurbObj);
    gluNurbsCurve(nurbObj, .., GL_MAP1_COLOR_4);
    gluNurbsCurve(nurbObj, .., GL_MAP1_VERTEX_3);
gluEndCurve(nurbObj);

2. Stupeň vytvořené NURB křivky

Stupeň NURB křivky udává nejvyšší mocninu polynomu, kterým je křivka či její část zadaná. V předchozích částech jsme si uvedli rekurentní vztah, pomocí něhož lze za použití lineární interpolace/kom­binace funkcí vypočítat jakýkoli bod na NURB křivce. Po rozepsání rekurentního vztahu dojdeme k (po částech) polynomiální funkci s jednou nezávislou proměnnou t. Nejvyšší mocnina, kterou je t umocněno, udává stupeň vzniklé NURB křivky. V OpenGL se však místo stupně křivky (degree) udává parametr order, který je vždy o jedničku vyšší, tj. například pro kubiky je hodnota order rovna čtyřem apod.

Čím je stupeň křivky vyšší, tím je křivka vizuálně hladší, ovšem pouze v případě, že neuvažujeme násobné hodnoty v uzlovém vektoru. U NURB křivek má smysl uvažovat o nejnižším stupni rovném jedné (tj. degree=1order=2). V tomto případě se jedná o křivku, která je složena z lineárních částí, tj. úseček. Zlomy nastávají v místech řídících bodů, tj. tam, kde se přechází z jednoho úseku parametrů uzlového vektoru do úseku dalšího (z předcházejících částí také víme, že složky uzlového vektoru musí tvořit neklesající posloupnost hodnot).

Ukázka NURB křivky prvního stupně je zobrazena na prvním ilustračním obrázku spolu s jejími řídícími body.

NURB křivka prvního stupně spolu s navzájem pospojovanými řídícími body
Obrázek 1: NURB křivka prvního stupně spolu s navzájem pospojovanými řídícími body

NURB křivka druhého stupně (order=3) má již pro potřeby počítačové grafiky zajímavější průběh. Křivka obecně neprochází všemi svými řídícími body, v případě požadavku na průchod těmito body (například při animacích) je možné jejich zdvojení. Křivka musí být zadána minimálně třemi řídícími body. Ukázka křivky druhého stupně je zobrazena na druhém ilustračním obrázku.

NURB křivka druhého stupně spolu s navzájem pospojovanými řídícími body
Obrázek 2: NURB křivka druhého stupně spolu s navzájem pospojovanými řídícími body

Prakticky nejpoužívanějšími NURB křivkami jsou však kubiky, tj. křivky či části křivek třetího stupně (degree=3order=4). U těchto křivek je parametr t mocněn na třetí a v případě korektně zadaného uzlového vektoru lze zajistit spojitost třídy C2 iG2. Také je možné vytvářet kuželosečky, zejména kružnice a kruhové oblouky. Příklad NURB křivky třetího stupně je zobrazen na třetím ilustračním obrázku.

NURB křivka třetího stupně spolu s navzájem pospojovanými řídícími body
Obrázek 3: NURB křivka třetího stupně spolu s navzájem pospojovanými řídícími body

3. NURB křivky vyšších stupňů

Ukázky NURB křivek vyšších stupňů jsou zobrazeny na dalších třech obrázcích:

NURB křivka čtvrtého stupně
Obrázek 4: NURB křivka čtvrtého stupně

NURB křivka pátého stupně
Obrázek 5: NURB křivka pátého stupně

NURB křivka šestého stupně
Obrázek 6: NURB křivka šestého stupně

4. Význam uzlového vektoru

Hodnoty složek uzlového vektoru mají na tvar NURB křivky stejně významný vliv jako pozice jejich řídících bodů. Počet složek uzlového vektoru je závislý jak na počtu řídících bodů, tak i na stupni křivky. Současně se na hodnoty složek uzlového vektoru kladou další omezující podmínky:

  1. Počet hodnot uložených v uzlovém vektoru je roven součtu řídících bodů a stupně křivky zvětšeného o jedničku.
  2. Posloupnost hodnot musí být neklesající, tj. ti<=ti+1.
  3. Počet stejných hodnot (ty musí být dle předchozího bodu sousední) musí být menší nebo roven stupni křivky.

Z rekurentního vztahu pro výpočet NURB křivek dále vyplývá, že hodnoty v uzlovém vektoru nejsou nijak omezeny, většinou se však počítá s rozsahem <0, 1>, což odpovídá i rozsahu parametru t u Bézierových křivek. Pokud jsou hodnoty uvedené na začátku a konci uzlového vektoru konstantní (jejich počet je omezen stupněm křivky), prochází křivka svými krajními body.

Na dalších třech ilustračních obrázcích jsou zobrazeny NURB křivky třetího stupně (kubiky), které se liší pouze hodnotami uloženými v jejich uzlových vektorech.

NURB křivka třetího stupně se změněným uzlovým vektorem
Obrázek 7: NURB křivka třetího stupně se změněným uzlovým vektorem

NURB křivka třetího stupně se změněným uzlovým vektorem
Obrázek 8: NURB křivka třetího stupně se změněným uzlovým vektorem

NURB křivka třetího stupně se změněným uzlovým vektorem
Obrázek 9: NURB křivka třetího stupně se změněným uzlovým vektorem

5. Vykreslování kuželoseček pomocí NURB křivek

Pomocí NURB křivek je možné reprezentovat i kuželosečky. V tomto případě je však nutné specifikovat u každého řídícího bodu i jeho váhu. To se provede tak, že se nejprve vytvoří pole řídících bodů, kde každému bodu odpovídají čtyři souřadnice [x, y, z, w]. Příklad:

GLfloat ctlpoints[][4]={
    // x   y   z   w
    { 50, 400, 0, 1.0},
    {225,  97, 0, 0.5},
    {400, 400, 0, 1.0}
};

Dále je nutné při volání funkce gluNurbsCurve() správně specifikovat parametry, zejména parametr stride, který bude nastaven na hodnotu 4, a parametr type, který by měl být nastaven na hodnotu GL_MAP1_VERTEX4. Nyní čtvrtá souřadnice řídících bodů odpovídá jejich váze.

Jak se však kuželosečky vytvoří? Ukážeme si nejjednodušší případ – tvorbu kruhového oblouku, který je speciálním případem oblouku eliptického. Při tvorbě kruhového oblouku nám stačí zadat pouze tři řídící body, a vytvořit tak NURB křivku druhého stupně. Řídící body by měly být v prostoru rozmístěny tak, aby tvořily vrcholy rovnoramenného trojúhelníka. Uzlový vektor bude mít hodnoty jednotlivých složek nastaveny následovně:

[0,0,0,1,1,1]

To, o jaký typ oblouku se jedná, se specifikuje vahou řídících bodů. První a poslední řídící bod má váhu jednotkovou, tj. w0=w2=1, liší se pouze váha prostředního bodu. Pro kruhový oblouk musí platit w1=cos a, kde a je úhel, který svírají úsečky spojující prostřední řídící bod s body krajními. Celou kružnici je možné vytvořit spojením třech nebo čtyřech kruhových oblouků – tyto lze dokonce vytvořit v rámci jedné NURB křivky, pouze se musí dát pozor na korektní nastavení hodnot uzlového vektoru.

6. Demonstrační příklady

Po spuštění prvního demonstračního příkladu se zobrazí NURB křivka prvního stupně spolu se svými řídícími body. Těmito body je možné pomocí myši pohybovat, a měnit tak výsledný tvar křivky.

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

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

Druhý demonstrační příklad je podobný příkladu předchozímu s tím rozdílem, že se zobrazí NURB křivka druhého stupně, tj. kvadrika.

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

Screenshot druhého demonstračního příkladu
Obrázek 11: Screenshot druhého demonstračního příkladu

Třetí demonstrační příklad navazuje na oba příklady předchozí. Rozdíl spočívá pouze v tom, že se zobrazí NURB křivka třetího stupně, tj. kubika.

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

Screenshot třetího demonstračního příkladu
Obrázek 12: Screenshot třetího demonstračního příkladu

bitcoin školení listopad 24

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

Další pokračování tohoto seriálu bude věnováno NURB plochám, které vznikají poměrně jednoduchým a přímočarým rozšířením NURB křivek.

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

gluNewNurbsRen­derer()
gluDeleteNurbsRen­derer()
gluNurbsProperty()
gluNurbsCallback()
gluNurbsCurve()
gluBeginCurve()
gluEndCurve()
 

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.