Dnes se podíváme, jak pracovat s transformačními maticemi v praxi.
Začneme nejdříve tím, že si popíšeme a vysvětlíme, co všechno se dá v OGL s maticemi provádět. Něco už jsem zmiňoval minule, ale radši ještě jednou a popořádku.
Je vždy důležité mít jistotu, se kterou maticí právě manipuluji. Jak bylo řečeno předminule, OGL je stavový automat a to, která matice je právě aktivní pro úpravu, je také jeden ze stavů. Přepíná se pomocí funkce glMatrixMode(), která může mít následující hodnoty.
- GL_MODELVIEW (defaultní stav)
- GL_PROJECTION
- GL_TEXTURE
- GL_COLOR
Už není třeba ji tady více rozebírat, jelikož to, co bylo třeba, bylo řečeno minule. Stejně tak už jsem se minule zmínil o tom, jak nastavit matici na jednotkovou, tedy takovou, která „nic nedělá“. Je to pomocí funkce glLoadIdentity().
Ta nastaví matici na následující hodnoty.
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
Co když ovšem nechceme nastavit matici jednotkovou, ale nějakou úplně jinou, a to ne tak, že bychom násobili stávající, ale prostě nahráli novou? Od toho je tu f-ce:
void glLoadMatrixd(const GLdouble * m);
void glLoadMatrixf(const GLfloat * m);
Ta má za parametr ukazatel na pole 4×4, a to buď pole float nebo double podle toho, jaký konkrétní tvar použijeme.
To se může hodit v případě, že máte nějakou matici transformací, kterou jste si připravili bokem. Pokud jste dobří v matematice, tak si ty matice můžete v zásadě složit sami, ale s tím já vám moc nepomůžu :). Jednodušší způsob je si tu matici transformací někde zvlášť vytvořit pomocí OGL funkcí a pak uložit do pole o 16 prvcích. K tomu potřebujeme funkci GlGet(). Ta má vždy 2 parametry; první říká, jakou informaci chceme z OGL vytáhnout, a druhý, jak ji chceme uložit. Dá se pomocí ní zjisti v zásadě úplně všechno, co člověk potřebuje. My se tady ale zabýváme pouze případem, kdy chceme zjistit hodnoty dané matice a uložit je. Funkce má opět více způsobů zápisu, a to podle konkrétní proměnné, do které ukládáme, pro nás budou relevantní tyto dva.
glGetDoublev(GLenum pname, GLdouble * params);
glGetFloatv(GLenum pname, GLfloat * params);
První parametr se liší podle toho, jakou matici chceme ukládat (tady tedy nezáleží na nastaveném stavu). Má hodnoty:
- GL_MODELVIEW_MATRIX
- GL_PROJECTION_MATRIX
- GL_TEXTURE_MATRIX
- GL_COLOR_MATRIX
Možná o něco užitečnější, než nahrávat celou novou matici, je násobit stávající (něco takového se v podstatě děje např. při volání f-ce glRotate), to zařizuje tato funkce:
void glMultMatrixd(const GLdouble * m);
void glMultMatrixf(const GLfloat * m);
Parametrem jí je předán ukazatel na 16 místní pole, v němž je uložena matice. Opět záleží na zápisu podle toho, jaký typ proměnné používáme. Tuto f-ci můžeme použít nejen pro vykonávání transformací, ale i třeba v případě, že si jen tak pro sebe potřebujeme vynásobit nějaké matice. Např. takto:
void nasobMatice() {
glPushMatrix();
glLoadMatrixf(matice1);
glMultMatrixf(matice2);
glGetFloatV(GL_MODELVIEW_MATRIX, vysledek);
glPopMatrix();
}
Tedy, nejprve uložíme aktuální stav, abychom se k němu mohli vrátit později. Načteme první matici, vynásobíme ji druhou maticí, uložíme a vrátíme se k původnímu stavu.
Ještě je zde jedna užitečná funkce. Ta se používá k nastavení kamery. To se samozřejmě dá udělat rotací a transformací (nejde opět o nic jiného, než o vynásobení modelview matice (dále MV) nějakou novou maticí). F-ce gluLookAt() je ale docela jednoduchá na použití a intuitivní (bohužel ne pro všechny typy kamery).
void gluLookAt( GLdouble eyeX, GLdouble eyeY, GLdouble eyeZ, GLdouble centerX, GLdouble centerY, GLdouble centerZ, GLdouble upX, GLdouble upY, GLdouble upZ);
F-ce přijímá za parametry jak proměnné typu double, tak i float. První tři jsou souřadnicemi bodu ve světě (tj. ve world space), ve kterém se kamera nachází, další tři určují bod (opět ve světě), na který se kamera dívá, a poslední tři pak definují vektor, který říká, kde je nahoře.
Podívejme se teď na trochu (dost) předělaný kód z minulé lekce. Přibude nám jedna funkce, která bude nahrávat a ukládat matice.
void vytvorMatice() {
glPushMatrix(); //uloží se MV
//kamera
glLoadIdentity(); // vynuluje se
gluLookAt(0.0,0.0,5.0,0.0,0.0,-10.0,0.0,0.1,0.0); // nastaví se kamera
glGetFloatv(GL_MODELVIEW_MATRIX, MaticeKamera); // uloží se matice kamery
glLoadIdentity();
//krychle 1
if(fps>0.0) { //ošetříme dělení nulou
uhel+=speed/fps;
}
glTranslatef(-1.5, 0.0, 0.0); //provedou se transformace pro první krychli
glRotatef(uhel,1.0,1.0,1.0);
glGetFloatv(GL_MODELVIEW_MATRIX, MaticeKrychle1); // uloží se matice
glLoadIdentity(); // vynuluje se
//krychle 2
glTranslatef(0.0, 0.0,0.0); //provedou se transformace pro druhou krychli
glRotatef(uhel,1.0,1.0,1.0);
glGetFloatv(GL_MODELVIEW_MATRIX, MaticeKrychle2); // uloží se matice
glLoadIdentity(); // vynuluje se
//krychle 3
glTranslatef(1.5, 0.0,0.0); //provedou se transformace pro třetí krychli
glGetFloatv(GL_MODELVIEW_MATRIX, MaticeKrychle3);// uloží se matice
glPopMatrix(); //vrátíme se zpět původní MV matici
}
Nejdříve uložíme daný stav modelview matice, abychom se k ní mohli na konci vrátit. Pak vynulujeme MV matici a nastavíme kameru pomocí funkce gluLookAt(). Říkáme jí, že kamera je umístěna v bodě (0,0,5), míří ve směru vektoru (0,0,-10). Výslednou matici kamery uložíme do pole MaticeKamera.
Pak opět vynulujeme (je to jako vygumovat si papír před tím, než začneme psát znova něco jiného). Nastavíme první krychli, uložíme do proměnné MaticeKrychle1. Zase vynulujeme a obdobně vytvoříme a uložíme matici pro druhou krychli. Ještě nám přibyla třetí, takže provedeme to samé i s ní.
Posléze se funkcí glPopMatrix() vrátíme k předchozímu stavu. Nezapomeňte všechny proměnné, se kterými se ve funkci VytvorMatice() pracuje, definovat ještě před ní.
Funkce onDisplay() se také změní.
void onDisplay(void){
timer(); //fce. měřící fps
vytvorMatice();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
glMultMatrixf(MaticeKamera);
glPushMatrix();
glMultMatrixf(MaticeKrychle1);
krychle(0.5);
glPopMatrix();
glPushMatrix();
glMultMatrixf(MaticeKrychle2);
krychle(0.5);
glPopMatrix();
glPushMatrix();
glMultMatrixf(MaticeKrychle3);
krychle(0.5);
glPopMatrix();
glutSwapBuffers();
}
Na začátek nastavíme všechny matice. Posléze, místo toho, abychom prováděli transformace přímo na místě, pouze vynásobíme stávající matici uloženými předpřipravenými maticemi transformací. Opět zde také přibyla třetí krychle. S funkcí onDisplay() už teď v podstatě nemusíme nic dělat, jelikož všechny pohyby budeme řešit při tvoření transformačních matic.
Asi si říkáte „A co z toho…“. Na takto zjednodušeném příkladě to nevypadá moc užitečně. To je sice pravda, ale jakmile máte někde uložený pohyb nějakého objektu, tak ho pak už vždy můžete použít a nemusíte znovu a znovu provádět ty samé transformace. Uložená transformační matice je vlastně proměnná, ve které není jen nějaká číselná hodnota, ale pohyb. Lze s ní pak operovat stejně jako s jakoukoli jinou proměnou. Sčítat, odečítat, násobit, invertovat atd. Také se to hodí z jiných důvodů (jak už jsem se minule zmiňoval o přechodech mezi jednotlivými prostory), ale o tom až jindy, až se budeme zabývat světlem a stíny, případně nějakou základní fyzikou.
Další velice mocnou výhodou toho, že máme uložené jednotlivé matice, je, že můžeme tvořit vztahy a závislosti mezi jednotlivými pohyby. Podívejme se znovu na funkci vytvorMatice().
void vytvorMatice() {
glPushMatrix(); //uloží se MV
//kamera
glLoadIdentity(); // vynuluje se
gluLookAt(0.0,0.0,5.0,0.0,0.0,-10.0,0.0,0.1,0.0); // nastaví se kamera
glGetFloatv(GL_MODELVIEW_MATRIX, MaticeKamera); // uloží se matice kamery
glLoadIdentity();
//krychle 1
glTranslatef(-1.5, 0.0, 0.0); //provedou se transformace pro první krychli
glRotatef(uhel,1.0,1.0,1.0);
glGetFloatv(GL_MODELVIEW_MATRIX, MaticeKrychle1); // uloží se matice
glLoadIdentity(); // vynuluje se
//krychle 2
if(fps>0.0) { //ošetříme dělení nulou
uhel+=speed/fps;
}
glMultMatrixf(MaticeKrychle1);
glTranslatef(1.0, 0.0,0.0); //provedou se transformace pro druhou krychli
glRotatef(uhel,1.0,1.0,1.0);
glGetFloatv(GL_MODELVIEW_MATRIX, MaticeKrychle2); // uloží se matice
glLoadIdentity(); // vynuluje se
//krychle 3
glMultMatrixf(MaticeKrychle2);
glTranslatef(1.0, 0.0,0.0); //provedou se transformace pro třetí krychli
glGetFloatv(GL_MODELVIEW_MATRIX, MaticeKrychle3);// uloží se matice
glPopMatrix(); //vrátíme se zpět původní MV matici
}
Nastavení kamery a první krychle zůstává stejné. Při nastavování druhé krychle nejdříve MV matici vynásobíme maticí té první. Co se stane? Pomyslným souřadným středem už pro ni (pro 2. krychli) není střed světa, ale střed první krychle (převedli jsme souřadný systém druhé krychle do souřadného systému první). Je to jako bychom seděli na krychli jedna, nevnímali, že se hýbe, a jenom bychom viděli druhou krychli visící kousek od ní (vzpomeňte si na vztažné soustavy ve fyzice). Tím, že se souřadný systém první krychle otáčí (vůči světu a tedy i pozorovateli) otáčí se podle něj pro nás i krychle 2. Pozor, posun pro druhou krychli se mění, jelikož ho už nenastavujeme vůči středu světa, ale vůči středu první krychle.
Aby nebylo komplikací málo, uděláme to samé se třetí krychlí. MV matici vynásobíme maticí druhé krychle. Její souřadný systém se tak stane podřazený systému druhé krychle (přebírá tak pohyby její i krychle 1).
Co s tím? Představte si, že děláte tank, ten se hýbe, zároveň se mu hýbe hlaveň a zároveň z hlavně vycházejí projektily. Kdybyste pohyby každého měli řešit zvlášť transformačními funkcemi, tak asi zešílíte :-).
Tak to by bylo protentokrát vše. Příště už se vrhneme na texturování a světla. Problém matic ale ještě zdaleka neopouštíme.