V tomto článku se dozvíte, jakým způsobem OpenGL ukládá hodnoty rotací a translací do své modelview matice. Samozřejmě nebudou chybět obrázky jejího obsahu po různých maticových operacích.
Předem chci upozornit, že všechno vysvětlím tak, jak tomu rozumím já, tedy neodborně. Doufám, že dané problematice půjde z mého výkladu snadno porozumět. Také neručím za to, že to přesně takto má být, protože jsem si daný postup vymyslel (= odvodil z něčeho úplně jiného). Ale funguje, tak co :-).
Začneme teorií. Vysvětlím, kde je co v modelview matici umístěno a pak se pokusím vysvětlit, jak vše dohromady funguje. Jistě víte, že v OpenGL má matice velikost 4x4. Po složitém výpočtu tedy získáme celkem 16 indexů pole. Ale pozor, matice není uložena jako dvourozměrná, nýbrž jednorozměrně. V programu potřebujeme vytvořit pole o 16 prvcích buď typu float nebo double. Osobně používám float, protože má dostatečnou přesnost, práce s ním je rychlejší a zabírá méně paměti.
float Matrix[16];// OpenGL matice
Na následujícím obrázku je znázorněno rozmístění indexů do 1D pole ve 2D matici, kterou si pro zpřehlednění představujeme.
Teď si ukážeme, co se na který index ukládá a k čemu slouží. Hodnoty Move označují posun objektu v osách X, Y, Z a Rot čísla definují rotace, kde je pro každou původní osu (velké písmeno X, Y, Z) poměr jejího "přenesení" na osy jiné.
Myslím si, že jak zacházet s posunem je každému jasné, ale rotace je alespoň na první pochopení složitá. Představme si, že velká písmena X, Y, Z definují původní osu a malá písmena x, y, z označují osu, na kterou se ta původní přetransformuje. Pro začátek si ukážeme, jak vypadá originální matice, která zobrazuje objekt nenatočený, neposunutý a ani nezmenšený. Když s ní násobíme bod, je to, jako bychom ho násobili jedničkou. Jeho souřadnice se tedy nezmění. Tuto matici v OpenGL generuje funkce glLoadIdentity().
Všimněte si, že ve všech Rot jsou jedničky na hlavní diagonále matice. Tedy tam, kde se shoduje malé a velké písmeno - Xx, Yy, Zz. Ve výsledném zobrazení budou mít osy stejné měřítko, polohu i natočení jako má absolutní soustava souřadnic.
glLoadIdentity();// Reset matice
Teď si ukážeme, co se stane, kdybychom chtěli objekt otočit o 90° na ose Y.
glLoadIdentity();
glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
Tak už to bývá na světě, že viník uniká bez trestu a všichni okolo to schytají. Ano, vidíte dobře, s osou Y se nic nestalo, protože jsme se okolo ní otočili o 90°. Když si to představíme v prostoru: co se stane s bodem na ose x, pokud před vykreslením provedeme rotaci okolo osy Y o 90°? Z osy X se přesune na osu Z. Když se podíváte, původní osa X (velké písmeno) se dostala na osu z (malé písmeno). To samé se stalo s původní osou Z která se dostala na zápornou část osy x. Pokud bychom rotovali opačným směrem, tedy o -90° na ose Y, v RotXz by byla hodnota -1 a v RotZx by bylo 1, což je přesný opak předchozího případu.
Pro ty z vás, kteří si nedokáží představit, jak by se mohla jedna osa přenést na jinou, si ukážeme obrázek, na kterém jde všechno bez problémů vidět.
Už chápete, jak se dostal bod z jedné osy na druhou? Stejně to funguje i s maticemi. Každou ze tří os si můžeme představit jako skupinu bodů v prostoru, které otáčíme o nějaký úhel. Přesně takto jsem se dopátral k funkčnímu kódu.
Je samozřejmě jasné, že se vše neotáčí jen o 90°, ale musel jsem to nějak vysvětlit. Jen pro ukázku se podívejme, co se stane pokud rotujeme okolo osy Y o 45°.
glLoadIdentity();
glRotatef(45.0f, 0.0f, 1.0f, 0.0f);
Asi vás některé napadlo, že když je úhel poloviční než předtím, proč na indexech nejsou hodnoty 0,5 namísto 0,707. Je to proto, že výpočet těchto hodnot provádíme pomocí goniometrických funkcí sin a cos a ty, jak víme, nejsou lineární. Pokud by byly, docházelo by k deformacím obrazu, zmenšování atd... Představte si kružnici vykreslenou pomocí sin a cos. Kdyby byly tyto funkce lineární, vznikl by čtverec. Otáčíme-li body v rovině, pohybujeme se po kružnici, ve 3D prostoru po kouli.
Nesmíme zapomenout na změnu měřítka. Následující obrázek ukazuje, jak bude matice vypadat po jednonásobném zvětšení na ose x (zůstává stejná), dvojnásobném na ose y a trojnásobném na ose z. V OpenGL by se to provedlo voláním funkce glScalef().
glLoadIdentity();
glScalef(1.0f, 2.0f, 3.0f);
Poslední příklad ukazuje současnou změnu měřítka a rotaci na ose y o 45°.
glLoadIdentity();
glScalef(1.0f, 2.0f, 3.0f);
glRotatef(45.0f, 0.0f, 1.0f, 0.0f);
To je snad vše o teorii a teď honem ke zdrojovým kódům - slíbené funkce pro rotaci. Musím jen dodat, že nejsou shodné s OpenGL glRotatef(), ve které se objekt po posunutí otáčí podle středu scény, transformuje tedy i svou pozici. Tyto funkce to nedělají a myslím, že je to tak lepší (záleží na zvyku). Pokud potřebujete přetransformovat i pozici (například když je připevněn k jinému objektu ve scéně), jednoduše si vypočítáte rotaci a pak pozici vynásobíte maticí stejně jako normální bod v prostoru.
void RV6_MATRIX::RotateX(float Angle)// Rotace na ose x
{
float p;
float _sin = sinf(-Angle);
float _cos = cosf(-Angle);
// Osa X na X
p = Matrix[4];
Matrix[4] = p * _cos - Matrix[8] * _sin;
Matrix[8] = p * _sin + Matrix[8] * _cos;
// Osa X na Y
p = Matrix[5];
Matrix[5] = p * _cos - Matrix[9] * _sin;
Matrix[9] = p * _sin + Matrix[9] * _cos;
// Osa X na Z
p = Matrix[6];
Matrix[6] = p * _cos - Matrix[10] * _sin;
Matrix[10] = p * _sin + Matrix[10] * _cos;
}
void RV6_MATRIX::RotateY(float Angle)// Rotace na ose y
{
float p, _sin = sinf(Angle), _cos = cosf(Angle);
// Osa Y na X
p = Matrix[0];
Matrix[0] = p * _cos - Matrix[8] * _sin;
Matrix[8] = p * _sin + Matrix[8] * _cos;
// Osa Y na Y
p = Matrix[1];
Matrix[1] = p * _cos - Matrix[9] * _sin;
Matrix[9] = p * _sin + Matrix[9] * _cos;
// Osa Y na Z
p = Matrix[2];
Matrix[2] = p * _cos - Matrix[10] * _sin;
Matrix[10] = p * _sin + Matrix[10] * _cos;
}
void RV6_MATRIX::RotateZ(float Angle)// Rotace na ose z
{
float p, _sin = sinf(-Angle), _cos = cosf(-Angle);
// Osa Z na X
p = Matrix[0];
Matrix[0] = p * _cos - Matrix[4] * _sin;
Matrix[4] = p * _sin + Matrix[4] * _cos;
// Osa Z na X
p = Matrix[1];
Matrix[1] = p * _cos - Matrix[5] * _sin;
Matrix[5] = p * _sin + Matrix[5] * _cos;
// Osa Z na X
p = Matrix[2];
Matrix[2] = p * _cos - Matrix[6] * _sin;
Matrix[6] = p * _sin + Matrix[6] * _cos;
}
void RV6_MATRIX::Rotate(float x, float y, float z)// Hlavní funkce pro rotaci
{
if(x)
{
RotateX(x);
}
if(y)
{
RotateY(y);
}
if(z)
{
RotateZ(z);
}
}
To, co vidíte jsou probdělé noci, vyplakané litry slz, pětky ve škole... Jak vidíte je to úryvek ze třídy, která obsahuje jen pole o 16 prvcích se jménem Matrix. Toto mučení by bylo na nic, kdyby se nedalo nějak použít. Proto zde uvedu další funkci, která podle matice přetransformuje vertex či vektor (u vektoru je ale lepší vytvořit funkci, ve které se nebudou přičítat pozice) na absolutní souřadnice. Jen pro pořádek, třída RV6_VECTOR3 obsahuje tři čísla x, y, z.
void RV6_MATRIX::TransformVertex(RV6_VECTOR3 *Vertex)// Transformuje vertex podle matice
{
RV6_VECTOR3 New;
New.x = Vertex->x * Matrix[0] + Vertex->y * Matrix[4] + Vertex->z * Matrix[8] + Matrix[12];
New.y = Vertex->x * Matrix[1] + Vertex->y * Matrix[5] + Vertex->z * Matrix[9] + Matrix[13];
New.z = Vertex->x * Matrix[2] + Vertex->y * Matrix[6] + Vertex->z * Matrix[10] + Matrix[14];
*Vertex = New;
}
Další důležitou věcí je nahrání naší matice do OpenGL. K tomu slouží standardní funkce glLoadMatrixf(). Poslední písmeno v jejím názvu znamená float, pokud požíváte double musíte jej zaměnit za d.
glLoadMatrixf(Matrix);// Uploadování matice do OpenGL
Pokud chcete naopak načíst matici, použijte funkci glGetFloatv(). První parametr označuje, kterou matici žádáme (GL_MODELVIEW_MATRIX, GL_PROJECTION_MATRIX nebo GL_TEXTURE_MATRIX) a druhý pole, kam se mají data uložit.
glGetFloatv(GL_MODELVIEW_MATRIX, Matrix);// Získání OpenGL matice
Když si to trochu shrneme... nikdy nezapomeňte před první operací uvést matici do základního stavu, kdy jsou na hlavní diagonále jedničky a všude jinde nuly, jinak by se vám nic nezobrazovalo a ani nerotovalo. Dále chcete asi vědět k čemu slouží poslední sloupec v matici. Přiznám se, že nevím, ale prostě tam je. Ve škole jsme matice ještě nebrali, vše jsem zkoumal grabováním hodnot z OpenGL a jejich výpisem do souboru.
To je snad vše k maticím, jestli jim ještě nechápete, zkuste si tento článek přečíst ještě jednou (pozor na nekonečný cyklus :-) nebo donuťte někoho ať vám vysvětlí co a jak. Já už asi lépe vysvětlovat nedokáži. Doufám, že jsem alespoň někomu pomohl.
napsal: Radomír Vrána <rvalien (zavináč) c-box.cz?subject=Článek - Matice>
Tento článek byl napsán pro web http://nehe.ceske-hry.cz/. Pokud ho chcete umístit i na svoje stránky, napřed se zeptejte autora, je to slušnost.