Si të konfiguroni telefonat inteligjentë dhe PC. Portali informativ
  • në shtëpi
  • OS
  • Matrica e transformimit të perspektivës së përgjithshme. Programimi DirectX9: Objekte rrotulluese

Matrica e transformimit të perspektivës së përgjithshme. Programimi DirectX9: Objekte rrotulluese

Aksonometria është një projeksion paralel. Tabela 3.3 liston matricat e para të projeksioneve drejtshkrimore në rrafshet e koordinatave të marra nga përkufizimet e tyre.

Tabela 3.3 Projektimi i matricave të transformimit dhe projeksionit

Projeksion drejtshkrimor në XOY

Projeksion drejtshkrimor në YOZ

Projeksion drejtshkrimor në XOZ

Projeksioni drejtshkrimor në rrafsh x = p

Matrica e transformimit trimetrik në rrafshin XOY

Matrica e transformimit izometrik në rrafshin XOY

Matrica e projeksionit izometrik XOY

Matrica e zhdrejtë e projeksionit në XOY

Matrica e projeksionit falas në XOY

Matrica e projektimit të kabinetit XOY

Matrica e transformimit të perspektivës me një pikë zhdukjeje (rrafshi i figurës pingul me boshtin e abshisave)

Matrica e transformimit të perspektivës me një pikë zhdukjeje (rrafshi i figurës pingul me ordinatën)

Matrica e transformimit të perspektivës me një pikë zhdukjeje (rrafshi i figurës pingul me boshtin e aplikuar)

Matrica e transformimit të perspektivës me dy pika zhdukjeje (rrafshi i figurës paralel me ordinatën)

Matrica e transformimit të perspektivës me tre pika zhdukjeje (rrafshi i figurës së pozicionit arbitrar)

Izometria, dimetria dhe trimetria përftohen duke kombinuar rrotullimet e ndjekura nga një projeksion nga pafundësia. Nëse keni nevojë të përshkruani projeksionin në rrafshin XOY, atëherë së pari duhet të transformoni rrotullimin me një kënd rreth boshtit të ordinatave, pastaj nga këndi në raport me boshtin e abshisë. Tabela 3.3 tregon matricën e transformimit trimetrik. Për të marrë një matricë të transformimit dimetrik, në të cilën, për shembull, koeficientët e shtrembërimit përgjatë boshteve të abshisës dhe ordinatave do të jenë të barabartë, marrëdhënia midis këndeve të rrotullimit duhet t'i bindet varësisë

Kjo është, duke zgjedhur një kënd , ju mund të llogarisni këndin dhe përcaktoni matricën e projeksionit dimetrik. Për një transformim izometrik, marrëdhënia e këtyre këndeve kthehet në vlera të përcaktuara rreptësisht, të cilat janë:

Tabela 3.3 tregon matricën e transformimit izometrik si dhe matricën e projeksionit izometrik në rrafshin XOY. Nevoja për matrica të tipit të parë qëndron në përdorimin e tyre në algoritme për heqjen e elementeve të padukshëm.

Në projeksionet e zhdrejtë, vijat e drejta të projektuara formojnë një kënd me rrafshin e projeksionit që ndryshon nga 90 gradë. Tabela 3.3 tregon matricën e përgjithshme të zhdrejtë të projeksionit në rrafshin XOY, si dhe matricat e projeksionit të lirë dhe të kabinetit, në të cilat:

Projeksionet e perspektivës (Tabela 3.3) përfaqësohen gjithashtu nga transformimet e perspektivës dhe projeksionet e perspektivës në rrafshin XOY. V X, V Y dhe V Z janë qendra projeksioni - pika në boshtet përkatëse. –V X, –V Y, –V Z do të jenë pikat në të cilat bashkohen tufat e drejtëzave paralele me boshtet përkatëse.

Sistemi koordinativ i vëzhguesit është majtas sistemi i koordinatave (Figura 3.3), në të cilin boshti z e drejtohet nga këndvështrimi përpara, boshti x e drejtohet djathtas dhe boshti y është lart. Një rregull i tillë është miratuar për koincidencën e boshteve x e dhe y e me boshtet x s dhe y s në ekran. Përcaktimi i vlerave të koordinatave të ekranit x s dhe y s për pikën P çon në nevojën për t'u pjesëtuar me koordinatat z e. Për të ndërtuar një pamje të saktë të perspektivës, është e nevojshme të ndahet me koordinatat e thellësisë së secilës pikë.

Tabela 3.4 tregon vlerat e përshkruesit të kulmit S (X, Y, Z) të modelit (Figura 2.1), që i nënshtrohen transformimeve rrotulluese dhe transformimeve izometrike.

Tabela 3.4 Përshkruesit e modelit të kulmit

Model origjinal

M (R (z, 90)) xM (R (y, 90))

Motori nuk e lëviz anijen.
Anija mbetet në vend, dhe
motorët lëvizin universin
Rreth tij.

Futurama

Ky është një nga mësimet më të rëndësishme. Lexojeni me mend të paktën tetë herë.

Koordinatat homogjene

Në mësimet e mëparshme, supozuam se kulmi ndodhet në koordinatat (x, y, z). Le të shtojmë një koordinatë më shumë - w. Tani e tutje, ne do të kemi kulmet në koordinatat (x, y, z, w)

Së shpejti do të kuptoni se çfarë është ajo, por tani për tani, merreni si të mirëqenë:

  • Nëse w == 1, atëherë vektori (x, y, z, 1) është pozicioni në hapësirë
  • Nëse w == 0 atëherë vektori (x, y, z, 0) është drejtimi.

Mos harroni këtë si një aksiomë pa prova !!!

Dhe çfarë na jep? Epo, asgjë për rrotullim. Nëse rrotulloni një pikë ose drejtim, merrni të njëjtin rezultat. Por nëse rrotulloni zhvendosjen (kur lëvizni pikën në një drejtim të caktuar), atëherë gjithçka ndryshon në mënyrë dramatike. Çfarë do të thotë "ndërrimi i drejtimit"? Asgje speciale.

Koordinatat homogjene na lejojnë të operojmë me një pajisje të vetme për të dyja rastet.

Matricat e transformimit

Hyrje në matricat

Me fjalë të thjeshta, një matricë është vetëm një grup numrash me një numër fiks rreshtash dhe kolonash.

Për shembull, një matricë 2-nga-3 do të duket kështu:

Në grafikat 3D, ne pothuajse gjithmonë përdorim matrica 4x4. Kjo na lejon të transformojmë kulmet tona (x, y, z, w). Është shumë e thjeshtë - ne shumëzojmë vektorin e pozicionit me matricën e transformimit.

Matrica * Vertex = Kulmi i transformuar

Nuk është aq e frikshme sa duket. Drejtojeni gishtin e majtë drejt a dhe gishtin e djathtë drejt x. Kjo do të jetë sëpatë. Lëvizni gishtin e majtë te numri tjetër b dhe gishtin e djathtë poshtë te numri tjetër y. ia dolëm. Edhe një herë - cz. Dhe përsëri - dw. Tani përmbledhim të gjithë numrat që rezultojnë - sëpatë + nga + cz + dw. Ne kemi x-në tonë të re. Përsëriteni këtë për çdo rresht dhe do të merrni një vektor të ri (x, y, z, w).

Sidoqoftë, ky është një operacion mjaft i mërzitshëm, ndaj lëreni kompjuterin ta bëjë atë për ne.

Në C ++ duke përdorur bibliotekën GLM:

glm :: mat4 myMatrix;


glm :: vec4 myVector;



glm:: vec 4 Vektor i transformuar = myMatrix * myVector; // Mos harroni për porosinë !!! Kjo është jashtëzakonisht e rëndësishme!!!

Në GLSL:

mat4 myMatrix;


vec4 myVector;


// mbushim matricën dhe vektorin me vlerat tona ... ne e kapërcejmë këtë


vec 4 Vektor i transformuar = myMatrix * myVector; // ashtu si në GLM

(Diçka më duket se ju nuk e keni kopjuar këtë pjesë të kodit në projektin tuaj dhe nuk e keni provuar ... ejani, provojeni, është interesante!)

Matrica e zhvendosjes

Matrica e zhvendosjes është ndoshta matrica më e thjeshtë nga të gjitha. Atje ajo është:


Këtu X, Y, Z janë vlerat që duam të shtojmë në pozicionin tonë të kulmit.

Pra, nëse duhet të lëvizim vektorin (10,10,10,1) me 10 pikë, në pozicionin X, atëherë:
(Provojeni vetë, mirë, ju lutem!)

... Dhe marrim (20,10,10,1) në një vektor homogjen. Siç shpresoj ta mbani mend, 1 do të thotë se vektori është një pozicion, jo një drejtim.

Tani le të përpiqemi të transformojmë drejtimin (0,0, -1,0) në të njëjtën mënyrë:

Dhe në fund morëm të njëjtin vektor (0,0, -1,0).
Siç thashë, nuk ka kuptim të lëvizësh drejtimin.

Si ta kodojmë këtë?

Në C ++ me GLM:

#përfshi // pas


glm :: mat4 myMatrix = glm :: përkthe (10.0f, 0.0f, 0.0f);


glm :: vec4 myVector (10.0f, 10.0f, 10.0f, 0.0f);


glm:: vec 4 Vektor i transformuar = myMatrix * myVector; // dhe cili është rezultati?


Dhe në GLSL: Në GLSL, kaq rrallë dikush. Më shpesh duke përdorur funksionin glm :: translate (). Së pari, ata krijojnë një matricë në C ++, dhe më pas e dërgojnë atë në GLSL, dhe tashmë atje ata bëjnë vetëm një shumëzim:

vec4 transformedVector = myMatrix * myVector;

Matrica e identitetit

Kjo është një matricë e veçantë. Ajo nuk bën asgjë. Por e përmend sepse është e rëndësishme të dihet se shumëzimi i A me 1.0 jep A:

glm :: mat4 myIdentityMatrix = glm :: mat4 (1.0f);

Matrica e shkallëzimit

Matrica e shkallëzimit është gjithashtu mjaft e thjeshtë:

Pra, nëse doni të dyfishoni vektorin (pozitën ose drejtimin, nuk ka rëndësi) në të gjitha drejtimet:

Dhe koordinata w nuk ka ndryshuar. Nëse pyetni, "Çfarë është shkallëzimi i drejtimit?" Jo shpesh i dobishëm, por ndonjëherë i dobishëm.

(vini re se shkallëzimi i matricës së identitetit me (x, y, z) = (1,1,1))

C ++:

// Përdorni#përfshi dhe#përfshi


glm :: mat4 myScalingMatrix = glm :: shkallë (2.0f, 2.0f, 2.0f);

Matrica e rrotullimit

Por kjo matricë është mjaft e ndërlikuar. Prandaj, nuk do të ndalem në detajet e zbatimit të tij të brendshëm. Nëse vërtet dëshironi, më mirë lexoni (Matricat dhe Quaternions FAQ)

VME++:

// Përdorni#përfshi dhe#përfshi


glm :: vec3 myRotationAxis (??, ??, ??);


glm :: rrotullim (kënd_në_gradë, myRotationAxis);

Transformimet e Përbëra

Tani dimë se si t'i rrotullojmë, lëvizim dhe shkallëzojmë vektorët tanë. Do të ishte mirë të dinit se si t'i kombinoni të gjitha këto. Kjo bëhet thjesht duke shumëzuar matricat me njëra-tjetrën.

TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector;

Dhe përsëri porosia !!! Së pari ju duhet të ndryshoni madhësinë, pastaj lëvizni dhe vetëm pastaj lëvizni.

Nëse i zbatojmë transformimet në një mënyrë tjetër, nuk do të marrim të njëjtin rezultat. Provoje:

  • Bëni një hap përpara (mos e rrëzoni kompjuterin nga tavolina) dhe kthehuni majtas
  • Ktheni majtas dhe bëni një hap përpara.

Po, duhet të mbani mend gjithmonë rendin e veprimeve kur kontrolloni, për shembull, një personazh të lojës. Së pari, nëse është e nevojshme, bëni shkallëzimin, më pas vendosni drejtimin (rotacionin) dhe më pas lëvizni. Le të hedhim një vështrim në një shembull të vogël (e hoqa rrotullimin për t'i bërë llogaritjet më të lehta):

Mënyra e gabuar:

  • Zhvendosni anijen në (10,0,0). Qendra e saj tani është 10 X nga qendra.
  • Ne e rrisim madhësinë e anijes sonë 2 herë. Çdo koordinatë shumëzohet me 2 në lidhje me qendrën, e cila është larg ... Dhe si rezultat, marrim një anije të madhësisë së kërkuar, por në pozicionin 2 * 10 = 20. E cila nuk është saktësisht ajo që ne dëshironim.

Rruga e duhur:

  • Ne e rrisim madhësinë e anijes me 2 herë. Tani kemi një anije të madhe në qendër.
  • Ne lëvizim anijen. Madhësia e anijes nuk ka ndryshuar dhe është e vendosur në vendin e duhur.
Shumëzimi matricë-matricë bëhet në të njëjtën mënyrë si shumëzimi matricë-vektor. Nuk do të hyjmë në detaje, por lërini kureshtarët ta lexojnë nga burime të specializuara. Ne thjesht do të mbështetemi në bibliotekën GLM.
VС ++:
glm :: mat4 myModelMatrix = myTranslationMatrix * myRotationMatrix * myScaleMatrix;
glm :: vec4 myTransformedVector = myModelMatrix * myOriginalVector;
Në GLSL:
mat4 transformim = mat2 * mat1;
vec4 out_vec = transformim * in_vec;

Matricat e modelit, pamjes dhe projektimit

Për qëllime ilustrimi, do të supozojmë se tashmë dimë të vizatojmë në OpenGL modelin tonë të preferuar 3D të Blenderit - kokën e majmunit Suzanne.

Matricat Model, View dhe Projection janë një metodë shumë e përshtatshme për ndarjen e transformimeve. Ju nuk keni pse t'i përdorni ato nëse vërtet dëshironi (ne nuk i kemi përdorur në mësimet 1 dhe 2). Por unë rekomandoj fuqimisht që t'i përdorni ato. Thjesht, pothuajse të gjitha bibliotekat 3D, lojërat, etj., i përdorin ato për të ndarë transformimet.

Matrica e modelit

Ky model, si trekëndëshi ynë i dashur, përcaktohet nga një grup kulmesh. Koordinatat X, Y, Z janë të specifikuara në lidhje me qendrën e objektit. Pra, nëse kulmi ndodhet në koordinatat (0,0,0), atëherë është në qendër të të gjithë objektit

Tani jemi në gjendje të lëvizim modelin tonë. Për shembull, sepse përdoruesi e kontrollon atë me tastierë dhe maus. Është shumë e lehtë për t'u bërë: shkallëzim * rrotullues * lëviz dhe kaq. Ju aplikoni matricën tuaj në të gjitha kulmet në çdo kornizë (në GLSL, jo në C ++) dhe gjithçka lëviz. Çdo gjë që nuk lëviz ndodhet në qendër të “botës”.

Kulmet janë në hapësirën botërore Shigjeta e zezë në figurë tregon se si kalojmë nga hapësira e modelit në hapësirën botërore (Të gjitha kulmet janë specifikuar në lidhje me qendrën e modelit, dhe çeliku janë specifikuar në lidhje me qendrën e botës)

Ky transformim mund të shfaqet me diagramin e mëposhtëm:


Shiko Matricën

Le të lexojmë përsëri citimin nga futurama:

“Motori nuk e lëviz anijen. Anija mbetet në vend dhe motorët lëvizin universin rreth saj."


E njëjta gjë mund të zbatohet për kamerën. Nëse dëshironi të fotografoni një mal nga çdo kënd, mund të lëvizni kamerën ... ose malin. Kjo është e pamundur në jetën reale, por është shumë e lehtë dhe e përshtatshme në grafikën kompjuterike.

Si parazgjedhje, kamera jonë është në qendër të Koordinatave Botërore. Për të lëvizur botën tonë, ne duhet të krijojmë një matricë të re. Për shembull, duhet ta zhvendosim kamerën tonë 3 njësi djathtas (+ X). Kjo është e njëjtë me lëvizjen e gjithë botës 3 njësi në të majtë (-X). Dhe ndërsa truri juaj po shkrihet, le të provojmë:


// Përdorni #include dhe #përfshi


glm :: mat4 ViewMatrix = glm :: përkthe (-3.0f, 0.0f, 0.0f);

Fotografia më poshtë tregon këtë: ne po lëvizim nga hapësira Botërore (të gjitha kulmet janë vendosur në lidhje me qendrën e botës siç bëmë në seksionin e mëparshëm) në hapësirën e kamerës (të gjitha kulmet janë vendosur në lidhje me kamerën).

Dhe përpara se koka juaj të shpërthejë plotësisht, shikoni këtë funksion fantastik nga GLM-ja jonë e vjetër:

glm :: mat4 CameraMatrix = glm :: LookAt (


Pozicioni i kamerës, // Pozicioni i kamerës në koordinatat botërore


kameraTarget, // pika që duam të shikojmë në koordinatat botërore


lartVektor// ka shumë të ngjarë glm:: vec3 (0,1,0) dhe (0, -1,0) do të tregojnë gjithçka me kokë poshtë, gjë që ndonjëherë është gjithashtu e lezetshme.


Ja një ilustrim për sa më sipër:


Por për kënaqësinë tonë, kjo nuk është e gjitha.

Matrica e Projeksionit

Tani kemi koordinata në hapësirën e kamerës. Kjo do të thotë që pas gjithë këtyre transformimeve, kulmi i të cilave ka fatin të jetë në x == 0 dhe y == 0 do të jepet në qendër të ekranit. Por nuk mund të përdorim vetëm koordinatat X, Y për të kuptuar se ku të vizatojmë kulmin: distanca nga kamera (Z) gjithashtu duhet të merret parasysh! Nëse kemi dy kulme, atëherë njëra prej tyre do të jetë më afër qendrës së ekranit se tjetra, pasi ka një koordinatë më të madhe Z.

Ky quhet projeksion perspektiv:


Dhe për fat të mirë për ne, një matricë 4x4 mund të përfaqësojë gjithashtu transformime të perspektivës:

glm :: mat4 projeksionMatrix = glm :: perspektiva (


FoV, // Fusha horizontale e pamjes në gradë. Ose magnitudëpërafrimet. Sisikur « lente» kamera. Zakonisht midis 90 (super i gjerë, si një sy peshku) dhe 30 (si një gotë e vogël spiun)


4.0 f / 3.0 f, // Raporti i pamjes. Varet nga madhësia e dritares suaj. Për shembull, 4/3 == 800/600 == 1280/960 tingëllon e njohur, apo jo?


0.1 f, // Pranë fushës së prerjes. Duhet të vendoset sa më i madh, përndryshe do të ketë probleme me saktësinë.


100.0 f // Fushë e prerë e largët. Duhet të mbahet sa më i vogël.


);

Le të përsërisim atë që kemi bërë tani:

Ne jemi larguar nga hapësira e kamerës (të gjitha kulmet janë vendosur në koordinata në lidhje me kamerën) në hapësirë ​​homogjene (të gjitha kulmet janë në koordinatat e një kubi të vogël (-1,1). Çdo gjë në kub është në ekran.)

Dhe diagrami përfundimtar:


Këtu është një foto tjetër për ta bërë më të qartë se çfarë ndodh kur shumëzojmë gjithë këtë marrëzi të matricës së projeksionit. Përpara se të shumëzojmë me matricën e projeksionit, kemi objekte blu të përcaktuara në hapësirën e kamerës dhe një objekt të kuq që përfaqëson fushën e shikimit të kamerës: hapësira që bie në lentet e kamerës:

Pas shumëzimit me matricën e projeksionit, marrim sa vijon:

Në foton e mëparshme, fusha e pamjes është kthyer në një kub të përsosur (me koordinata kulmore nga -1 në 1 në të gjitha boshtet.), Dhe të gjitha objektet janë deformuar në perspektivë. Të gjitha objektet blu që janë afër kamerës janë bërë të mëdha, dhe ato më larg - të vogla. Njësoj si në jetë!

Ja pamja që kemi nga “lentet”:

Megjithatë, ai është katror dhe duhet të zbatohet një transformim matematikor për t'iu përshtatur fotografisë me madhësinë e dritares.

Për të rrotulluar objektet (ose një aparat fotografik), nevojitet një bazë serioze matematikore, me ndihmën e së cilës do të llogariten koordinatat e të gjitha objekteve kur shfaqen në një ekran kompjuteri "të sheshtë". Unë dua të them menjëherë se ju nuk duhet të frikësoheni, të gjitha bibliotekat matematikore tashmë janë shkruar për ne, ne do t'i përdorim vetëm ato. Në çdo rast, teksti i mëposhtëm nuk ka nevojë të anashkalohet, pavarësisht nivelit të njohurive të matematikës.

1. Matricat, konceptet e përgjithshme

Çfarë janë matricat? Kujtojmë matematikën më të lartë: një matricë ¬ është një grup numrash me një dimension të njohur më parë rreshtash dhe kolonash.

Matricat mund të shtohen, të shumëzohen me një numër, të shumëzohen me njëra-tjetrën dhe shumë gjëra të tjera interesante, por ne do ta kalojmë këtë moment, sepse përshkruhet me detaje të mjaftueshme në çdo tekst shkollor të matematikës së lartë (tekstet shkollore mund të kërkohen në google.com). Ne do të përdorim matricat si programues, i plotësojmë dhe themi se çfarë të bëjmë me to, të gjitha llogaritjet do të kryhen nga biblioteka matematikore Direct3D, kështu që ju duhet të përfshini modulin e kokës d3dx9.h (dhe bibliotekën d3dx9.lib) në projekti.

Detyra jonë është të krijojmë një objekt, d.m.th. plotësoni matricën me koordinatat e kulmeve të objektit. Çdo kulm është një vektor (X, Y, Z) në hapësirën 3D. Tani, për të kryer një veprim, duhet të marrim objektin tonë (d.m.th. matricën) dhe të shumëzojmë me matricën e transformimit, rezultati i këtij operacioni është një objekt i ri i specifikuar në formën e një matrice.

Ekzistojnë tre matrica kryesore të përcaktuara dhe të përdorura në Direct3D: matrica botërore, matrica e pamjes dhe matrica e projektimit. Le t'i shqyrtojmë ato në më shumë detaje.

Matrica Botërore- lejon rrotullimin, transformimin dhe shkallëzimin e një objekti, dhe gjithashtu i pajis secilit prej objekteve me sistemin e vet të koordinatave lokale.

Funksionet për të punuar me matricën botërore:

  • D3DXMatrixRotationX (), D3DXMatrixRotationY (), D3DXMatrixRotationZ () - rrotullimi i pikës rreth njërit prej boshteve;
  • D3DXMatrixTranslation () - zhvendosni një pikë në një pozicion tjetër;
  • D3DXMatrixScale () - shkallëzim.

    Shiko Matricën- përcakton vendndodhjen e kamerës së shikimit të skenës dhe mund të përbëhet nga çdo kombinim i transmetimit dhe rrotullimit.
    D3DXMatrixLookAtLH () dhe D3DXMatrixLookAtRH () përcaktojnë pozicionin e kamerës dhe këndin e shikimit për sistemet e koordinatave me dorën e majtë dhe të djathtë, përkatësisht.

    Matrica e Projeksionit- krijon një projeksion të një skene 3D në ekranin e monitorit. Ai e transformon objektin, e zhvendos origjinën në pjesën e përparme dhe përcakton rrafshin e prerjes së përparme dhe të pasme.

    Duke plotësuar këto matrica dhe duke bërë transformime, ju krijoni një skenë tredimensionale në të cilën ju merrni aftësinë për të lëvizur, rrotulluar, zmadhuar, hequr dhe kryer veprime të tjera në objekte, në varësi të nevojave tuaja.

    2. Krijimi i objektit

    Ne krijojmë një projekt të ri, të ngjashëm me të parin. Përpara se të vazhdojmë të komplikojmë kodin tonë, le ta ndajmë atë në pjesë për lexueshmëri më të mirë të kodit. Është logjike ta ndajmë projektin tonë në tre komponentë:
    1. Dritarja e Windows (inicializimi i dritares, mesazhet, ...)
    2. Inicializimi 3D (ngarkimi i koordinatave të objektit, fshirja e burimeve, ...)
    3. Paraqitja e skenës (matrica, vizatimi i primitivëve, ...)
    Si rezultat, do të kemi 3 skedarë - window.cpp, init3d.h, render.h me përmbajtjen e mëposhtme: init3d.h- ne transferojmë variabla dhe struktura globale, deklarimin e funksioneve, funksionet InitDirectX (), InitBufferVertex (), Destroy3D () render.h- ne transferojmë funksionin RenderScene (), gjithçka që mbetet prek dritaren kryesore, do të jetë një skedar - dritare.cpp.

    Shtimi i një skedari kokë dhe bibliotekë për përdorimin e funksioneve të matricës

    #përfshi // ose C: \ DXSDK \ Përfshi \ d3dx9.h # koment pragma (lib, "d3dx9.lib") // ose C: \\ DXSDK \\ Lib \\ d3dx9.lib

    Ne gjithashtu kemi nevojë për funksione standarde për të punuar me kohën, kështu që ne përfshijmë skedarin e duhur të kokës:

    #përfshi

    Le të ndryshojmë formatin e paraqitjes së kulmit:

    #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ / D3DFVF_DIFFUSE) struktura CUSTOMVERTEX ( Noton x, y, z; DWORD ngjyrë; );

    Ne do të përdorim një lloj kulmi të pakonvertuar, pasi transformimet do të bëhen me matrica.
    Ndryshoni kodin e funksionit InitDirectX (). Cilësimi i dy mënyrave të ekranit duhet t'i shtohet këtij funksioni.
    Fikni modalitetin e prerjes në mënyrë që kur të rrotulloheni të mund të shihni të gjitha anët e objektit:

    PDirectDevice-> SetRenderState (D3DRS_CULLMODE, D3DCULL_NONE);

    Për momentin, ne nuk përdorim ndriçimin, por lyejmë kulmet me një ngjyrë të caktuar, kështu që e fikim ndriçimin:

    PDirectDevice-> SetRenderState (D3DRS_LIGHTING, FALSE);

    Le ta thjeshtojmë zemrën tonë duke e paraqitur atë si tre trekëndësha. Ne do të përdorim një sistem koordinativ lokal.


    CUSTOMVERTEX stVertex = ((-1,0f, 0,5f, 0,0f, 0x00ff0000), (-0,5f, 1,0f, 0,0f, 0x00ff0000), (0,0f, 0,5f, 0,0f, 0,0,0) f, 0.0f, 0x000000ff), (0.5f, 1.0f, 0.0f, 0x000000ff), (1.0f, 0.5f, 0.0f, 0x000000ff), (-1.0f, 0.5f, 0.0f, 0.0f, 0.0f f, 0.5f, 0.0f, 0x0000ff00), (0.0f, -1.0f, 0.0f, 0x0000ff00),);

    3. Krijimi i matricave të transformimit

    Le të shkruajmë në skedarin render.h funksionin SetupMatrix () në të cilin do të ndodhin të gjitha veprimet në matrica.

    Le të krijojmë matrica:

  • D3DXMATRIX MatrixWorld; - matrica botërore
  • D3DXMATRIX MatrixView; - matrica e formës
  • D3DXMATRIX MatrixProjection; - matrica e projeksionit
    Vendosja e matricës së botës

    Në mënyrë që objekti të rrotullohet, është e nevojshme të merret koha e sistemit dhe çdo "çast" të ndryshojë këndin midis sistemit koordinativ lokal dhe sistemit të koordinatave botërore. Do të rrotullohemi rreth boshtit X, kështu që përdorim funksionin D3DXMatrixRotationX. Pas llogaritjes së matricës botërore, duhet të aplikoni vlerat e saj duke përdorur funksionin SetTransform:

    UINT iTime = kohaGetTime ()% 5000; FLOAT fAngle = iTime * (2.0f * D3DX_PI) /5000.0f; D3DXMatrixRotationX (& MatrixWorld, fAngle); pDirectDevice-> SetTransform (D3DTS_WORLD, & MatrixWorld); Vendosja e matricës së pamjes

    Instaloni kamerën në vendin e duhur dhe drejtojeni te objekti

  • D3DXMatrixLookAtLH (& MatrixView, - rezultati i funksionit
  • & D3DXVECTOR3 (0.0f, 0.0f, -8.0f), - pika në të cilën ndodhet kamera
  • & D3DXVECTOR3 (0.0f, 0.0f, 0.0f), - pika në të cilën po shikojmë
  • & D3DXVECTOR3 (0.0f, 1.0f, 0.0f)); - maja e objektit

    Pas llogaritjes, duhet të aplikoni vlerat e marra.

  • Motori nuk e lëviz anijen. Anija mbetet në vend, dhe motori lëviz universin në lidhje me të.

    Kjo është një pjesë shumë e rëndësishme e mësimeve, sigurohuni që ta lexoni disa herë dhe ta kuptoni mirë.

    Koordinatat homogjene

    Deri më tani, ne kemi vepruar me kulme 3-dimensionale si treshe (x, y, z). Le të prezantojmë një parametër tjetër w dhe të veprojmë me vektorët e formës (x, y, z, w).

    Mbani mend përgjithmonë se:

    • Nëse w == 1, atëherë vektori (x, y, z, 1) është një pozicion në hapësirë.
    • Nëse w == 0, atëherë vektori (x, y, z, 0) është drejtimi.

    Çfarë na jep? Ok, kjo nuk ndryshon asgjë për rrotullimin, pasi në rastin e një rrotullimi të pikës dhe në rastin e një rrotullimi të vektorit të drejtimit, ju merrni të njëjtin rezultat. Megjithatë, në rastin e transferimit, ka një ndryshim. Lëvizja e vektorit të drejtimit do të japë të njëjtin vektor. Në këtë do të ndalemi më në detaje më vonë.

    Koordinatat homogjene na lejojnë të operojmë me vektorë në të dyja rastet duke përdorur një formulë matematikore.

    Matricat e transformimit

    Hyrje në matricat

    Mënyra më e lehtë për të imagjinuar një matricë është si një grup numrash, me një numër të përcaktuar rreptësisht të rreshtave dhe kolonave. Për shembull, një matricë 2x3 duket si kjo:

    Megjithatë, në grafikat 3D ne do të përdorim vetëm matrica 4x4 të cilat do të na lejojnë të transformojmë kulmet tona (x, y, z, w). Kulmi i transformuar është rezultat i shumëzimit të matricës me vetë kulmin:

    Matrica x kulm (në atë rend !!) = Transformo. kulm

    Shumë e thjeshtë. Ne do ta përdorim këtë mjaft shpesh, kështu që ka kuptim të udhëzojmë një kompjuter ta bëjë këtë:

    Në C ++ duke përdorur GLM:

    glm :: mat4 myMatrix; glm :: vec4 myVector; glm :: // Kushtojini vëmendje porosisë! Ai është i rëndësishëm!

    Në GLSL:

    mat4 myMatrix; vec4 myVector; // Mos harroni të plotësoni matricën dhe vektorin me vlerat e kërkuara këtu vec4 transformedVector = myMatrix * myVector; // Po, kjo është shumë e ngjashme me GLM :)

    Provoni të eksperimentoni me këto fragmente.

    Matrica e transferimit

    Matrica e transferimit duket si kjo:

    ku X, Y, Z janë vlerat që duam t'i shtojmë vektorit tonë.

    Pra, nëse duam ta zhvendosim vektorin (10, 10, 10, 1) me 10 njësi në drejtimin X, atëherë marrim:

    … Marrim (20, 10, 10, 1) vektor homogjen! Mos harroni se 1 në parametrin w do të thotë pozicion, jo drejtim, dhe transformimi ynë nuk e ndryshoi faktin që ne po punojmë me pozicionin.

    Tani le të shohim se çfarë ndodh nëse vektori (0, 0, -1, 0) është një drejtim:

    ... dhe marrim vektorin tonë origjinal (0, 0, -1, 0). Siç u tha më parë, vektori me parametrin w = 0 nuk mund të transferohet.

    Dhe tani është koha për ta zhvendosur atë në kod.

    Në C ++, me GLM:

    #përfshi // pas glm :: mat4 myMatrix = glm :: përkthe (glm :: mat4 (), glm :: vec3 (10.0 f, 0.0 f, 0.0 f)); glm :: vec4 myVector (10.0 f, 10.0 f, 10.0 f, 0.0 f); glm :: vec4 transformedVector = myMatrix * myVector;

    Në GLSL:

    vec4 transformedVector = myMatrix * myVector;

    Në fakt, këtë nuk do ta bëni kurrë në shader, më së shpeshti do të ekzekutoni glm :: translate () në C ++ për të llogaritur matricën, ia kaloni GLSL dhe në shader kryeni shumëzimin.

    Matrica e njësive

    Kjo është një matricë e veçantë që nuk bën asgjë, por ne po e prekim atë, pasi është e rëndësishme të mbani mend se A e shumëzuar me 1.0 jep A:

    Në C ++:

    glm :: mat4 myIdentityMatrix = glm :: mat4 (1.0 f);

    Matrica e shkallëzimit

    Duket gjithashtu e thjeshtë:

    Pra, nëse dëshironi të aplikoni shkallëzimin e vektorit (pozicioni ose drejtimi - nuk ka rëndësi) me 2.0 në të gjitha drejtimet, atëherë duhet:

    Vini re se w nuk ndryshon, dhe gjithashtu vini re se matrica e identitetit është një rast i veçantë i një matrice shkallëzuese me një faktor shkalle prej 1 në të gjitha akset. Gjithashtu, matrica e identitetit është një rast i veçantë i matricës së transferimit, ku përkatësisht (X, Y, Z) = (0, 0, 0).

    Në C ++:

    // shtoni #include dhe #përfshi glm :: mat4 myScalingMatrix = glm :: shkallë (2,0 f, 2,0 f, 2,0 f);

    Matrica e rrotullimit

    Më e vështirë se sa u diskutua më parë. Ne do t'i kapërcejmë detajet këtu pasi nuk keni nevojë t'i dini me siguri për përdorim të përditshëm. Për më shumë informacion, mund të ndiqni lidhjen Matricat dhe Kuaternionet FAQ (një burim mjaft i njohur dhe ndoshta gjuha juaj është e disponueshme atje)

    Në C ++:

    // shtoni #include dhe #përfshi glm :: vec3 myRotationAxis (??, ??, ??); glm :: rrotullim (kënd_në_gradë, myRotationAxis);

    Duke bashkuar transformimet

    Pra, tani ne mund të rrotullojmë, përkthejmë dhe shkallëzojmë vektorët tanë. Hapi tjetër do të ishte mirë për të kombinuar transformimet, i cili zbatohet duke përdorur formulën e mëposhtme:

    TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector;

    KUJDES! Kjo formulë në fakt tregon se shkallëzimi bëhet fillimisht, më pas rrotullimi dhe i fundit është përkthimi. Kështu funksionon shumëzimi i matricës.

    Sigurohuni që të mbani mend se në çfarë rendi është bërë e gjithë kjo, sepse rendi është me të vërtetë i rëndësishëm, në fund mund ta kontrolloni vetë:

    • Bëni një hap përpara dhe kthehuni majtas
    • Ktheni majtas dhe bëni një hap përpara

    Dallimi është vërtet i rëndësishëm për t'u kuptuar, pasi vazhdimisht do të përballeni me këtë. Për shembull, kur punoni me personazhet e lojës ose disa objekte, gjithmonë së pari zmadhoni, më pas rrotullojeni dhe vetëm më pas transferoni.

    Në fakt, renditja e mësipërme është ajo që ju nevojitet zakonisht për personazhet e luajtshëm dhe artikujt e tjerë: së pari rriteni atë nëse është e nevojshme; pastaj vendosni drejtimin e tij dhe më pas lëvizni atë. Për shembull, për një model anijeje (kthesa të hequra për thjeshtësi):

    • Mënyra e gabuar:
      • Ju e zhvendosni anijen në (10, 0, 0). Qendra e saj tani është 10 njësi nga origjina.
      • Ju shkallëzoni anijen tuaj 2 herë. Çdo koordinatë shumëzohet me 2 "në lidhje me origjinalin", që është larg ... Pra, ju e gjeni veten në një anije të madhe, por qendra e saj është 2 * 10 = 20. Jo ajo që dëshironit.
    • Rruga e duhur:
      • Ju shkallëzoni anijen tuaj 2 herë. Ju merrni një anije të madhe me qendër në origjinë.
      • Ju po mbani anijen tuaj. Është ende e njëjta madhësi dhe në distancën e duhur.

    Në C ++, me GLM:

    glm :: mat4 myModelMatrix = myTranslationMatrix * myRotationMatrix * myScaleMatrix; glm :: vec4 myTransformedVector = myModelMatrix * myOriginalVector;

    Në GLSL:

    mat4 transformim = mat2 * mat1; vec4 out_vec = transformim * in_vec;

    Matricat e botës, pamjes dhe projeksionit

    Për pjesën tjetër të këtij tutoriali, ne do të supozojmë se dimë të shfaqim modelin tonë të preferuar 3D nga Blender - majmuni Suzanne.

    Matricat e botës, pamjes dhe projeksionit janë një mjet i përshtatshëm për ndarjen e transformimeve.

    Matrica Botërore

    Ky model, ashtu si trekëndëshi ynë i kuq, vendoset nga një grup kulmesh, koordinatat e të cilave vendosen në lidhje me qendrën e objektit, domethënë, kulmi me koordinatat (0, 0, 0) do të jetë në qendër. të objektit.

    Më pas, ne do të donim të lëviznim modelin tonë, pasi lojtari e kontrollon atë duke përdorur tastierën dhe miun. Gjithçka që bëjmë është të shkallëzojmë, më pas të rrotullojmë dhe përkthejmë. Këto veprime kryhen për çdo kulm, në çdo kornizë (të kryera në GLSL, jo në C ++!) Dhe kështu modeli ynë lëviz në ekran.

    Tani majat tona janë në hapësirën botërore. Kjo tregohet nga shigjeta e zezë në figurë. Ne kemi lëvizur nga hapësira e objektit (të gjitha kulmet janë në lidhje me qendrën e objektit) në hapësirën botërore (të gjitha kulmet janë në lidhje me qendrën e botës).

    Ai tregohet skematikisht si më poshtë:

    Shiko matricën

    Për të cituar sërish Futurama:

    Motori nuk e lëviz anijen. Anija mbetet në të njëjtin vend, dhe motori lëviz universin rreth saj.

    Mundohuni ta imagjinoni këtë në lidhje me kamerën. Për shembull, nëse dëshironi të fotografoni një mal, atëherë nuk e lëvizni kamerën, por lëvizni malin. Kjo nuk është e mundur në jetën reale, por është tepër e thjeshtë në grafikën kompjuterike.

    Pra, fillimisht, kamera juaj është në qendër të sistemit të koordinatave botërore. Për të lëvizur botën ju duhet të futni një matricë tjetër. Le të themi se dëshironi ta lëvizni kamerën 3 njësi Djathtas (+ X), që është ekuivalenti i lëvizjes së gjithë botës 3 njësi Majtas (-X). Në kod, duket kështu:

    // Shto #include dhe #përfshi glm :: mat4 ViewMatrix = glm :: përkthe (glm :: mat4 (), glm :: vec3 (- 3,0 f, 0,0 f, 0,0 f));

    Përsëri, imazhi më poshtë e ilustron plotësisht këtë. Ne kaluam nga sistemi i koordinatave botërore (të gjitha kulmet janë të specifikuara në lidhje me qendrën e sistemit botëror) në sistemin e koordinatave të kamerës (të gjitha kulmet janë të specifikuara në lidhje me kamerën):

    Dhe ndërsa truri juaj po e tret këtë, ne do t'i hedhim një sy funksionit që na ofron GLM, ose më mirë glm :: LookAt:

    glm :: mat4 CameraMatrix = glm :: LookAt (Pozicioni i kamerës, // Pozicioni i kamerës në hapësirën botërore objektivi i kamerës, // Tregon se ku po shikoni në hapësirën botërore lartVektor // Një vektor që tregon lart. Zakonisht (0, 1, 0));

    Dhe këtu është një diagram që tregon se çfarë po bëjmë:

    Megjithatë, ky nuk është fundi.

    Matrica e projeksionit

    Pra, tani jemi në hapësirën e kamerave. Kjo do të thotë se kulmi që merr koordinatat x == 0 dhe y == 0 do të shfaqet në qendër të ekranit. Sidoqoftë, kur shfaqni një objekt, distanca nga kamera (z) gjithashtu luan një rol të madh. Për dy kulme me të njëjtat x dhe y, kulmi me vlerën më të lartë z do të shfaqet më afër se tjetri.

    Ky quhet projeksion perspektiv:

    Dhe për fatin tonë, një matricë 4x4 mund të bëjë këtë projeksion:

    // Krijon një matricë vërtet të vështirë për t'u lexuar, por megjithatë kjo është një matricë standarde 4x4 glm :: mat4 projeksionMatrix = glm :: perspektivë (glm :: radian (FoV), // Fusha vertikale e shikimit në radianë. Zakonisht midis 90 ° (shumë i gjerë) dhe 30 ° (i ngushtë) 4,0 f / 3,0 f, // Raporti i pamjes. Varet nga madhësia e dritares suaj. Vini re se 4/3 == 800/600 == 1280/960 0,1 f, // Pranë aeroplanit të prerjes. Duhet të jetë më i madh se 0. 100,0 f // Aeroplan me prerje të largët.);

    Ne kemi lëvizur nga Hapësira e Kamerës (të gjitha kulmet janë vendosur në raport me kamerën) në hapësirën homogjene (të gjitha kulmet janë në një kub të vogël. Çdo gjë brenda kubit shfaqet).

    Tani le t'i hedhim një sy imazheve të mëposhtme në mënyrë që të kuptoni më mirë se çfarë po ndodh me projeksionin. Para projeksionit kemi objekte blu në hapësirën e kamerës, ndërsa forma e kuqe tregon pamjen e kamerës, pra gjithçka që shikon kamera.

    Përdorimi i Matricës së Projeksionit ka efektin e mëposhtëm:

    Në këtë imazh, pamja e kamerës është një kub dhe të gjitha objektet janë të deformuara. Objektet më afër kamerës duken të mëdha, dhe ato më larg duken të vogla. Ashtu si në realitet!

    Kështu do të duket:

    Imazhi është katror, ​​kështu që transformimet matematikore të mëposhtme aplikohen për të shtrirë imazhin për t'iu përshtatur madhësisë aktuale të dritares:

    Dhe kjo imazh është ajo që do të shfaqet në të vërtetë.

    Bashkimi i transformimeve: matrica ModelViewProjection

    ... Vetëm transformimet standarde të matricës që tashmë i doni!

    // C ++: llogaritja e matricës glm :: mat4 MVPmatrix = projeksion * pamje * model; // Mbani mend! Në rend të kundërt!

    // GLSL: Aplikoni Matricën kulmi_i transformuar = MVP * në kulm;

    Duke i bashkuar të gjitha

    • Hapi i parë është krijimi i matricës sonë MVP. Kjo duhet të bëhet për çdo model që shfaqni.

    // Grupimi i projektimit: 45 ° Fusha e pamjes, raporti i pamjes 4: 3, Gama: 0,1 njësi<->100 njësi glm :: mat4 Projeksion = glm :: perspektivë (glm :: radian (45,0 f), 4,0 f / 3,0 f, 0,1 f, 100,0 f); // Ose, për ortokamerën glm :: mat4 Pamje = glm :: lookAt (glm :: vec3 (4, 3, 3), // Kamera është në koordinatat botërore (4,3,3) glm :: vec3 (0, 0, 0), // Dhe drejtuar kah origjina glm :: vec3 (0, 1, 0) // "Koka" është në krye); // Matrica e modelit: matrica e identitetit (Modeli është në origjinë) glm :: mat4 Model = glm :: mat4 (1.0 f); // Individualisht për secilin model // Matrica që rezulton ModelViewProjection, e cila është rezultat i shumëzimit të tre matricave tona glm :: mat4 MVP = Projeksion * Pamje * Model; // Mos harroni se shumëzimi i matricës bëhet në rend të kundërt

    • Hapi i dytë është t'ia kaloni këtë GLSL:

    // Merrni dorezën te ndryshorja në shader // Vetëm një herë gjatë inicializimit. GLuint MatrixID = glGetUniformLocation (ID-ja e programit, "MVP"); // Kalojmë transformimet tona në shader aktual // Kjo bëhet në ciklin kryesor, pasi çdo model do të ketë një matricë të ndryshme MVP (të paktën një pjesë të M) glUniformMatrix4fv (MatrixID, 1, GL_FALSE, & MVP [0] [0]);

    • Hapi i tretë është përdorimi i të dhënave të marra në GLSL për të transformuar kulmet tona.

    // Të dhënat hyrëse vertex, të ndryshme për të gjitha ekzekutimet e këtij shader. faqosja (vendndodhja = 0) në vec3 kulmiPosition_modelspace; // Vlerat që mbeten konstante për të gjithë rrjetin. uniforme mat4 MVP; kryesore e zbrazët () ( // Pozicioni i daljes së kulmit tonë: pozicioni MVP * gl_Position = MVP * vec4 (vertexPosition_modelspace, 1); )

    • Gati! Tani kemi të njëjtin trekëndësh si në mësimin 2, ende i vendosur në origjinë (0, 0, 0), por tani e shohim në perspektivë nga pika (4, 3, 3).

    Në mësimin 6, do të mësoni se si t'i ndryshoni në mënyrë dinamike këto vlera duke përdorur tastierën dhe miun për të krijuar kamerën që jeni mësuar të shihni në lojëra. Por së pari do të mësojmë se si t'u japim modeleve tona ngjyra (Mësimi 4) dhe tekstura (Mësimi 5).

    Detyrat

    • Provoni të ndryshoni vlerat e perspektivës glm ::
    • Në vend që të përdorni projeksionin perspektiv, provoni të përdorni ortografik (glm: ortho)
    • Modifiko ModelMatrix për të lëvizur, rrotulluar dhe shkallëzuar trekëndëshin
    • Përdorni detyrën e mëparshme, por me një renditje të ndryshme operacionesh. Kushtojini vëmendje rezultatit.

    Projeksion perspektiv

    Ne kemi konsideruar projeksione të rëndësishme të përdorura në gjeometrinë afine. Tani le të kalojmë në shqyrtimin e gjeometrisë së perspektivës dhe disa llojeve të reja të projeksionit.

    Në fotografi, piktura, ekran, imazhet na duken të natyrshme dhe korrekte. Këto imazhe quhen imazhe perspektive. Karakteristikat e tyre janë të tilla që objektet më të largëta përshkruhen në një shkallë më të vogël, linjat paralele në përgjithësi nuk janë paralele. Si rezultat, gjeometria e imazhit rezulton të jetë mjaft komplekse, dhe është e vështirë të përcaktohet madhësia e pjesëve të caktuara të objektit nga imazhi i përfunduar.

    Një projeksion konvencional perspektiv është një projeksion qendror në një plan me rreze të drejta që kalojnë nëpër një pikë - qendra e projeksionit. Njëra nga rrezet e projeksionit është pingul me rrafshin e projeksionit dhe quhet kryesore. Pika e kryqëzimit të kësaj rrezeje dhe planit të projeksionit është pika kryesore e figurës.

    Ekzistojnë tre sisteme koordinative. Zakonisht një programues punon dhe mban të dhëna për objektet gjeometrike në koordinatat botërore. Për të rritur realizmin në përgatitjen për shfaqjen e imazhit në ekran, të dhënat për objektet nga koordinatat botërore shndërrohen në koordinata pamjeje. Dhe vetëm në momentin e shfaqjes së imazhit drejtpërdrejt në ekranin e ekranit, ato kalojnë në koordinatat e ekranit, që janë numrat e pikselave të ekranit.

    Dy sistemet e para mund të përdoren në sistemet koordinative shumëdimensionale, por kjo e fundit vetëm në 2D. Operacionet janë të pakthyeshme, domethënë është e pamundur të rivendosni një imazh tre-dimensional nga një imazh projeksion dydimensional.

    Në këtë matricë, elementet a, d, e janë përgjegjës për shkallëzimin, m, n, L- për zhvendosje, fq, q, r- për projektim, s- për shkallë komplekse, NS- për rrotullim.

    Projeksioni me një pikë në rrafshin z = 0

    Thelbi i këtij projeksioni është si më poshtë: sa më i thellë të jetë objekti, aq më e madhe bëhet vlera e koordinatës z dhe emëruesi rz + 1, dhe, për rrjedhojë, sa më i vogël të duket objekti në rrafshin e projeksionit. Le të bëjmë llogaritje të thjeshta dhe t'i shpjegojmë ato në mënyrë grafike:
    ekuacioni x "/ F = x / (F + z pr) është ekuivalent me: x" = xF / (F + z pr) = x / (1 + z pr / F) = x / (1 + rz pr) , ku r = 1 / F, F - fokus.

    Për pikat që shtrihen në një vijë paralele me boshtin z, nuk humbën njëri pas tjetrit, përdoret projeksioni me një pikë në vijë (shih matricën e transformimit dhe oriz. 4.2); koordinata z është zhdukur, por duke qenë se objektet e largëta janë bërë më të vogla se ato të afërta, shikuesi ka një ndjenjë thellësie. Mos harroni: kjo është mënyra e parë për të përcjellë thellësinë në një avion!

    Artikujt kryesorë të lidhur