Poglavlje 2: 3D prostor i matematika
U ovom poglavlju učiš matematičke temelje na kojima počiva sve u 3D grafici. Koordinatni sistemi, vektori, matrice i transformacije — ovo je jezik kojim kompjuter opisuje 3D svet. Bez ovoga, ništa što sledi nema smisla. Ali ne brini — objasnićemo sve korak po korak, sa primerima iz realnog sveta.
Zašto je ovo poglavlje važno
Svaki put kad GPU nacrta trougao na ekranu, koristi matematiku iz ovog poglavlja. Svaki put kad pomeriš kameru, rotiraš objekat, ili skaliraj model — matrice. Svaki put kad engine izračuna da li svetlost pogađa površinu — vektori. Svaki put kad shader izračuna boju piksela — dot product.
Matematika 3D grafike nije apstraktna akademska disciplina. Ona je alat. Kao što stolar koristi čekić i testeru, grafički programer (i svako ko želi da razume renderovanje) koristi vektore, matrice i transformacije. Ne moraš da budeš matematičar — ali moraš da razumeš šta ovi koncepti rade i zašto postoje.
Ovo poglavlje pokriva tačno onoliko matematike koliko ti treba za razumevanje ostatka knjige — ni manje, ni više.
Koordinatni sistemi
Šta je koordinatni sistem?
Koordinatni sistem je način da opišeš gde se nešto nalazi u prostoru koristeći brojeve.
Zamisli da si u velikom praznom polju i hoćeš nekome da kažeš gde si. Možeš da kažeš "ispred velikog drveta" ili "blizu reke" — ali to je neprecizno. Koordinatni sistem ti daje precizan način: definišeš referentnu tačku (ishodište, origin), definišeš pravce (ose, axes), i definišeš jedinicu mere — i onda bilo koja pozicija može da se opiše brojevima.
2D koordinatni sistem
Počnimo sa dve dimenzije, jer je intuitivnije.
Dekartov (Cartesian) koordinatni sistem u 2D ima:
- Ishodište (origin) — tačka gde se ose seku, obično označena kao O ili (0, 0)
- X osa — horizontalna linija
- Y osa — vertikalna linija
- Jedinica mere — koliko "daleko" je 1 na svakoj osi
Svaka tačka u 2D prostoru može se opisati parom brojeva (x, y):
- x govori koliko je tačka udaljena horizontalno od ishodišta
- y govori koliko je tačka udaljena vertikalno od ishodišta
Na primer, tačka (3, 2) je 3 jedinice udesno i 2 jedinice nagore od ishodišta.
Ovo verovatno već znaš iz škole. Ali postoje detalji koji su bitni za kompjutersku grafiku:
Smer Y ose — u matematici, Y osa ide nagore (veći Y = gore). Ali u mnogim 2D grafičkim sistemima (uključujući većinu monitor koordinatnih sistema), Y osa ide nadole — (0,0) je u gornjem levom uglu ekrana, i veći Y znači niže na ekranu. Ovo je istorijski artefakt iz vremena CRT monitora gde je elektronski snop skenirao od gore ka dole.
Ova naizgled mala razlika je izvor beskonačne konfuzije i bagova. Zapamti je.
3D koordinatni sistem
Za trodimenzionalni prostor, dodajemo treću osu — Z osu — koja predstavlja dubinu.
Svaka tačka u 3D prostoru se opisuje trojkom brojeva (x, y, z).
Ali ovde nastaje problem: postoje dva načina da organizuješ tri ose, i oni nisu ekvivalentni.
Left-hand vs Right-hand koordinatni sistem
Zamislite da stojite u ishodištu koordinatnog sistema. X osa ide udesno, Y osa ide nagore. Pitanje je: kuda ide Z osa?
Right-hand (desnoručni) koordinatni sistem: Uzmi desnu ruku. Ispruži prste u smeru X ose (udesno). Savi ih prema Y osi (nagore). Tvoj palac pokazuje smer Z ose — prema tebi (iz ekrana).
Left-hand (levoručni) koordinatni sistem: Uradi isto sa levom rukom. Sada palac pokazuje od tebe (u ekran). Z osa ide u suprotnom smeru.
Ovo nije trivijalna razlika. Zamena sistema koordinata menja matematiku — cross product menja znak, rotacije idu u suprotnom smeru, normali se okreću. Mnogi bagovi u grafici nastaju kad se pomešaju koordinatni sistemi.
Različiti softveri koriste različite konvencije:
- OpenGL, Blender — desnoručni (right-hand), Y je gore
- DirectX, Unreal Engine — levoručni (left-hand), Z je gore
- Maya — desnoručni, Y je gore
- 3ds Max — desnoručni, Z je gore
Unreal Engine koristi levoručni koordinatni sistem gde je:
- X — napred (forward)
- Y — desno (right)
- Z — gore (up)
Ovo je bitno zapamtiti jer kad importuješ model iz Blender-a (koji koristi desnoručni sistem sa Z gore) u Unreal (levoručni sa Z gore), engine mora da uradi konverziju. Ponekad ta konverzija nije savršena — objekti se pojave rotirani za 90 stepeni, ili im normali budu okrenuti na pogrešnu stranu. Razumevanje koordinatnih sistema ti pomaže da dijagnostikuješ i rešiš takve probleme.
Zašto je bitan izbor koordinatnog sistema?
U suštini, matematika radi isto u oba sistema — ali se formule malo razlikuju. Kad čitaš dokumentaciju, tutoriale, ili izvorni kod, moraš da znaš koji sistem se koristi, inače ćeš dobiti pogrešne rezultate.
Konzistentnost je ključna. Nije bitno koji sistem koristiš, sve dok ga koristiš konzistentno.
Vektori
Šta je vektor?
Vektor je matematički objekat koji ima pravac i veličinu (magnitude), ali nema fiksnu poziciju.
Zamisli strelicu. Strelica pokazuje u nekom pravcu i ima neku dužinu. Ako pomeriš strelicu na drugo mesto a ne promeniš ni pravac ni dužinu, to je isti vektor. Vektor opisuje "koliko i kuda" — ne "gde".
U 3D grafici, vektor se obično zapisuje kao uređena trojka brojeva: v = (x, y, z). Ovi brojevi su komponente vektora i govore koliko vektor "ide" duž svake ose.
Na primer, vektor (3, 0, 0) ide 3 jedinice duž X ose — čisto horizontalno udesno. Vektor (0, 1, 0) ide jednu jedinicu nagore. Vektor (1, 1, 0) ide dijagonalno — jednu jedinicu udesno i jednu nagore.
Vektor vs tačka
Ovo je izvor česte konfuzije: i vektor i tačka se zapisuju kao trojka brojeva (x, y, z). U čemu je razlika?
Tačka (point) opisuje lokaciju u prostoru — "gde". Tačka (3, 5, 2) je konkretno mesto u 3D prostoru.
Vektor opisuje pomeranje — "koliko i kuda". Vektor (3, 5, 2) kaže "idi 3 udesno, 5 napred, 2 gore" — ali ne kaže odakle.
Veza između njih: ako imaš dve tačke A i B, vektor od A do B je B - A. Na primer, ako je A = (1, 2, 3) i B = (4, 6, 3), vektor od A do B je (4-1, 6-2, 3-3) = (3, 4, 0). Ovaj vektor kaže "da bi stigao od A do B, idi 3 udesno i 4 napred".
U kompjuterskom kodu, tačke i vektori se često čuvaju u istoj strukturi podataka (npr. FVector u Unreal Engine-u), pa je na programeru da vodi računa šta je šta. Ova razlika postaje kritična kad govorimo o homogenim koordinatama (kasnije u ovom poglavlju) i transformacijama.
Operacije sa vektorima
Sabiranje vektora
Ako imaš dva vektora a = (a₁, a₂, a₃) i b = (b₁, b₂, b₃), njihov zbir je:
a + b = (a₁ + b₁, a₂ + b₂, a₃ + b₃)
Vizuelno: staviš kraj prvog vektora na početak drugog, i rezultat ide od početka prvog do kraja drugog. Kao kad ideš 3 koraka napred, pa 2 koraka udesno — ukupno pomeranje je kombinacija oba.
Gde se koristi u grafici: Pomeranje objekata (translacija). Ako imaš poziciju objekta P = (10, 5, 0) i hoćeš da ga pomeriš za vektor v = (0, 0, 3), nova pozicija je P + v = (10, 5, 3).
Oduzimanje vektora
a - b = (a₁ - b₁, a₂ - b₂, a₃ - b₃)
Gde se koristi: Nalaženje pravca od jedne tačke do druge. Ako je igrač na poziciji P i neprijatelj na poziciji E, vektor od igrača do neprijatelja je E - P. Ovo ti daje pravac u kom treba gledati/pucati.
Množenje skalarom
Skalar je običan broj (ne vektor). Množenje vektora skalarom menja njegovu veličinu (dužinu) bez promene pravca:
k · a = (k · a₁, k · a₂, k · a₃)
Ako je k > 1, vektor se "rastegne" (postaje duži). Ako je 0 < k < 1, vektor se "skupi". Ako je k negativan, vektor se okrene u suprotnom smeru.
Gde se koristi: Skaliranje brzine, skaliranje sile, podešavanje intenziteta.
Veličina (magnitude/length) vektora
Dužina vektora a = (a₁, a₂, a₃) je:
|a| = √(a₁² + a₂² + a₃²)
Ovo je direktna primena Pitagorine teoreme, proširena na tri dimenzije. U 2D, Pitagorina teorema kaže da je hipotenuza pravouglog trougla c = √(a² + b²). U 3D, dodajemo treću dimenziju.
Gde se koristi: Izračunavanje udaljenosti između dve tačke (udaljenost = veličina vektora između njih). Provera da li je igrač unutar dometa oružja (da li je udaljenost manja od nekog praga). Normalizacija vektora.
Važna optimizaciona napomena: Izračunavanje korena (√) je relativno spora operacija. Kad samo poređuješ udaljenosti (npr. "da li je A bliže od B"), ne moraš da računaš koren — možeš da porediš kvadrate udaljenosti. Ovo je klasična optimizacija u game development-u:
Umesto: if (distance(A, B) < 100) → računa koren
Koristi: if (distanceSquared(A, B) < 10000) → nema korena, brže
Normalizacija
Normalizovan vektor (unit vector) je vektor čija je dužina tačno 1. Dobija se deljenjem vektora svojom dužinom:
â = a / |a|
Normalizovani vektor čuva pravac ali gubi informaciju o veličini. On kaže samo "kuda" — ne i "koliko daleko".
Gde se koristi svuda u grafici:
- Normali površina — vektor koji pokazuje "na koju stranu gleda" površina, uvek je normalizovan
- Pravci svetlosti — pravac od površine ka svetlu
- Pravac kamere — kuda kamera gleda
- Pravci kretanja — kad hoćeš da odvojiš "kuda se kreće" od "koliko brzo se kreće"
Normali su toliko fundamentalni za renderovanje da im je posvećen značajan deo poglavlja 3 i poglavlja 10.
Dot product (skalarni proizvod)
Ovo je verovatno najvažnija operacija u čitavoj 3D grafici. Ozbiljno — dot product je svuda.
Definicija
Za dva vektora a = (a₁, a₂, a₃) i b = (b₁, b₂, b₃):
a · b = a₁·b₁ + a₂·b₂ + a₃·b₃
Rezultat je skalar (jedan broj), ne vektor. Zato se zove "skalarni proizvod".
Geometrijsko značenje
Dot product se može izraziti i ovako:
a · b = |a| · |b| · cos(θ)
gde je θ ugao između dva vektora.
Ovo je ključna formula. Ona kaže da je dot product proporcionalan kosinusu ugla između dva vektora. Ako su oba vektora normalizovana (dužine 1), dot product JE kosinus ugla:
â · b̂ = cos(θ)
Šta to znači praktično?
- Ako su vektori paralelni (gledaju u istom smeru, θ = 0°): dot product = 1 (cos 0° = 1)
- Ako su pod pravim uglom (θ = 90°): dot product = 0 (cos 90° = 0)
- Ako gledaju u suprotnim smerovima (θ = 180°): dot product = -1 (cos 180° = -1)
Ovo ti daje neverovatno koristan alat: dot product ti govori koliko su dva vektora "usklađena".
Primene u grafici — zašto je ovo toliko bitno
1. Osvetljenje (Lambertian/diffuse lighting)
Najosnovniji model osvetljenja — koliko je površina osvetljena — zavisi od ugla između normale površine (N) i pravca ka svetlu (L). Što svetlo pogađa površinu direktnije (manji ugao), površina je svetlija.
Intenzitet = max(0, N · L)
Jedan dot product. To je cela formula za osnovni diffuse lighting. Kad je normala usmerena direktno ka svetlu (paralelni), rezultat je 1 — potpuna osvetljenost. Kad je svetlo paralelno sa površinom (90°), rezultat je 0 — nema osvetljenja. max(0, ...) osigurava da negativne vrednosti (svetlo pogađa zadnju stranu) budu tretirane kao 0 a ne kao negativno svetlo.
Ovo ćemo detaljno razraditi u poglavlju 10, ali zapamti — dot product je srce osvetljenja.
2. Provera vidljivosti (back-face culling)
Kad renderuješ 3D objekat, ne moraš da crtaš trouglove koji su okrenuti od kamere — ne vide se jer su na "pozadini" objekta. Kako znaš da li je trougao okrenut od kamere?
Izračunaj dot product normale trougla i vektora od trougla do kamere:
- Ako je > 0: trougao gleda ka kameri (nacrtaj ga)
- Ako je < 0: trougao gleda od kamere (preskoči ga — back-face)
Ova jednostavna provera (jedan dot product) eliminiše oko 50% svih trouglova u sceni — ogromna ušteda.
3. Specular refleksije
Spekularne refleksije (sjajni odsjaji na površinama) zavise od toga koliko je vektor refleksije blizu vektoru ka kameri. I to se — pogađaš — računa dot productom.
4. Fresnel efekat
Fresnelov efekat (površine su reflektivnije kad ih gledaš pod strmim uglom) koristi dot product između normale i pravca gledanja. Detaljno u poglavlju 11 o PBR-u.
5. Projekcija
Dot product se koristi za projekciju jednog vektora na drugi — "koliko vektor A ide u pravcu vektora B". Ovo je fundamentalno za mnoge proračune u grafici — od sekvenci, preko okluzije, do svakojakih shader operacija.
Projekcija a na b = (a · b̂) · b̂
gde je b̂ normalizovani b.
6. Provera "da li je ispred ili iza"
Dot product sa forward vektorom kamere ti kaže da li je objekat ispred ili iza kamere. Pozitivan = ispred, negativan = iza. Korisno za frustum culling i mnoge gameplay mehanike.
Cross product (vektorski proizvod)
Dok dot product daje skalar, cross product daje vektor.
Definicija
Za dva vektora a = (a₁, a₂, a₃) i b = (b₁, b₂, b₃):
a × b = (a₂·b₃ - a₃·b₂, a₃·b₁ - a₁·b₃, a₁·b₂ - a₂·b₁)
Geometrijsko značenje
Rezultat cross producta je vektor koji je normalan (okomit) na oba ulazna vektora. Njegov pravac se određuje pravilom desne ruke (u desnoručnom sistemu) ili leve ruke (u levoručnom), a njegova veličina je:
|a × b| = |a| · |b| · sin(θ)
što je jednako površini paralelograma koji formiraju vektori a i b.
Važno: Cross product NIJE komutativan!
a × b ≠ b × a
Zapravo:
a × b = -(b × a)
Rezultat je u suprotnom smeru. Ovo je čest izvor bagova — redosled operanada je bitan.
Primene u grafici
1. Izračunavanje normala
Ovo je najčešća primena. Ako imaš trougao definisan sa tri temena (V₀, V₁, V₂), normalu trougla dobijaš ovako:
- Izračunaj dva ivična vektora: E₁ = V₁ - V₀, E₂ = V₂ - V₀
- Cross product: N = E₁ × E₂
- Normalizuj: N̂ = N / |N|
Rezultat je vektor koji je okomit na površinu trougla — normala. Normala govori "na koju stranu gleda" trougao, što je ključno za osvetljenje.
2. Konstruisanje koordinatnog sistema
Ako imaš dva vektora koja definišu dve ose, cross product ti daje treću. Na primer, ako imaš "napred" vektor i "gore" vektor, cross product ti daje "desno" vektor. Ovo se koristi za konstruisanje TBN (Tangent-Bitangent-Normal) matrice koja je ključna za normal mapping (poglavlje 19).
3. Orijentacija (Winding order)
Redosled temena trougla (clockwise vs counter-clockwise) određuje na koju stranu gleda normala. Ovo je fundamentalno za back-face culling i pravilan prikaz geometrije.
4. Torque i rotacija
U fizičkim simulacijama, moment sile (torque) je cross product radijusa i sile.
Matrice
Matrice su alat koji nam omogućava da efikasno opišemo i kombinujemo transformacije u 3D prostoru. Ako su vektori "reči" jezika 3D grafike, matrice su "rečenice" — one opisuju kompleksne operacije u kompaktnom obliku.
Šta je matrica?
Matrica je pravougaona tabela brojeva. U 3D grafici najčešće koristimo:
- 3×3 matrice — za rotaciju i skaliranje
- 4×4 matrice — za sve transformacije uključujući translaciju (o čemu uskoro)
Na primer, 3×3 matrica izgleda ovako:
| m₀₀ m₀₁ m₀₂ |
| m₁₀ m₁₁ m₁₂ |
| m₂₀ m₂₁ m₂₂ |
gde indeksi predstavljaju red i kolonu (m_{red,kolona}).
Množenje matrice i vektora
Kad pomnožiš matricu M sa vektorom v, dobijaš novi vektor v' — transformisan vektor:
v' = M · v
Za 3×3 matricu i 3D vektor:
| v'x | | m₀₀ m₀₁ m₀₂ | | vx |
| v'y | = | m₁₀ m₁₁ m₁₂ | · | vy |
| v'z | | m₂₀ m₂₁ m₂₂ | | vz |
Svaka komponenta rezultata je dot product odgovarajućeg reda matrice sa ulaznim vektorom:
- v'x = m₀₀·vx + m₀₁·vy + m₀₂·vz
- v'y = m₁₀·vx + m₁₁·vy + m₁₂·vz
- v'z = m₂₀·vx + m₂₁·vy + m₂₂·vz
Zašto su matrice korisne?
Ključna stvar: kolone matrice definišu novi koordinatni sistem.
Identiteta matrica (identity matrix) ne radi ništa — ulaz = izlaz:
| 1 0 0 |
| 0 1 0 |
| 0 0 1 |
Kolone ove matrice su (1,0,0), (0,1,0), (0,0,1) — standardne ose X, Y, Z. Kad pomnožiš bilo koji vektor ovom matricom, dobiješ isti vektor nazad.
Ali ako promeniš kolone, definišeš nov koordinatni sistem i time transformišeš vektor. Ovo je suština svih transformacija u 3D grafici.
Množenje matrica
Ako imaš dve matrice A i B, njihov proizvod C = A · B je nova matrica koja predstavlja kombinovanu transformaciju — prvo B, pa onda A (redosled je bitan!).
Ovo je izuzetno moćno. Umesto da primenjuješ svaku transformaciju posebno na svaki vertex, možeš da kombinuješ sve transformacije u jednu matricu i primeniš tu jednu matricu. Ako imaš mesh sa 100.000 vertices-a i hoćeš da ga rotiraš, pa skaliraj, pa translatiraš — umesto 3 × 100.000 = 300.000 operacija, izračunaš jednu kombinovanu matricu (3 operacije) i primeniš je 100.000 puta. Ogromna ušteda.
Redosled množenja: A · B ≠ B · A (matrice generalno nisu komutativne). Ovo znači da redosled transformacija menja rezultat — isto kao u realnom svetu: rotiranje pa translacija daje drugačiji rezultat od translacije pa rotacije.
Transformacije
Tri fundamentalne transformacije u 3D grafici su: translacija (pomeranje), rotacija i skaliranje. Svaki objekat u sceni je transformisan nekom kombinacijom ove tri operacije.
Skaliranje (Scale)
Skaliranje menja veličinu objekta. Matrica skaliranja za faktore (sx, sy, sz) duž svake ose:
| sx 0 0 |
| 0 sy 0 |
| 0 0 sz |
- Uniformno skaliranje: sx = sy = sz — objekat se jednako menja u svim dimenzijama
- Neuniformno skaliranje: sx ≠ sy ≠ sz — objekat se različito menja u različitim dimenzijama (npr. rastegne se po X osi)
Neuniformno skaliranje može da uzrokuje probleme sa normalama — normala se ne transformiše istom matricom kao vertex, već inverznom transponovanom matricom. Ako ovo zvuči komplikovano, ne brini — detaljno u poglavlju 3. Za sada, zapamti: neuniformno skaliranje komplikuje stvari.
Rotacija (Rotation)
Rotacija menja orijentaciju objekta. Postoji matrica za rotaciju oko svake ose:
Rotacija oko Z ose za ugao θ:
| cos(θ) -sin(θ) 0 |
| sin(θ) cos(θ) 0 |
| 0 0 1 |
Rotacija oko X ose za ugao θ:
| 1 0 0 |
| 0 cos(θ) -sin(θ) |
| 0 sin(θ) cos(θ) |
Rotacija oko Y ose za ugao θ:
| cos(θ) 0 sin(θ) |
| 0 1 0 |
| -sin(θ) 0 cos(θ) |
Rotacija oko proizvoljne ose je kompleksnija — koristi se Rodriguesova formula ili kvaternioni (quaternions).
Gimbal lock — ako rotiraš objekat sekvencijalno oko tri ose (Euler angles), postoji problem: u određenim konfigurcijama dve ose se "poklope" i gubiš jedan stepen slobode. Na primer, ako rotiraš oko Y ose za 90°, X i Z rotacije postaju iste stvar. Ovo je poznat problem u avijaciji i 3D grafici.
Rešenje je korišćenje kvaterniona — matematičke strukture sa 4 komponente (w, x, y, z) koje predstavljaju rotaciju bez problema gimbal lock-a. Kvaternioni su standardni način predstavljanja rotacija u game engine-ima. Unreal Engine koristi FQuat za rotacije i interno konvertuje iz Euler uglova (koji su intuitivniji za ljude) u kvaternione (koji su bolji za računanje).
Kvaternioni imaju još jednu prednost: interpolacija (blending) između dve rotacije je trivijalna sa kvaternionima (Slerp — Spherical Linear Interpolation), a komplikovana i podložna artefaktima sa Euler uglovima. Ovo je ključno za animaciju — kad blenduješ između dva položaja, hoćeš glatku rotaciju.
Translacija (Translation)
I ovde nastaje problem. Translacija je pomeranje — dodavanje vektora na svaki vertex:
v' = v + t (gde je t vektor translacije)
Ali ovo nije matrično množenje! Sa 3×3 matricom, možeš da rotiraš i skaliraš, ali ne možeš da transliraš (pomeriš). Zašto? Jer matrično množenje uvek daje rezultat koji je linearna kombinacija ulaznih komponenti — i (0,0,0) uvek ostaje (0,0,0). Translacija pomera i ishodište, što 3×3 matrica ne može.
Rešenje: homogene koordinate.
Homogene koordinate
Motivacija
Želimo da sve transformacije (translacija, rotacija, skaliranje) možemo da predstavimo kao množenje matricom. To bi nam omogućilo da ih kombinujemo u jednu matricu. Ali 3×3 matrice ne mogu translaciju.
Trik: dodajemo četvrtu koordinatu.
Kako rade
Umesto tačke (x, y, z), koristimo (x, y, z, w).
Za tačke: w = 1 → (x, y, z, 1) Za vektore: w = 0 → (x, y, z, 0)
A umesto 3×3 matrice, koristimo 4×4 matricu:
| m₀₀ m₀₁ m₀₂ tx |
| m₁₀ m₁₁ m₁₂ ty |
| m₂₀ m₂₁ m₂₂ tz |
| 0 0 0 1 |
Gornji levi 3×3 deo je rotacija i skaliranje. Poslednja kolona (tx, ty, tz) je translacija.
Kad pomnožiš ovu matricu sa tačkom (x, y, z, 1):
x' = m₀₀·x + m₀₁·y + m₀₂·z + tx·1
y' = m₁₀·x + m₁₁·y + m₁₂·z + ty·1
z' = m₂₀·x + m₂₁·y + m₂₂·z + tz·1
w' = 1
Translacija (tx, ty, tz) se dodaje! Jer w = 1.
A kad pomnožiš sa vektorom (x, y, z, 0):
x' = m₀₀·x + m₀₁·y + m₀₂·z + tx·0
y' = m₁₀·x + m₁₁·y + m₁₂·z + ty·0
z' = m₂₀·x + m₂₁·y + m₂₂·z + tz·0
w' = 0
Translacija se ne dodaje! Jer w = 0.
Ovo je tačno ono što želimo:
- Tačke (pozicije) se pomeraju kad transliraš — jer tačka je lokacija u prostoru
- Vektori (pravci) se NE pomeraju kad transliraš — jer vektor opisuje pravac, ne poziciju. Pravac "udesno" je i dalje "udesno" bez obzira gde se nalaziš
Elegantan trik. Jedna dodatna dimenzija rešava ceo problem.
Perspektivna projekcija i w
Homogena koordinata w ima još jednu ključnu ulogu — perspektivnu projekciju. Kad projeciraš 3D scenu na 2D ekran sa perspektivom (bliže stvari izgledaju veće, daljine manje), delovi perspektivne transformacije koriste w komponentu.
Nakon perspektivne projekcije, w više nije 1 — sadrži informaciju o dubini. Da bi dobio konačne 2D koordinate, deliš x, y, z sa w — ovo se zove perspektivno deljenje (perspective divide) i daje efekt da dalji objekti izgledaju manji. Detaljno o ovome u poglavlju 6 o koordinatnim prostorima.
Zašto se 4×4 matrice koriste svuda
U Unreal Engine-u (i svim modernim engine-ima), gotovo sve transformacije se čuvaju kao 4×4 matrice (FMatrix u UE5). Razlog:
- Uniformnost — svaka transformacija (translate, rotate, scale, i njihove kombinacije) može se izraziti kao jedna 4×4 matrica
- Kompozicija — više transformacija se kombinuje prostim množenjem matrica u jednu
- GPU efikasnost — GPU je dizajniran da veoma brzo množi 4×4 matrice sa 4-komponentnim vektorima. Ovo je jedna od njegovih primarnih operacija.
Ceo rendering pipeline se može posmatrati kao niz matrničnih množenja:
- Model matrica (object → world space)
- View matrica (world → camera space)
- Projection matrica (camera → clip space)
Sve ovo su 4×4 matrice, i mogu se kombinovati u jednu — MVP (Model-View-Projection) matricu — koja jednim množenjem transformiše vertex iz prostora modela u prostor ekrana. O ovome detaljno u poglavlju 6.
Transform hijerarhije (Parent-child relationships)
U game engine-u, objekti često imaju hijerarhiju — "roditelj-dete" odnos. Kad pomeriš roditelja, dete se pomera sa njim.
Primer: auto. Telo auta je roditelj. Točkovi su deca. Kad pomeriš auto, točkovi se pomeraju sa njim. Ali točkovi mogu i da se rotiraju nezavisno (vrte se).
Matematički, ovo funkcioniše tako što svako dete čuva svoju lokalnu transformaciju — poziciju, rotaciju, skaliranje relativno na roditelja. Da bi dobio svetsku (world) transformaciju deteta, množiš njegovu lokalnu transformaciju sa svim roditeljskim transformacijama iznad njega:
WorldTransform(dete) = WorldTransform(roditelj) · LocalTransform(dete)
Ako imaš duboku hijerarhiju (roditelj → dete → unuk → praunuk...), množiš sve matrice niz lanac:
WorldTransform(praunuk) = Transform(roditelj) · Transform(dete) · Transform(unuk) · Transform(praunuk)
Ovo je elegantno i efikasno — kad pomeriš roditelja, ne moraš ručno da ažuriraš svako dete. Samo ažuriraš roditeljevu matricu, i kad ti treba svetska pozicija deteta, pomnožiš matrice.
Ali ovde se krije i performance zamka: duboke hijerarhije zahtevaju više matrničnih množenja. Za skeletal mesh sa stotinama kostiju, ovo može da bude značajan CPU trošak. Zato se u praksi teži "plitkim" hijerarhijama gde je moguće.
Inverzna matrica
Inverzna matrica M⁻¹ je matrica koja "poništava" efekat M:
M⁻¹ · M = I (identitet matrica)
Ako M rotira objekat 30° u smeru kazaljke na satu, M⁻¹ ga rotira 30° u suprotnom smeru. Ako M skalira za faktor 2, M⁻¹ skalira za faktor 0.5.
Gde se koristi:
-
View matrica — pozicioniranje kamere. Umesto da pomeriš ceo svet da bude ispred kamere, transformišeš sve inverznom transformacijom kamere. Ako je kamera na poziciji (10, 5, 3) i rotirana za 45°, view matrica je inverz te transformacije — pomeri svet za (-10, -5, -3) i rotiraj za -45°.
-
Unprojection — pretvaranje 2D koordinata ekrana nazad u 3D zrak (ray) u svetu. Ovo se koristi za picking (klik na objekat u sceni) i ray tracing.
-
Transformacija normala — normali se ne transformišu istom matricom kao verteksi. Koristi se inverzna transponovana matrica — (M⁻¹)ᵀ. Ovo osigurava da normala ostaje okomita na površinu čak i posle neuniformnog skaliranja.
Izračunavanje inverzne matrice je relativno skupa operacija (O(n³) generalno), ali za specifične tipove matrica postoje prečice. Na primer, inverz čiste rotacione matrice je njena transpozicija (zamena redova i kolona), što je trivijalno brzo.
Transponovanje matrice
Transpozicija matrice je zamena redova i kolona:
Originalna: Transponovana:
| a b c | | a d g |
| d e f | → | b e h |
| g h i | | c f i |
Za ortogonalnu matricu (matricu čiji su redovi i kolone međusobno okomiti i normalizovani — što važi za čiste rotacione matrice), transpozicija je jednaka inverzu:
Mᵀ = M⁻¹
Ovo je veoma korisno jer je transponovanje trivijalno brza operacija (samo presložiš podatke), dok je pravi inverz skup. U praksi, kad imaš čistu rotacionu matricu i treba ti inverz, samo je transponuješ.
Ostali matematički koncepti
Interpolacija — Lerp
Linearna interpolacija (Lerp) je jedan od najčešće korišćenih koncepata u grafici i game development-u. Formula:
lerp(A, B, t) = A + t · (B - A) = (1 - t) · A + t · B
gde je t vrednost od 0 do 1.
- t = 0 → rezultat je A
- t = 1 → rezultat je B
- t = 0.5 → rezultat je tačno na pola puta
- t = 0.25 → rezultat je četvrtina puta od A ka B
Lerp radi sa brojevima, vektorima, bojama — bilo čim gde ima smisla "tačka između". Koristi se za:
- Glatko pomeranje kamere
- Blending između animacija
- Interpolacija boja (gradient)
- Interpolacija atributa preko trougla (baricentrične koordinate — poglavlje 9)
- Praktično svaki "glatki prelaz" u igri
Clamping
Clamping ograničava vrednost na opseg:
clamp(x, min, max) = max(min, min(max, x))
Ako je x ispod min, vraća min. Ako je iznad max, vraća max. Inače vraća x.
Koristi se konstantno — na primer, da se intenzitet svetlosti ne spusti ispod 0 ili da UV koordinate ostanu u opsegu 0-1.
Saturate / Clamp01
Specijalan slučaj clampa — ograničenje na opseg [0, 1]:
saturate(x) = clamp(x, 0, 1)
Toliko je čest u shaderima da ima sopstvenu instrukciju na GPU-u — saturate() u HLSL-u je besplatan (nema dodatni cost).
Trigonometrija
Sin, cos, tan i njihovi inverzi se koriste za rotacije, projekcije, i mnoge druge proračune. Ključne veze:
- sin²(θ) + cos²(θ) = 1 (uvek!)
- sin(0) = 0, cos(0) = 1
- sin(90°) = 1, cos(90°) = 0
- atan2(y, x) — daje ugao vektora (x, y) u opsegu [-π, π], korisno za nalaženje ugla između dva vektora u 2D
U shaderima, trigonometrijske funkcije su relativno skupe. Kad je moguće, koriste se aproksimacije ili se rezultati predračunaju.
Radian vs Degree
Postoje dva načina merenja uglova:
- Stepeni (degrees): pun krug = 360°
- Radijani (radians): pun krug = 2π ≈ 6.283
Konverzija:
- radians = degrees × π / 180
- degrees = radians × 180 / π
Matematičke funkcije (sin, cos, itd.) u programskim jezicima i shaderima koriste radijane. Unreal Engine GUI prikazuje stepene (intuitivnije za ljude), ali interno radi u radijanima.
Prostori i bazni vektori
Koncept koji povezuje sve prethodno:
Svaki koordinatni sistem može da se opiše sa:
- Ishodištem (origin) — tačka (0, 0, 0)
- Tri bazna vektora — pravci osa X, Y, Z
Bazni vektori standardnog koordinatnog sistema su:
- e₁ = (1, 0, 0) — X osa
- e₂ = (0, 1, 0) — Y osa
- e₃ = (0, 0, 1) — Z osa
Svaki vektor u prostoru je linearna kombinacija baznih vektora:
v = v₁ · e₁ + v₂ · e₂ + v₃ · e₃
Kad kažeš "vektor (3, 4, 5)", zapravo kažeš "3 puta X bazni vektor + 4 puta Y bazni vektor + 5 puta Z bazni vektor".
Promena baze (change of basis) je transformacija iz jednog koordinatnog sistema u drugi. I to se radi — pogodioci — množenjem matricom. Kolone matrice su bazni vektori novog koordinatnog sistema izraženi u starom.
Ovo je ključno za razumevanje poglavlja 6 (Coordinate Spaces) — svaki prostor u render pipeline-u (object space, world space, view space...) je definisan svojim baznim vektorima, i prelazak između prostora je matrično množenje.
Matematika u praksi: GPU i preciznost
Postoji praktičan aspekt matematike koji je bitan za real-time grafiku: preciznost brojeva.
Floating-point (decimalni) brojevi
Računari čuvaju decimalne brojeve u formatu koji se zove floating-point (IEEE 754 standard). Postoje tri veličine:
- half (16-bit, FP16) — 3.3 decimalne cifre preciznosti
- float (32-bit, FP32) — 7 decimalnih cifara preciznosti
- double (64-bit, FP64) — 15 decimalnih cifara preciznosti
GPU-i su optimizovani za float (32-bit). Double (64-bit) je 2-4x sporiji na većini GPU-a. Half (16-bit) je često 2x brži od float-a i koristi polovinu memorije — pa se koristi kad god je preciznost dovoljna.
Precision problemi u grafici
Floating-point brojevi imaju ograničenu preciznost, i ta preciznost se smanjuje kako brojevi rastu. Broj 1.0 se čuva savršeno precizno, ali broj 16777216.0 (2²⁴) nema preciznost za decimale — dodavanje 1.0 na ovaj broj ne menja ga!
Ovo ima praktične posledice:
- Large world coordinates — ako imaš svet koji je 10 km velik i pozicije čuvaš u metrima kao float, na ivicama sveta (pozicije oko 10000.0) imaš preciznost od oko 1 mm. Za većinu igara to je OK, ali za simulacije ili igre sa ogromnim svetovima može biti problem. Unreal Engine 5.x uvodi Large World Coordinates — koristi double za svetske pozicije, ali float za lokalne proračune.
- Z-fighting — kad su dva objekta veoma blizu, floating-point preciznost depth buffer-a nije dovoljna da ih razlikuje, i delovi jednog i drugog naizmenično "pobede" — prikazuju se naizmenično, stvarajući neprijatan vizuelni efekat "treperenja". Ovo je direktna posledica ograničene preciznosti.
- Shader precision — u mobile shaderima, half (16-bit) preciznost može da uzrokuje vidljive artefakte, posebno za UV koordinate na daljim objektima ili za proračune koji zahtevaju akumulaciju malih vrednosti.
Opseg i preciznost depth buffer-a
Depth buffer (Z-buffer) je posebno osetljiv na preciznost jer mora da razlikuje udaljenosti objekata od kamere. Standardni depth buffer koristi floating-point format gde su vrednosti bliže kameri preciznije nego one dalje.
Ovaj nelinearan raspored preciznosti — mnogo preciznosti blizu, malo daleko — je razlog zašto igre imaju "near clip" i "far clip" ravni kamere. Ako staviš near clip na 0.01 i far clip na 100000, skoro sva preciznost otpada na prvih par metara, a na velikim udaljenostima dobijaš Z-fighting.
Reverse-Z je tehnika (koristi se u UE5) gde se depth buffer obrne — 1.0 je blizu, 0.0 je daleko. Ovo bolje raspoređuje preciznost jer floating-point ima više preciznosti oko 0 nego oko 1. Detalji u poglavlju 9.
Rezime ključnih pojmova
| Pojam | Značenje |
|---|---|
| Koordinatni sistem | Okvir za opisivanje pozicija u prostoru pomoću brojeva |
| Left-hand / Right-hand | Dve konvencije za orijentaciju 3D osa — UE5 koristi left-hand |
| Vektor | Matematički objekat sa pravcem i veličinom |
| Tačka | Pozicija u prostoru |
| Dot product | Operacija koja daje skalar — meri "usklađenost" dva vektora |
| Cross product | Operacija koja daje vektor okomit na dva ulazna vektora |
| Normalizacija | Skaliranje vektora na dužinu 1, čuvajući pravac |
| Matrica (4×4) | Tabela brojeva koja predstavlja transformaciju |
| Translacija | Pomeranje objekta |
| Rotacija | Okretanje objekta |
| Skaliranje | Promena veličine objekta |
| Homogene koordinate | 4-komponentne koordinate (x,y,z,w) koje omogućavaju translaciju kroz matrično množenje |
| Identitet matrica | Matrica koja ne menja ništa — ekvivalent broja 1 za množenje |
| Inverzna matrica | Matrica koja "poništava" drugu matricu |
| Kvaternion | 4-komponentna matematička struktura za predstavljanje rotacija bez gimbal lock-a |
| Lerp | Linearna interpolacija — glatki prelaz između dve vrednosti |
| Floating-point | Format za čuvanje decimalnih brojeva sa ograničenom preciznošću |
Sledeće poglavlje ulazi u svet mesh-eva i topologije — šta je zapravo 3D model, kako su organizovani njegovi sastavni delovi, i kako GPU čuva geometriju u memoriji.
📖 Dalje čitanje: