/------+ --+ +--+ +-----+/| | | ++ | | | --+ | | | |/ | | ++ +-----+ --+ +--+ 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ň