Open Inventor: Jednoduchý model

10. 11. 2004
Doba čtení: 7 minut

Sdílet

Dnešní příklad má ukázat, jak jednoduše budovat modely v Inventoru "ručně" - tedy ve chvílích, kdy potřebujeme vytvořit jednoduchý model a nechceme k tomu používat modelovací software.

Ne každý programátor je spřátelen s modelovacím software, jako je 3D Studio MAX nebo Blender. Proto si mnozí programátoři udělají ty svoje modely často raději sami. Nehledě na to, že mnoho modelů potřebujeme v aplikaci generovat za běhu.

Pro modely složené z trojúhelníků byl v grafice zaveden pojem mesh, který budeme používat i my. Původně tento díl tutoriálu vznikl po dlouhém hledání chyby v jednom inventorovském programu, aby ulehčil od podobných problémů všem ostatním. Vše si představíme na dvou modelech ve třech příkladech:

Tabulka č. 606
4.1 – Jednoduchá raketa vytvořená „v kódu“ Použití pouze SoCoordinate3 a SoIndexedTrian­gleStripSet.
4.2 – Jednoduchá raketa vytvořená „ze stringu“ Jiná forma minulého příkladu.
4.3 – Raketa se vším všudy Do modelu přidáme normály, materiály a textury.

U příkladů nám nepůjde ani tak o vizuální kvalitu jako spíše o jednoduchost a snadné pochopení.

Příklad 4.1: Jednoduchá raketa vytvořená „v kódu“

V tomto příkladě použijeme pouze SoCoordinate3 a SoIndexedTrian­gleStripSet. Více informací o nich je například u skyboxu v předchozích dílech tutoriálu. My se zde soustředíme pouze na konstrukci modelu.

Dalo by se říci, že máme tři možnosti, jak model vytvořit:

  • načtení ze souboru (příklad 2.1 ve třetím dílu tutoriálu)
  • „ruční“ konstrukce
  • konstrukce „ze stringu“

Nejprve si ukážeme ruční konstrukci:

Vytvoříme SoCoordinate3 se vším všudy:

static float vertices[][3] = {
  -2, 0,
4,
   2, 0,
4,

  -2,  1,
2,
   2,  1,

2,
   2, -1,
2,
  -2, -1,

2,

  -2,  1,
-3,
   2,  1,

-3,
   2, -1,
-3,
  -2, -1,

-3,
};

static int32_t indices[] = {
  2, 0,
3, 1, 4, 0, 5, 2, -1,      //predek
  6, 2,
7, 3, 8, 4, 9, 5, 6, 2, -1,//trup
  6, 7,
9, 8, -1,              

   //zadek
};

[...]

SoCoordinate3 *coords = new SoCoordinate3;
coords->point.setValues(0, 30, vertices);
root->addChild(coords);

SoIndexedTriangleStripSet *stripSet =
    new SoIndexedTriangleStripSet;
stripSet->coordIndex.setValues(
    0, 25, indices);
root->addChild(stripSet); 

A máme celou scénu postavenou. O rendrování a podobně se nám už postará Open Inventor:

Zdroják tohoto příkladu je ke stažení zde. Po spuštění by se měl objevit následující obrázek:

Příklad 4.2: Jednoduchá raketa vytvořená „ze stringu“

Druhou možností je konstrukce „ze stringu“, což je obdoba načtení ze souboru, ale místo souboru použijeme statický string v paměti. To je velmi elegantní řešení pro malé grafy. (Upozorňuji maniaky na efektivitu kódu, mezi které se rovněž počítám, že není velkého rozdílu mezi dobou provedení předchozího a tohoto příkladu, a svůj čas je lepší trávit u optimalizace jiných částí kódu než u načítání několika uzlů.)

Zde je odpovídající kód, který ve výsledku dělá přesně totéž co minulý příklad:

static char inlineModel[] =
"#Inventor V2.1 ascii\n"
""
"Separator {"
"  Coordinate3 {"
"    point ["
"      -2 0 4,"
"       2 0 4,"
""
"      -2  1 2,"
"       2  1 2,"
"       2 -1 2,"
"      -2 -1 2,"
""
"      -2  1 -3,"
"       2  1 -3,"
"       2 -1 -3,"
"      -2 -1 -3,"
"    ]"
"  }"
"  IndexedTriangleStripSet {"
"    coordIndex ["
"      2, 0, 3, 1, 4, 0, 5, 2, -1,"
"      6, 2, 7, 3, 8, 4, 9, 5, 6, 2, -1,"
"      6, 7, 9, 8, -1,"
"    ]"
"  }"
"}";

[...]
// Precti scenu ze stringu
SoInput in;
in.setBuffer(inlineModel,
             strlen(inlineModel));

SoSeparator *root = SoDB::readAll(&in);
if (root == NULL) exit(-1);
root->ref(); 

Kód je ke stažení zde.

Jak vidíme, tento kód může být často čitelnější, a je na volbě programátora, který přístup zvolí. Tento přístup se hodí spíše pro malé modely se složitou grafovou strukturou, kdežto předchozí pro modely s mnoha vertexy a velikými statickými poli.

„File formát“ použitý ve stringu je inventorovský a poměrně dobře čitelný. S ním je kompatibilní i VRML1, které je podmnožinou inventorovského formátu. Pro experty jsou zde linky na web3d konsorcium a jejich stránky věnované VRML1 uzlům.

Poznámka ke špatnému osvětlení modelu: Je způsobeno tím, že jsme nespecifikovali normály v našem modelu. Inventor umí normály dopočítat, ale generovací algoritmus, ač nemá chybu, to neudělá vždy podle našich představ. Více informací k tomuto tématu je možno najít v dokumentaci k SoShapeHints::cre­aseAngle.

Příklad 4.3: Raketa se vším všudy

Tento příklad nás má uvést do plného kontextu rendrování modelů – co vše je možno nastavovat a jak to nastavit. Je třeba upozornit na dva problémy, se kterými se člověk může při této příležitosti potkat a které se jako chyby v kódu velmi špatně hledají. Jsou to:

  1. špatné indexy odkazující se obyčejně za pole hodnot, např. SoIndexedTrian­gleStripSet::co­ordIndex odkazující se až za pole vertexů v SoCoordinate3::po­int
  2. špatně nastavený binding, více k tomuto tématu vzápětí

A ještě jedna otázka se občas objevuje: neosvětlený nebo špatně osvětlený model – více informací na konci příkladu.

Podíváme-li se na problematiku rendrování trojúhelníků v OpenGL obecně, tak nám stačí následující třídy:

Tedy souřadnice vertexů, normály, materiály, textura a texturovací souřadnice.

Samotné rendrování obstarávají nejčastěji tyto třídy:

Pro odborníky bych doplnil, že kompletní seznam tříd najdeme v Coin dokumentaci u třídy SoShape, která je rodičovskou třídou pro všechny rendrovací třídy.

Mimo tyto třídy jsou zde ale ještě „bindovací“ třídy, které nám v případě normál říkají, budeme-li je specifikovat pro každý vertex – tedy tři na trojúhelník – nebo jen jedinou normálu pro celý trojúhelník. Normála pro každý vertex znamená, že je normála mezi vrcholy interpolována, s čímž lze dosáhnou například velmi hladkých povrchů bez viditelných hran.

Má-li tedy model 120 trojúhelníků a pro ně 120 normál a nějakým nedopatřením bychom se přepsali a nastavili binding normál na PER_VERTEX, rendrovací funkce by očekávala, že normál je 360 – pro každý trojúhelník tři. To by mělo za důsledek přistup do paměti za alokovanou paměť pro normály a vše končí buď pádem programu, nebo jen špatně vyrendrovaným modelem. Totéž se stane, pokud zaměníme hodnoty PER_VERTEX a PER_VERTEX_INDEXED, což je ještě častější chyba. Vývojáři Coinu se prozatím rozhodli tyto špatné indexy nekontrolovat, protože se jedná o kritické místo, kudy často teče pár milionů vertexů každou vteřinou.

Zde jsou třídy nastavující binding:

Otázku bindingu probíral příklad 3.5 a 3.6 z minulého dílu našeho tutoriálu. Tam jsou uvedeny detailnější informace, popřípadě ještě v Coin dokumentaci k SoMaterialBin­ding::Binding. My se zde spokojíme s nejčastěji používanými nastaveními.

Jak se dá asi čekat, pro SoTriangleStripSet a SoIndexedTrian­gleStripSet budeme většinou používat různé bindingy pro jejich normály, materiály a texturovací souřednice. Nejčastější použití jsou tato:

Tabulka č. 607
SoTriangle-
StripSet
SoIndexed-
TriangleStripSet
bindování
vertexů
jak jdou za sebou indexovaně
SoNormal-
Binding
PER_VERTEX,
PER_FACE
PER_VERTEX_INDEXED,
PER_FACE_INDEXED
SoMaterial-
Binding
PER_VERTEX,
PER_FACE
PER_VERTEX_INDEXED,
PER_FACE_INDEXED
SoTexture-
Coordinate-
Binding
PER_VERTEX PER_VERTEX_INDEXED

Tato tabulka nás samozřejmě neomezuje v tom, míchat bindingy, jak se nám zachce, jen pak musíme ve správném formátu naplnit nódy SoNormal, SoMaterial a SoTextureCoor­dinate2. Chyby v neodpovídajícím si bindingu, popřípadě chyby v plnění nódů SoNormal, SoMaterial a SoTextureCoor­dinate2 vedou ke špatnému zobrazení modelu a k pádům aplikace. A protože jsou to občas těžko hledatelné chyby, je vhodné tomu věnovat větší pozornost.

Teď už je čas podívat se na náš vylepšený model. Přibyly normály, materiály a jedna textura vytvářející kabinu. Model snad již nemá cenu komentovat a je uložen v souboru, k jehož načtení použijeme SoDB::readAll() funkci.

Zdroják je k dispozici zde a screenshot následuje:

Další detaily jsou shrnuty v bodech:

  • -1 v indexech

     –1 se v poli indexů SoIndexedTrian­gleStripSet::nor­malIndex, materialIndex a textureCoordIndex uvádí pouze v režimu PER_VERTEX_INDEXED.

  • defaultní bindování

    normály: PER_VERTEX_INDEXED
    materiály: OVERALL
    textureCoordinates: PER_VERTEX_INDEXED

  • nespecifinované indexy

    V SoIndexedTri­angleStripSet můžeme nechat normalIndex, materialIndex nebo textureCoordIndex prázdné. Pokud tak učiníme a je pro danou položku specifikován binding PER_VERTEX_INDEXED, použijí se indexy z pole coordIndex. Není tedy potřeba plýtvat pamětí tím, že budeme nastavovat stejné indexy pro souřadnice, normály a texturovací souřadnice, což je častá situace.

Občas se stává, že zobrazený model je černý a jde vidět jen, když ho rendrujeme jako wireframe. Nebo, že když vypneme headlight, tedy světlo u kamery, a umístíme do scény jiné světlo, model je stále černý. Zde je několik tipů na možný problém:

  • Špatně nastavený SoMaterial a jeho difuzní složka, nebo indexy na materiály, nebo barva a intenzita světla.
  • Světlo svítí na model z opačné strany – např. u krajiny světlo svítí zespoda. Zkuste obrátit jeho směr.
  • Normály jsou opačně orientované.

Pro experty jsem vyhledal ještě dodatečné informace:

bitcoin_skoleni

Tabulka č. 608
back face culling odstraňování neviditelných polygonů, nastavuje se skrze nód SoShapeHints
two side lighting oboustranné osvětlování polygonů, viz nód SoShapeHints
různé materiály z obou stran polygonu více k tomuto tématu Coin dokumentace k SoIndexedShapeSet
generování normál Pokud jsme nespecifikovali normály a Inventor je dogenerovává sám, je generovací algoritmus řízen hodnotou SoShapeHints::cre­aseAngle. Pokud dva sousedící polygony svírají úhel menší jak tato hodnota, považuje se hrana za neviditelnou a normály jsou přes ní interpolovány. Naopak hrany s větším úhlem jsou považovány za ty, které mají být vidět.

Tímto jsme vyčerpali dnešní téma. Zbývá link na stažení celého článku.

A to je pro dnešek vše.

Autor článku