/------+ --+ +--+
+-----+/| | | ++
| | | --+ | |
| |/ | | ++
+-----+ --+ +--+
Programování 3D Enginů
Část I
Za dobu existence Výhně bylo v rubrice Programování mnoho
návodů, jak kreslit polygony, jak je vyplňovat texturou, jak dělat
env-mapping, jak rotovat body kolem osy a dalších. To stačí tak k
vytvoření efektů typu rotující brambora, ale žádný trochu
složitější 3D systém tím nevytvoříme. Co by měl umět takový lepší
systém umět? Měl by zvládat kreslení několika objektů a alespoň
jedné kamery a světla. To všechno by se mohlo naprosto volně a
nezávisle na sobě pohybovat prostorem. Také by bylo dobré
umožnit předdefinování drah, po kterých se objekty pohybují, ale
to lze přidat již relativně snadno.
Předpokládám, že čtenář ví, jak v 3D prostoru zapsat pozici
bodu (pomocí 3 souřadnic,) co je to vektor, jak rotovat bod kolem
os x,y a z (viz. článek v některém z prvních čísel Výhně) a byl by
schopen napsat jednoduchý 3d systém, který by uměl alespoň rotovat
množinu více bodů a nakreslit je. Také je téměř nutné vědět, co to
je vektorový součin a skalární součin.
Co obsahuje 3D scéna ?
Jak jsou reprezentovány předměty ?
3D scéna může obsahovat tělesa (objekty) a kameru. Světly ani
ničím jiným se zatím nebudu zabývat. Každý objekt se skládá z
konvexních n-úhelníkových plošek, většinou ale pouze z
trojúhelníků, protože to je pro program mnohem jednodušší. Každý
trojúhelník má samozřejmě tři vrcholy - každý je bodem v naší
soustavě souřadnic.
Jednoduché 3D enginy, často používané v demech hlavně kolem
roku 1995, často mohly obsahovat jen jediný objekt - na obrazovce
rotovalo jedno těleso, po něm další a další, k tomu hrála hudba a
už byl konec. Různá dema navíc často obsahovala naprosto stejné
objekty, většinou vypůjčené z 3D Studia (většinou duck.3ds nebo
chrmface.3ds) Rotace objektů byla spíše čistě náhodná, autorům
prostě stačilo, že se na obrazovce něco hýbe. Tělesa rotovala o
nějaký úhel kolem osy x, o jiný kolem y a z a to bylo všechno.
Když však chceme ovládat například pohyb tělesa jako je
letadlo, pomocí rotací kolem hlavních os dlouho nevystačíme. Dejme
tomu, že letadlo míří ve směru osy x, křída jsou rovnoběžná s osou
y a osa z je na obě kolmá.
.
. x
.
/\
||
.........../----++-----\...................
/-----++------\ y
||
-++-
.
.
.
Nyní chceme, aby se letadlo otočilo doleva (z pohledu pilota)
třeba o 60 stupňů - provedeme to jednoduše rotací kolem osy z.
Dále se má letadlo naklonit o 10 stupňů doprava (kolem své osy!).
V původní pozici by to šlo jednoduše provést rotací kolem osy x,
ale co teď? Osa letadla je nyní rovnoběžná s x a y a s x svírá
úhel 60 stupňů - de to rovněž provést, avšak už o dost složitěji
než pouhou rotací kolem y. A nakonec třetí úkol: chceme, aby
letadlo mířilo vzhůru, dejme tomu o 20 stupňů. Už se to velmi
zamotává, rotace jsou stále obtížněji proveditelné. Je zde i další
problém: pokud po každé rotaci letadla rotujeme všechny jeho body,
ztratíme jeho původní data. Také se težko zjišťuje, v které poloze
se letadlo nachází. Když to chceme vyřešit tak, že původní body
letadla při každém jeho vykreslování znovu rotujeme, musíme
pokaždé zopakovat všechny předchozí rotace a to je většinou
nezvládnutelné...
Jak to tedy udělat ?
Pro začátek je dobré si alespoň prohlédnout:
Základy lineární algebry
I když většinu z toho nebudeme přímo potřebovat.
Máme množinu bodů (a trojúhelníků,) definující 3D objekt. Ta se
nebude vůbec měnit, bude vždy taková, jako na počátku. Dejme tomu
že je to opět letadlo z minulého odstavce.
Co potřebujeme znát, abychom mohli letadlo nakreslit?
1) pozici
2) směr, kterým letadlo míří
3) směr, kterým míří křídla
A teď stačí jen přenést (zobrazit, transformovat) každý bod letadla
do prostoru, ve kterém ho chceme mít - v 3D grafice se mu říká
worldspace (=prostor světa.) Podobně prostor, ve kterém je letadlo
definováno a z kterého se do worldspace transformuje, se nazývá
objectspace (=prostor objektu.)
Jak takovou transformaci provést?
Jak transformovat bod z objectspace do worldspace?
Jednoduše - podívejme se znova, co všechno víme o poloze letadla:
1) známe pozici, víme tedy, na jaký bod se bude transformovat
bod [0,0,0] z objectspace
-budu ji značit p a jeho složky p0, p1 a p2
2) známe vektor, který odpovídá ose x
-budu ho značit vx a jeho složky vx0, vx1 a vx2
3) známe rovněž vektor, který odpovídá ose y
-budu značit vy a jeho složky vy0, vy1 a vy2
4) jistě se vyplatí znát y vektor osy z - dá se sice většinou
vypočítat z vektorů os y a x skalárním součinem, ale jsou případy,
kdy nechceme, aby byl na vektory os x a y kolmý.
-budu značit vz a jeho složky vz0, vz1 a vz2
Původní souřadnice bodu z objectspace budu značit x,y,z.
Souřadnice po transformaci x', y', z'.
Způsob, jak je vypočítat, je už asi nyní zřejmý:
x'= x*vx0 + y*vx1 + z*vx2 + p0
y'= x*vy0 + y*vy1 + z*vy2 + p1
z'= x*vz0 + y*vz1 + z*vz3 + p2
To velice připomíná lineární transformaci - zobrazení
aritmetického lineárního prostoru Vn (náš 3D prostor
je rovněž takový prostor) do Vn definované předpisem:
y = C*x
C je matice, x je vektor z prostoru Vn a y je vektor
po transformaci. Nyní je dobré si prostudovat nebo
zopakovat matice -a ti, kdo to ještě nevědí, to snad
pochopí:
Shrnutí matic a maticové algebry
Matice C by tedy vypadala asi takto:
+ +
| vx0 vx1 vx2 p0 |
| vy0 vy1 vy2 p1 |
| vz0 vz1 vz2 p2 |
+ +
a transformovala by (x,y,z) na (x',y',z')
Kdo si prostudoval matice důkladně, přišelna jeden zádrhel:
matice transformace musí být čtvercová, my naše matice má
rozměry 3x4. Co s tím? Přidáme jeden řádek:
+ +
| 0 0 0 1 |
+ +
a dostáváme:
x'= x*vx0 + y*vx1 + z*vx2 + w*p0
y'= x*vy0 + y*vy1 + z*vy2 + w*p1
z'= x*vz0 + y*vz1 + z*vz3 + w*p2
w'= x*0 + y*0 + z*0 + w*1
Kde vzít souřadnici w ?
Používáme sice třírozměrný prostor a ne čtyřrozměrný, ale
když jako w dosadíme číslo 1, výsledné souřadnice jsou stejné jako
předtím a w vyjde 1. (Kvůli tomu je v přidaném řádku na konci
jednička.)
Když dosadíme za w číslo 0, výsledky se liší a w vyjde 0. Při
bližším zkoumání zjistíme, že pokud se w=0, transformujeme
vektory, a pokud se w=1, transformujeme body.
Bod má tedy tvar: (x,y,z,1)
A vektor: (x,y,z,0)
Pokud sečteme 2 vektory, vyjde zase vektor. Když sčítáme bod
a vektor, vujde bod (posunutý o vektor.) Když odčítáme jeden bod
od druhého, vyjde vektor. Takže všechno jak má být. :)
Zbývá ještě jedna možnost, když sčítáme několik bodů, vyjde w
vyšší než 1, a tudíž výsledek není ani vektor, ani bod. To nám
rovněž vyhovuje.
Jak ale matice použít?
Na začátku se matice nastaví tak, aby odpovídala počáteční
poloze a směru objektu. Často se nastaví na jednotkovou matici,
takže platí x = x' atd. (Jednotková matice má samé nulové prvky,
jenom v hlavní diagonále má jedničky.
Veškeré další změny pozice či směru objektu se provádejí jen
a pouze úpravou jeho matice!
Dejme tomu, že chceme objekt otočit - jeho matici vynásobíme
maticí pro otočení a je to. když chceme objekt zvětšit, vynásobíme
jeho matici maticí pro zvětšení. Když chceme objekt posunout,
vynásobíme ho maticí pro posunutí. Všechno je opravdu velice
jednoduché...
Znamená to také, že pokud chceme mít najednou více objektů,
musíme u každého uchovávat jeho aktuální transformační matici. Ta
zachycuje všechny transformace, které s objektem provádíme, jako:
posunutí (translation)
rotace (rotation)
změna velikosti (scaling)
a jiné...
Matice objektu uchovává úplně všechny jeho transformace v
jakékoliv kombinaci. A to všechno zabírá jen pár desítek bajtů...
Jaké matice ale použít pro které transformace?
Jde je snadno odvodit, proto nebudu uvádět, proč která matice
vypadá tak, jak vypadá... Jednoduše uvedenou maticí vynásobte
matici objektu. Ale ne v opačném pořadí!
Posun (Translation)
(Podle vlastních os objektu! Takže když posunu letadlo po ose
x, vždycky se posune opravdu letadlo dopředu, ať je v jakékoliv
poloze!)
+ + tx...posunutí po ose x
| 1 0 0 tx | ty...posunutí po ose y
| 0 1 0 ty | tz...posunutí po ose z
| 0 0 1 tz |
| 0 0 0 1 |
+ +
Zvětšování (Scaling)
+ + sx...zvětšení na ose x
| sx 0 0 0 | sy...zvětšení na ose y
| 0 sy 0 0 | sz...zvětšení na ose z
| 0 0 sz 0 | (když nechcete zvětšovan
| 0 0 0 1 | na všech osách, jednoduše
+ + dosaďte jedničku :)
Rotace kolem osy X
+ +
| 1 0 0 0 | u...úhel rotace
| 0 cos(u) -sin(u) 0 | (pokud by objekt rotoval
| 0 sin(u) cos(u) 0 | opačném směru, prohoďte
| 0 0 0 1 | znaménka u funkcí sin)
+ +
Rotace kolem osy Y
+ +
| cos(u) 0 sin(u) 0 | u...úhel rotace
| 0 1 0 0 | (pokud by objekt rotoval
|-sin(u) 0 cos(u) 0 | opačném směru, prohoďte
| 0 0 0 1 | znaménka u funkcí sin)
+ +
Rotace kolem osy Z
+ +
| cos(u) -sin(u) 0 0 | u...úhel rotace
| sin(u) cos(u) 0 0 | (pokud by objekt rotoval
| 0 0 1 0 | opačném směru, prohoďte
| 0 0 0 1 | znaménka u funkcí sin)
+ +
Pokud byste rádi viděli alespoň nějaký zdroják pro práci s
maticemi, tak tady je! Je to část starší verze mého 3D enginu a za
případné chyby se zase omlouvám. A také ze místy nekvalitní kód -
psal jsem to, když jsem ještě pořádně neuměl programovat v C...
A používal jsem to až donedávna :-)
A to je prozatím vše. Ještě důležitá rada: prvky matice je
téměř nutné uchovávat jako proměnné s pohyblivou desetinou čárkou.
Jinak již po pár desítkách transformací začnou ztrácet přesnost a
po chvíli již jsou nepoužitelné. Naproti tomu třeba proměnné typu
float vydrží mnohem víc, a ze ztráty přesnosti není třeba mít strach,
neboť k znatelné ztrátě dojde až za velice dlouhou dobu, vydrží
možná i miliony transformací...
A ještě upozornění: spousta 3D enginů a různých jiných textů
vysvětlujících použití matic v 3D grafice uvádějí matice v jiném
tvaru - jsou prohozené osy, jinak jsou shodné. Matice s
prohozenými osami používá též Direct3D.
A co zbývá pro příště ? (a určitě i přespříště :-)
Zatím jsem se vůbec nezabýval tím, jak přidat kameru, která
se může volně pohybovat, ani hierarchiemi objektů.
Také bych se rád zmínil o tom, jak používat v 3D enginu
soubory 3DS vytvořené 3D Studiem.
A rovněž příště nebude chybět funkční zdroják jednoduchého
3D enginu včetně kamery a jejího ovládání klávesnicí!
Bude psaný v C a kompilovat pujde pomoci DJGPP a WATCOM C.
A pokud máte jakékoliv dotazy, neváhejte a zeptejte se.
Rovněž mě informujte o případných chybách!
Uvítám také jakékoliv nápady na pokračování tohoto seriálu.
Shakul / MovSD (a.k.a. Lukáš Pokorný)
email:
shakul@iname.com
pokornylukas@bbs.infima.cz
www:
http://shakul.home.ml.org
mail:
Lukáš Pokorný
Pomněnková 545
25243 Průhonice
výheň