Ettől a résztől kezdve már végre a térben fogunk munkálkodni, és a 3D objektumokat, azok kezelését vesszük nagyító alá. Most még csak az alapokkal ismerkednünk, mint ahogy azt már megszokhattátok. Alapjába véve 3D objektumnak nevezhetünk minden 3D testet, amit a környezetünkben megfigyelhetünk. A szabályos testeket, mint pl. amilyen a kocka is, könnyen modellezhetünk. A természet viszont igencsak eltér bármiféle szabályosságtól, pontosan ebből adódik, hogy nagyon kevés test esetén van meg azaz egyszerű lehetőségünk, hogy pl. egy függvénnyel megadhassuk őket, így kénytelenek vagyunk egyéb eszközöket alkalmazni. Egy lehetséges megoldást nézünk majd meg erre, és ha már térszámítás, akkor a klasszikus kocka-forgatás sem maradhat el, gyakorlatunk első feléből.

Kockaforgatás

Az általunk egyelőre megkívánt ábrázolási mód a 3D-s testek úgynevezett drótvázas ábrázolása lesz. A képszintézis során már pár szót ejtettünk róla. Nagy előnye, hogy a lehető legegyszerűbb és leggyorsabb mód arra, hogy egy 3D-s testet ábrázolni tudjunk. Ábrázolás során a test minden éle látható lesz, és a hátsó éleket sem különböztetjük meg egyelőre.

Mivel a térben vagyunk, egy pontot az x,y,z számhármas fog jellemezni, ezért az első dolgunk létrehozni egy tömböt, amelynek minden egyes eleme egy 3D-s pontot ír le. Ezt nyilván egy rekordtömb teszi lehetővé, amelynek deklarációja a következőképpen néz ki:

Const pn=8; Type T3D = record                  x,y,z : real;              end; Var points: Array [1..pn] of T3D;

Egy kocka esetében ez a tömb tehát 8 elemű lesz, mert a kocka csúcsainak a száma 8. A kocka koordinátáit természetesen tetszőlegesen felvehetnénk a 3D térben, de a legegyszerűbb először egy olyan kockát ábrázolni, amelyek oldalai párhuzamosak a 3 tengelyek megfelelőivel, és az origó a kocka súlypontjában van. Az ábrán egy 100 pixel oldalhosszal rendelkező kocka szerepel megfelelő koordinátáival. Ezek alapján az 1-es sorszámmal ellátott csúcspontot a következőképpen adhatjuk meg:

  points[1].x := -50;   points[1].y :=  50;   points[1].z :=  50;

A számozott csúcsok koordinátái a points tömbbe kerülnek (csúcs száma és a tömbindex megegyezik). A többi ponttal is hasonlóan kell eljárni, egészen addig, míg el nem jutunk az utolsóig. A mi esetünkben ez a nyolcat fog jelenteni. Ezekután már csak annyi dolgunk maradt, hogy megtudjuk azt, hogy mely pontot mely másik ponttal kell összekötnünk. Ha az ábrát megnézzük, akkor leolvashatjuk, hogy ahhoz, hogy a felső lapot megkapjuk az 1-es pontot a 2-essel, utána 2-est az 5-sel, 5-öst a 4-sel, és végül 4-est az 1-sel kell összekötni. Ha ez megvan, máris felállítottuk a kötési sort, és az objektumot ábrázolni tudjuk helyesen. A kötési sorhoz egy rekordtömböt is használhatunk, amely az egyes felületeket fogja leírni benne:

Type F3D = record               p1, p2, p3, p4: byte;      end; Var face: Array [1..test_feluleteinek_szama] of F3D;

A felület-rekordtömb egy eleme (pl. face[1]) egy rekordot tartalmaz, amelyben a csnx1, csnx2, csnx3, csnx4 azon csúcsok indexeit jelenti, amelyek az adott számú felületet alkotják a testen. Ezekután a felületeket tartalmazó tömböt kell feltöltenünk, a követezőekhez hasonlatosan:

  face[1].p1=1;   face[1].p2=2;   face[1].p3=3;   face[1].p4=4;   face[2].p1=2;   face[2].p2=5;   face[2].p3=6;   face[2].p4=4;

Ezen logikát felhasználva lesz egy egyértelmű leírásunk az adott test minden felületére vonatkozóan. Egy adott 3D-s test tehát két tömbünk segítségével már a memóriában van. Ezekután következhet az adatok alapján az objektum ábrázolása. A memóriában lévő objektum 3 dimenziós, ahhoz, hogy látható formába hozzuk a képernyőn, alkalmaznunk kell a leképezést, ami dimenziócsökkentő művelet, és az így kapott koordinátákat egy másik tömbbe, mint képernyő-koordinátákat eltároljuk. Ezek elemről elemre megfelelnek majd a 3D-s koordinátákat tartalmazó tömbnek.

Type T2D=record            x,y:integer;       end; Var     Kpoints : Array[1..pn] of T2D;

A leképezést minden pontra külön-külön kell elvégezni, ha változnak a pontok koordinátái, pl. forgatás során akkor is végre kell hajtani. A leképezés fogalma egyszerű, képzeljük le, hogy a nézőpont nincsen a képernyő felszínén, hanem bizonyos távolsággal mögötte helyezkedik el. Ezt a pontot fogjuk, nézőpont középpontú relatív koordinátarendszerként használni a következő módon. Mivel a z koordináta a képernyő belseje felé nő, el kell osztani a koordinátákat z-vel, az x-et és az y-t is. Ennyi az egész. Persze nem kell az irányokat x, y és z-nek nevezned, ez a helyzethez illő leírástól függ. És persze el kell döntened, mi történjen a "képernyőből kifelé tartó irányú" dolgokkal - de ez már egy másik kérdés. Illetve még azt is el kell döntened, hány egységnyivel van a nézőpont a képernyő mögött (ezt kell hozzáadnod z-hez az osztás előtt). Ami viszont bosszantó, hogy nem akarsz osztást végezni minden a képernyőre rajzolandó pixelre. Ez például azt jelenti, hogy találkozhatsz olyan vonallal, amelynek mindkét vége a képernyőn kívül van, de bizonyos része látható. Ez viszont már az ütközések vizsgálatának problémája. Vagy még inkább a vágásé, ez csak egy előretekintés az arról szóló fejezetre.

Procedure Centralis_projekcio;   var i:byte;   begin     for i:=1 to pn do       begin        kpoints[i].x:=xk+round(points[i].x*nezopont/(nezopont-points[i].z));        kpoints[i].y:=yk+round(points[i].y*nezopont/(nezopont-points[i].z));       end;   end;

A leképezés végrehajtás után már tudjuk azt, hogy milyen sorrendben kell a csúcsokat összekötni - innentől annyi a feladatunk, hogy a megfelelő pontokat a megfelelő sorrendben összekössük, és a kockát kirajzoljuk:

for i:=1 to Fn do       begin         Canvas.moveto(kpoints[face[i].p1].x,kpoints[face[i].p1].y);         Canvas.lineto(kpoints[face[i].p2].x,kpoints[face[i].p2].y);         Canvas.lineto(kpoints[face[i].p3].x,kpoints[face[i].p3].y);         Canvas.lineto(kpoints[face[i].p4].x,kpoints[face[i].p4].y);         Canvas.lineto(kpoints[face[i].p1].x,kpoints[face[i].p1].y);       end;

Az így kapott kép, nem egy nagy durranás, de ha kicsit megforgatjuk a kockánkat, a kapott képleteinkkel, akkor máris látványosabb lesz minden. A három tengelykörüli forgatás a következőképpen néz ki:

Procedure RotateX; var   i : integer; begin   for i := 1 to pn do   begin     points[i].x :=  points[i].x;     points[i].y :=  points[i].y * cos(fszog) + points[i].z * sin(fszog);     points[i].z := -points[i].y * sin(fszog) + points[i].z * cos(fszog);   end; end; Procedure RotateY; var   i : integer; begin   for i := 1 to pn do   begin     points[i].x := points[i].x * cos(fszog) - points[i].z * sin(fszog);     points[i].y := points[i].y;     points[i].z := points[i].x * sin(fszog) + points[i].z * cos(fszog);   end; end; Procedure RotateZ; var   i : integer; begin   for i := 1 to pn do   begin     points[i].x :=  points[i].x * cos(fszog) - points[i].y * sin(fszog);     points[i].y :=  points[i].x * sin(fszog) + points[i].y * cos(fszog);     points[i].z :=  points[i].z;   end; end;

Ezzel készen is volnánk, előállt az első igazi 3D-s objektumunk. Valószínűleg már sokan készítettek ehhez hasonlót, de a térszámítást és vetítést úgy gondoltam így lehet a legjobban bemutatni. Gyakorlatunk második felében a térgörbékkel fogunk foglalkozni. Ezután a kisebb vezetés után már könnyű dolgom lesz, hiszen alig kell pár szóval kiegészíteni az eddigieket.

Térgörbék

Síkbeli függvényeket (görbéket) már sikeresen létrehoztunk. Most már csak az eddigi ismereteinket kell átvinni a térbe is. Semmi extra dologra nem lesz szükségünk, csak újra a függvényekhez kell nyúlnunk. Mivel egy pontot a térben három koordináta határoz meg, ezért nekünk is három függvény kell felírnunk, amelyek sorra fogják megadni a pont x, y, z koordinátáját. Tehát egy térgörbe a következőképpen fog kinézni például:

Function fx(t: real): real; begin fx:=a*(cos(t)-sin(2*t)); end; Function fy(t: real): real; begin fy:=a*(cos(t)-sin(t)); end; Function fz(t: real): real; begin   fz:=a*sin(3*t); end;

Ezzel már meg is adtuk a mi kis térbeli görbénket, csak még a képernyőre kell vetítenünk a görbénk térbeli pontjait. Ezért minden kiszámított pontot egy a vetítést végző eljárással kétdimenzióssá konvertálunk:

Procedure Vetit(x,y,z : real); begin     xt:=xk+round((-bp*x+ap*y)/nv1);     yt:=yk-round((-cp*(ap*x+bp*y)+sqr(nv1)*z)/nv2); end;

Ahol x,y,z mindig a vetítendő pont megfelelő koordinátáit jelöli. A képpontokat pedig az xt és yt változóba tölti be. Ezután a megjelenítés már nem lehet probléma. Ugyan úgy járunk el, mint a síkban:

    t:=tol;     Vetit(Fx(t),Fy(t),Fz(t));     Canvas.MoveTo(xt,yt);     While t<ig do       begin         Vetit(Fx(t),Fy(t),Fz(t));         Canvas.LineTo(xt,yt);         t:=t+finomsag;       end;

Ha a görbét el is szeretnénk forgatni, akkor azt ugyan úgy tehetjük, meg mint a kocka esetében, és az elforgatás után végezzük el újra a vetítést. Az eddig megismert interpolációs és approximációs eljárások esetében ugyan így kell eljárnunk. Azaz csak annyi a dolgunk, hogy az eddig megismert x és y koordinátára felirt polinomot a z koordinátára is felírjuk. Azaz az eddigi x és y mellett egy z is lesz.

Ennyit gondoltam ez alkalommal erről a témáról, ha bármiféle kérdés merülne fel arra szívesen válaszolok, ha tudok. Remélem sikerült megérteni és megragadni a lényeget, hogy mindenki bátran merjen és tudjon majd kísérletezni. Én most búcsúzom, legközelebb a felületekkel foglalkozunk.

A cikkhez tartozó példaprogramok forráskóddal együtt letölthetők innen: forraskod

Felhasznált és ajánlott irodalom:
Foley, van Dam, Feiner, and Hughes: "Számítógépes grafika: Alapelvek és gyakorlat" c.
Dr. Szirmay-Kalos László: Számítógépes grafika, 2001
L. Ammeraal: Programming Principles in Computer Graphics
Bárczy-Barnabás: Differenciálszámítás, 2001
Turcsányi Tamás, Debreceni Egyetem, 2000
Budai Attila: Számítógépes grafika, 1999
Füzi János: Grafikai Alkalmazások Delphi Nyelven, 2000
Füzi János: Interaktív grafika, 1997