Open Inventor: Knihovna pro realtimovou 3D grafiku

15. 8. 2003
Doba čtení: 8 minut

Sdílet

Open Inventor je velmi populární knihovna pro tvorbu realtimové 3D grafiky, tedy i her. Programátorovi poskytuje rozsáhlou množinu C++ tříd, které skrývají před programátorem vlastní OpenGL API a posunují ho na mnohem vyšší úroveň. Tak může programátor mnohem rychleji vyvinout to, co potřebuje. Navíc, aplikace napsané v Open Inventoru jsou obyčejně rychlejší než ty přímo psané v OpenGL, jak bude zmíněno v článku.

Toto je první ze série článků, ve kterých si postupně ukážeme, jak vytvořit jednoduchou aplikaci typu „Hello world!“, od které přejdeme k dalším, složitějším příkladům, na jejichž základě by snad každý měl být schopen naprogramovat si vlastní aplikaci či hru. Tutoriál předpokádá pouze znalost jazyka C++, přičemž znalost základů OpenGL je velikou výhodou. (V tuto dobu vychází na rootu seriál o OpenGL.)

Abychom pochopili něco z kultu Open Inventoru, bylo by vhodné uvést něco z historie. Ta je úzce spjata s firmou SGI – kdysi jedničkou v oblasti počítačové grafiky, známou také jako Silicon Graphics. První release Open Inventoru byl již v dávných dobách, kdy kraloval procesor 486, dokonce už rok před tím, než byla vypuštěna první specifikace OpenGL. Byl to rok 1991. Poté následoval intenzivní vývoj po sedm let, během kterých se Open Inventor stal standardem v oblasti profesionální 3D grafiky. V roce 1998 byl vývoj ukončen a SGI se soustředila již jen na vývoj svého OpenGL Performeru, který je opět podivuhodným dílem a dle své architektury bratrem Open Inventoru. Avšak není to free software a neslyšel jsem zatím ani o žádném free-klonu, který by se rodil v komunitě svobodného software, proto se mu nebudeme nadále věnovat.

V současné době jsou k dispozici tři významnější balíky kompatibilní s Open Inventor API. První, komerční, je Open Inventor firmy TGS, která od SGI odkoupila práva na další vývoj jejich kódu. Druhá verze je přímo od SGI. Ta v roce 2000 zveřejnila svou poslední verzi Open Inventoru 2.1 jako open-source a světu tím udělala velkou radost. Nicméně kvůli absenci dalšího vývoje již tento balík poměrně zastaral. Poslední balík pochází od norské firmy Systems In Motion, ta vyvinula vlastní knihovnu Coin, která je kompatibilní s Open Inventor API a je k dispozici pod GPL licencí (starší verze 1, kterou jsem dlouho používal, dokonce pod LGPL). Právě této poslední knihovny se budeme držet v našem tutoriálu.

Domnívám se, že by bylo nečestné nezmínit i konkurenční balíky k balíkům Open Inventor rodiny: Nejznámější zřejmě bude Direct3D retained mode od Microsoftu, které však již zřejmě zmizelo v propadlišti softwarových dějin. Dalším produktem je OpenGL Performer, o kterém již byla řeč a který má své pevné pozice v profesionální oblasti. V komunitě svobodného softwaru vzniklo velké množství projektů, avšak jen málo z nich dosáhlo vyšších kvalit. Ze jmen, která se donesla mým uším, by se za oblast tvorby her dal jmenovat Crystal Space a za oblast obecných knihoven, většinou s velmi podobným designem jako Open Inventor, by se daly zmínit OpenSceneGraph a OpenRM. Gizmo3Dnení sice open-source, ale přesto je poměrně známé v kruzích virtuální reality. Význam mnoha z těchto knihoven však poklesl ve chvíli, kdy se Open Inventor stal free-softwarem.

Nyní něco blíže k designu Open Inventoru. Je to knihovna napsaná v C++ a postavená nad OpenGL, která posunuje programátora od primitivního OpenGL rozhraní na vyšší úroveň a nabízí mu rozsáhlou množinu C++ tříd. Ta podstatně zjednodušuje práci programátora a dokonce často poskytuje vyšší výkon než přímá implementace v OpenGL. Vyšší výkon je možný díky jistým optimalizacím, které Open Inventor může provádět nad daty scény. Běžný programátor také obyčejně nemá čas provádět profilování a optimalizaci renderovacích algoritmů. Proto již vyprofilované rutiny Inventoru nejsou špatnou volbou.

Design Open Inventoru vychází z konceptu grafu scény. Tedy, scéna je složena z uzlů – anglicky nodes. Nody jsou různých typů. Jedny nesou informace o geometrii těles (krychle, kužel, model tělesa), další různé atributy (barva, textury, souřadnice objektu) a také existují speciální nody, které obsahují seznam jiných nodů, anglicky zvané groups. A právě tyto groupy umožňují organizovat ostatní nody do hierarchických struktur zvaných grafy. Takovýto graf nám pak reprezentuje naši scénu. Celou problematiku si můžeme hned ukázat na prvním příkladu.

Příklad 1.1: Ahoj kuželi! (anglicky: Hello Cone!)

V tomto příkladu vytvoříme minimální aplikaci zobrazující červený kužel osvětlený jedním světlem. Graf scény, který budeme vytvářet, vypadá takto:

Hello Cone Graph


Kořen grafu tvoří objekt typu SoSeparator. Předpona So (=Scene Object) se používá pro zabránění kolizím jmen ve vašem projektu s názvy tříd v Open Inventoru. SoSeparator je třída odvozená ze SoGroup, tedy základní třídy udržující seznam jiných nodů. Třídu SoSeparator budeme používat místo SoGroup téměř vždy pro její speciální vlastnosti. Například proto, že dokáže scénu pod sebou předkompilovat do OpenGL display listu a tím urychlit proces renderování. Když se podíváme pod separátor, zjistíme, že má čtyři syny: kamera, světlo, materiál a kužel. První z nich – kamera – je speciální nod, který určuje umístění pozorovatele a některé další atributy pohledu do scény. Světlo (SoLight) osvěcuje scénu bílým světlem. Následující nod, tedy materiál, udává optické vlastnosti kužele, jednoduše řečeno – udává jeho barvu. Posledním nodem je pak vlastní kužel, což je nod specifikující geometrii tělesa.

Nyní si můžeme ukázat kód pro vytvoření takového grafu scény.

Nejprve vytvoříme kořen scény (typu SoSeparator) a „zareferencuje­me“ jej:

SoSeparator *root = new SoSeparator;
root->ref();

Poté vytvoříme objekt kamery a přidáme ho jako prvního syna rootu:

SoPerspectiveCamera *camera = new SoPerspectiveCamera;
root->addChild(camera);

Pak vložíme světlo – barva světla je standardně bílá:

root->addChild(new SoDirectionalLight);

Třetí syn je typu SoMaterial. V něm nastavíme difúzní barvu na červenou. To při renderování scény nastaví aktuální barvu na červenou a bude vykreslen kužel v červené barvě. Složky barvy jsou udávány v pořadí červená, zelená, modrá. Jedničková hodnota značí plnou intenzitu, nulová absenci dané barevné složky.

SoMaterial *material = new SoMaterial;
material->diffuseColor.setValue(1.0f, 0.0f, 0.0f);
root->addChild(material);

Na závěr vložíme samotný kužel:

root->addChild(new SoCone);

Tím máme graf scény vytvořen. Jako další krok musíme vytvořit okno, ve kterém bychom mohli scénu zobrazovat. K tomu používáme knihovnu SoQt, která je k dispozici spolu s Coin. Alternativně můžeme použít knihovny SoWin (pouze Windows), SoXt (všude, kde najdeme X) nebo například SoGtk.

V prvé řadě musíme provést inicializaci SoQt, a to hned na začátku programu. Inicializaci Open Inventoru provádět nemusíme, neboť to SoQt::init automaticky provede za nás. Pokud inicializace neuspěje, je vráceno NULL a aplikaci ukončíme.

QWidget *window = SoQt::init(argv[0]);
if (window == NULL) exit(1);

Po inicializaci SoQt následuje kód pro vytvoření grafu scény, který jsme si před chvílí uvedli. Po něm zbývá jen nastavit okenní systém a spustit renderovací smyčku. V prvé řadě vytvoříme objekt typu SoQtRenderArea, což je okno, do kterého budeme renderovat naši scénu:

SoQtRenderArea *renderArea = new SoQtRenderArea(window);

Poté nastavíme parametry kamery tak, aby šla vidět celá scéna. První parametr funkce viewAll je kořen grafu scény, který má být vidět, druhý je rozměr renderovacího okna:

camera->viewAll(root, renderArea->getViewportRegion());

Pak nastavíme graf scény, který budeme zobrazovat v okně, a titulek okna. Funkcí show označíme okno jako viditelné:

renderArea->setSceneGraph(root);
renderArea->setTitle("Hello Cone");
renderArea->show();

Na závěr zobrazíme všechny okna vytvořená s parametrem window voláním SoWin::show a spustíme hlavní renderovací smyčku:

SoQt::show(window);
SoQt::mainLoop();

Zbylý kód, jak možná mnozí tuší, se nikdy neprovede, protože funkce mainLoop se nikdy nevrací. Proč? Je to široce rozšířený designerský koncept v Linux/unixovém světě (viz třeba i glutMainLoop z knihovny GLUT), kdy aplikace pracuje až do zavolání funkce exit nebo přijetí signálu, který ji ukončí. Poté uvolní všechnu paměť i zdroje operační systém – finalizace aplikace tedy není potřebná. Všimněme si, že tato idea nemá silnou odezvu na Windows, neboť záruka, že všechny zdroje budou uvolněny, zde nebyla příliš silná ;-). Tedy ještě za Win98 to alespoň u mě platilo.

I na Linux/unixech občas potřebujeme dealokaci provádět, například pro hledání děr v paměti (memory leaks). Jsme nuceni buď využívat funkce atexit, nebo vývojáři Coinu přidali funkci mainLoopWithExit, která sice není standardní v inventorovském světě (SGI ani TGS ji nemají, u TGS si nejsem jistý u posledních verzí). Pokud však neplánujeme být Coinu nevěrní, můžete ji směle používat.

Zde je tedy kód pro dealokaci paměti:

delete renderArea;
root->unref();

A kompletní kód příkladu HelloCone.cpp:

//
// Příklad 1.1 - Hello Cone
//


#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/SoQtRenderArea.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoSeparator.h>


int main(int , char **argv)
{
// Inicializuj Inventor
QWidget *window = SoQt::init(argv[0]);
if (window == NULL) exit(1);

// Vytvoř kořen scény
SoSeparator *root = new SoSeparator; root->ref();

// Vlož kameru do scény
SoPerspectiveCamera *camera = new SoPerspectiveCamera;
root->addChild(camera);

// Světlo
root->addChild(new SoDirectionalLight);

// Materiál
SoMaterial *material = new SoMaterial;
material->diffuseColor.setValue(1.0, 0.0, 0.0);
root->addChild(material);

// Kužel
root->addChild(new SoCone);

// Vytvoř renderovací okno
SoQtRenderArea *renderArea = new SoQtRenderArea(window);

// Nastav parametry kamery
camera->viewAll(root, renderArea->getViewportRegion());

// Nastav renderovací okno
renderArea->setSceneGraph(root);
renderArea->setTitle("Hello Cone");
renderArea->show();

// Zobraz okna a rozjeď renderovací smyčku
SoQt::show(window);
SoQt::mainLoop();

// Uvolni paměť
delete renderArea;
root->unref();

return 0;
}

Obrázek, který bychom měli získat, až vše nainstalujeme, zkompilujeme a spustíme, by měl být podobný následujícímu:

Hello Cone Shot

bitcoin_skoleni

Celý příklad je k dispozici včetně makefile. Pro platformu Windows můžete použít speciální verzi s projektovými soubory pro Visual C++ 6, která nepoužívá knihovnu SoQt, nýbrž SoWin. SoWin je pro nás výhodnější, protože nepotřebuje žádné další knihovny (SoQt vyžaduje Qt knihovnu – asi 12 MB).

Toť pro dnešek vše. Příště se dozvíme, jak úspěšně nainstalovat Coin a všechny potřebné knihovny, a pak navážeme dalšími příklady, které nás uvedou do toho, co vše nám nabízí rozsáhlá knihovna Open Inventor.

Odkazy:

Seriál: Open Inventor

Autor článku