Poglavlje 09: Rasterizacija Detaljno

Poglavlje 09: Rasterizacija Detaljno


Uvod: Od trouglova do piksela -- najvazniji korak u renderovanju

U poglavlju 07 smo prosli kroz ceo render pipeline od pocetka do kraja -- od Application Stage-a, preko vertex shader-a, do output merger-a. Videli smo makro sliku. U poglavlju 08 smo zaronili u GPU arhitekturu i razumeli kako hardver fizicki izvrsava sve te operacije -- warp-ovi, SIMT, occupancy, latency hiding.

Sada je vreme da se posvetimo jednoj fazi pipeline-a koja je toliko fundamentalna, toliko kriticna, a toliko cesto preskocena u tutorijalima -- rasterizaciji.

Rasterizacija je proces u kome se trouglovi (koji su nakon vertex shader-a definisani u 2D prostoru ekrana) pretvaraju u fragmente -- potencijalne piksele koji ce proci kroz pixel shader i eventualno zavrsiti na ekranu. Ovo zvuci jednostavno, ali ispod haube krije se fascinantna matematika, pametne optimizacije, i mnostvo zamki koje direktno uticu na performanse vase igre.

Zamislite da ste arhitekta koji je nacrtao plan kuce (to su vasi trouglovi u screen space-u). Sada morate da prenesete taj plan na teren -- da odredite tacno koje plocice poda pripadaju kojoj prostoriji, kakva je boja svake plocice, i da li je neka plocica sakrivena ispod tepiha (depth test). To je, u sustini, rasterizacija.

U ovom poglavlju cemo detaljno obraditi:

Svaki od ovih koncepata ima direktne implikacije za performanse u Unreal Engine 5. Kada zavrsimo, imateri kristalno jasnu sliku o tome sta se desava izmedju vertex shader-a i pixel shader-a, i moci cete da donosite informisane odluke o optimizaciji.

Krenimo od pocetka -- od trougla koji je upravo izasao iz vertex shader-a.


9.1 Triangle Setup -- Priprema Trougla za Rasterizaciju

Sta se desava pre rasterizacije?

Kada vertex shader zavrsi svoj posao (kao sto smo detaljno opisali u poglavlju 07, sekcija 2), svaki vertex trougla ima svoju poziciju u clip space-u. Pipeline zatim obavlja perspective divide (deljenje sa w komponentom) da bi dosao do NDC (Normalized Device Coordinates), pa onda viewport transformaciju da bi dobio screen space koordinate -- stvarne piksel pozicije na ekranu (poglavlje 06, sekcija o Screen Space).

U tom trenutku, trougao je definisan sa tri temena u 2D prostoru ekrana:

V0 = (x0, y0)   -- sa dubinom z0 i svim atributima
V1 = (x1, y1)   -- sa dubinom z1 i svim atributima
V2 = (x2, y2)   -- sa dubinom z2 i svim atributima

Ali GPU ne moze odmah da pocne da "boji" piksele. Prvo mora da obavi triangle setup -- pripremu matematickih struktura koje ce mu omoguciti da efikasno odredi koji pikseli pripadaju trouglu i kako da interpolira atribute.

Sta triangle setup radi?

Triangle setup je fixed-function faza (kao sto smo objasnili u poglavlju 07, sekcija 8 -- ne programirate je, hardver je obavlja automatski) i ona priprema sledece:

1. Bounding box trougla

Prvo i najjednostavnije -- GPU izracunava pravougaonik koji obuhvata ceo trougao:

minX = min(x0, x1, x2)
maxX = max(x0, x1, x2)
minY = min(y0, y1, y2)
maxY = max(y0, y1, y2)

Zamislite da imate trougao nacrtan na papiru sa mrežom. Bounding box je najmanji pravougaonik koji potpuno obuhvata trougao. GPU nece testirati svaki piksel na ekranu -- testirace samo piksele unutar ovog pravougaonika. Na ekranu od 1920x1080, trougao koji zauzima 50x50 piksela ce biti testiran samo na 2.500 pozicija umesto na 2.073.600 (ceo ekran).

     ┌──────────────────────────┐
     │        Bounding Box      │
     │     /\                   │
     │    /  \                  │
     │   /    \                 │
     │  /  △   \               │
     │ /________\              │
     └──────────────────────────┘
     
     GPU testira samo piksele unutar bounding box-a

2. Edge equations (jednacine ivica)

Ovo je srce triangle setup-a. Za svaku od tri ivice trougla, GPU izracunava koeficijente jednacine ivice (edge equation). O ovome cemo detaljno u sledecem odeljku, ali ukratko -- to su linearne funkcije koje za svaku tacku (x, y) vracaju pozitivnu ili negativnu vrednost, u zavisnosti od toga na kojoj strani ivice se tacka nalazi.

3. Koeficijenti za interpolaciju atributa

Za svaki vertex atribut (boja, UV, normala, dubina, itd.), GPU izracunava koeficijente koji ce omoguciti efikasnu interpolaciju preko povrsine trougla. Ovo ukljucuje i pripremu za perspektivno-korektnu interpolaciju, o kojoj cemo takodje detaljno.

4. Face culling provera

Pre nego sto uopste krene u rasterizaciju, GPU proverava orijentaciju trougla. Ako je trougao okrenun "naopako" (backface), on se odbacuje i uopste ne ulazi u rasterizer. Ovo smo pomenuli u poglavlju 03 kada smo govorili o winding order-u -- redosled vertex-a odredjuje koja strana trougla je "prednja", a koja "zadnja". Front-face culling ili back-face culling eliminise trouglove koji nisu vidljivi, stedeci znacajnu kolicinu posla.

Matematicki, orijentacija se odredjuje znakom povrsinskog integrala (zapravo dvostruke povrsine trougla):

signed_area = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)

Ako je signed_area pozitivna, trougao je u smeru kazaljke na satu (CW). Ako je negativna, u suprotnom smeru (CCW). API (DirectX ili Vulkan) definise koja orijentacija je "front face" -- DirectX koristi CW kao front face po default-u.

Ako je signed_area jednaka nuli (ili blizu nule), trougao je degenerisan -- sva tri temena su kolinearna (u istoj liniji) i trougao nema povrsinu. Takav trougao se takodje odbacuje jer nema smisla rasterizovati nesto sto nema dimenziju.

Zasto je triangle setup fixed-function?

Ovo je odlican primer onoga o cemu smo govorili u poglavlju 07 (sekcija 8.2) -- triangle setup radi uvek isti posao, zahteva ogroman throughput (milioni trouglova po frejmu), i nema potrebe za kreativnom slobodom. Specijalizovani hardver moze da obavi ovaj posao mnogo brze i efikasnije od programabilnog shader-a.

Na modernim GPU-ovima, triangle setup je toliko brz da retko predstavlja bottleneck. Ali postoji jedan bitan izuzetak: ekstreman broj sitnih trouglova. Ako imate mesh sa milionima trouglova od kojih svaki pokriva samo 1-2 piksela, sam triangle setup moze postati znacajan trosak jer GPU mora da pripremi edge equations i interpolacione koeficijente za svaki trougao, bez obzira na to koliko je mali. Ovo je jedan od razloga zasto je Nanite u UE5 toliko vazan -- njegov software rasterizer moze efikasnije da obradi ovaj slucaj (poglavlje 08, sekcija 8.10).


9.2 Edge Equations -- Matematika Koja Odredjuje "Unutra" i "Spolja"

Fundamentalni problem

Evo osnovnog pitanja rasterizacije: imate trougao definisan sa tri temena na ekranu. Za svaki piksel unutar bounding box-a trougla, morate odgovoriti na pitanje: Da li je ovaj piksel unutar trougla ili nije?

Zvuci prosto, ali GPU mora da odgovori na ovo pitanje milionima puta po frejmu, za milione trouglova. Potreban nam je metod koji je matematicki elegantan i hardverski efikasan.

Jednacina ivice -- intuicija

Zamislite da stojite na jednoj ivici trougla -- recimo na ivici koja ide od temena V0 do temena V1. Ako pogledate ulevo, vidite unutrasnjost trougla. Ako pogledate udesno, vidite spoljasnjost.

Sada zamislite da imate magicnu formulu koja vam za svaku tacku na ekranu kaze: "ova tacka je levo od ivice" (pozitivna vrednost) ili "ova tacka je desno od ivice" (negativna vrednost). Ako je tacka tacno na ivici, formula vraca nulu.

To je edge equation -- linearna funkcija koja za svaku tacku (x, y) u 2D prostoru vraca vrednost ciji znak govori na kojoj strani ivice se tacka nalazi.

Matematicka definicija

Za ivicu od temena V0 = (x0, y0) do V1 = (x1, y1), edge equation je:

E01(x, y) = (y0 - y1) * x + (x1 - x0) * y + (x0 * y1 - x1 * y0)

Ili, u kompaktnijoj formi:

E01(x, y) = A * x + B * y + C

gde:
A = y0 - y1
B = x1 - x0
C = x0 * y1 - x1 * y0

Ovo je zapravo implicitna jednacina prave koja prolazi kroz V0 i V1. Vrednost E01(x, y) je proporcionalna rastojanju tacke (x, y) od te prave, sa znakom koji odredjuje stranu.

Analogija iz svakodnevnog zivota

Zamislite reku koja tece od tacke A do tacke B. Ako stojite i gledate niz reku (od A ka B), sve sto je levo od reke ima pozitivnu "rečnu koordinatu", a sve sto je desno ima negativnu. Reka sama je nula. Edge equation je upravo to -- "rečna koordinata" za svaku ivicu trougla.

Sign test -- da li je tacka unutar trougla?

Trougao ima tri ivice, dakle tri edge equations:

E01(x, y) = A01 * x + B01 * y + C01    (ivica V0 → V1)
E12(x, y) = A12 * x + B12 * y + C12    (ivica V1 → V2)
E20(x, y) = A20 * x + B20 * y + C20    (ivica V2 → V0)

Tacka (x, y) je unutar trougla ako i samo ako su sve tri edge functions istog znaka:

Tacka je unutar trougla ako:
    E01(x, y) >= 0  AND  E12(x, y) >= 0  AND  E20(x, y) >= 0

(pod uslovom da su koeficijenti postavljeni tako da pozitivna strana gleda ka unutrasnjosti trougla. Ako trougao ima suprotnu orijentaciju, uslov bi bio da su sva tri negativna.)

Ovo je sign test -- jednostavno proveravate znak tri linearne funkcije. Tri mnozenja, tri sabiranja, i tri poredjenja. Hardverski -- trivijalno.

            V0
            /\
           /  \
     E20  /    \  E01
         /  P?  \
        /________\
      V2   E12    V1

Za tacku P:
  E01(P) > 0  ✓
  E12(P) > 0  ✓
  E20(P) > 0  ✓
  → P je unutar trougla!

Inkrementalno izracunavanje

Evo gde edge equations postaju zaista elegantne. Posto je E(x, y) = A*x + B*y + C linearna funkcija, kada predjete sa piksela (x, y) na piksel (x+1, y), promena edge function-a je jednostavno:

E(x+1, y) = A * (x+1) + B * y + C = E(x, y) + A

Dakle, kada se pomeramo za jedan piksel udesno, edge function se menja za konstantu A. Kada se pomeramo za jedan piksel nadole, menja se za konstantu B:

E(x, y+1) = E(x, y) + B

Ovo znaci da GPU ne mora da radi puno mnozenje za svaki piksel -- jednom izracuna E za pocetni piksel, i onda samo sabira konstantu dok se krece kroz piksele. Ovo je razlog zasto se ovaj metod zove edge walking ili incremental edge evaluation, i razlog zasto je toliko efikasan u hardveru.

Top-left pravilo (Rasterization Rules)

Postoji jedan suptilan problem: sta se desava kada piksel lezi tacno na ivici trougla? Ako dva susedna trougla dele istu ivicu (sto je gotovo uvek slucaj u mesh-u -- setite se iz poglavlja 03 da trouglovi dele ivice), piksel na toj ivici bi matematicki "pripadao" oba trougla. Ako ga iscrtamo u oba -- dupliramo ga. Ako ga ne iscrtamo ni u jednom -- imamo rupu.

Resenje je top-left pravilo (top-left fill convention), koje koriste i DirectX i Vulkan:

Ovo garantuje da je svaki piksel dodeljen tacno jednom trouglu, bez rupa i bez duplikata. Hardver ovo implementira dodavanjem malog bias-a na edge equations za ivice koje nisu top ili left.

Zasto bas "top-left"? Nema posebno dubokog razloga -- moglo je biti i "bottom-right". Bitno je samo da postoji konzistentno pravilo koje oba susedna trougla postuju.

Edge equations u praksi

U stvarnom GPU hardveru, edge equations se ne evaluiraju piksel po piksel sekvencijalno. Moderan rasterizer evaluira edge equations za blok piksela istovremeno (obicno 4x4 ili 8x8 blok, zavisno od arhitekture). Ovo je moguce jer su edge equations linearne funkcije -- ako znate vrednosti na uglovima bloka, mozete brzo odrediti:

  1. Ceo blok je unutar trougla → obradi sve piksele bez daljih testova
  2. Ceo blok je van trougla → preskoci ceo blok
  3. Blok je delimicno unutar trougla → testiraj svaki piksel pojedinacno

Ovaj hijerarhijski pristup drasticno ubrzava rasterizaciju velikih trouglova -- umesto da testirate milion piksela, testirate hiljade blokova, od kojih su vecina trivijalno unutar ili van trougla.


9.3 Baricentricne Koordinate -- Tezinski Sistem Trougla

Sta su baricentricne koordinate?

Ovo je jedan od najvaznijih koncepata u celoj racunarskoj grafici, i zasluzuje pazljivu paznju.

Baricentricne koordinate (barycentric coordinates) su sistem koji opisuje poziciju bilo koje tacke unutar (ili van) trougla kao tezinski zbir tri temena trougla.

Svaka tacka P unutar trougla sa temenima V0, V1, V2 moze se izraziti kao:

P = u * V0 + v * V1 + w * V2

gde:
u + v + w = 1
u >= 0, v >= 0, w >= 0

Vrednosti (u, v, w) su baricentricne koordinate tacke P. One predstavljaju "koliko" svako teme doprinosi poziciji tacke P.

Analogija sa tegovima

Zamislite trougao kao tanku metalnu plocu (bez tezine). Na svako teme stavite teg:

Tacka ravnoteze (centar mase) te ploce sa tegovima bice tacno na poziciji P = uV0 + vV1 + w*V2. Otuda ime "baricentricne" -- od grcke reci "bary" (tezina) i "centrum" (centar). Bukvalno: koordinate centra tezine.

Ako stavite sav teg na jedno teme (recimo u=1, v=0, w=0), centar mase je na tom temenu. Ako rasporedite teg jednako (u=v=w=1/3), centar mase je u centru trougla (centroid).

Specijalni slucajevi

(u, v, w) = (1, 0, 0) → tacka je na temenu V0
(u, v, w) = (0, 1, 0) → tacka je na temenu V1
(u, v, w) = (0, 0, 1) → tacka je na temenu V2
(u, v, w) = (0.5, 0.5, 0) → tacka je na sredini ivice V0-V1
(u, v, w) = (1/3, 1/3, 1/3) → tacka je u centru trougla
         V0 (1,0,0)
          /\
         /  \
        / ·  \     ← centar (1/3, 1/3, 1/3)
       / (C)  \
      /________\
V2 (0,0,1)    V1 (0,1,0)

Tacka na sredini ivice V0-V1: (0.5, 0.5, 0)
Tacka bliza V2: npr. (0.1, 0.2, 0.7) -- V2 ima najveci uticaj

Veza sa edge equations

Evo elegantnog dela: baricentricne koordinate tacke (x, y) su direktno proporcionalne vrednostima edge equations za tu tacku!

u = E12(x, y) / (E12(x0, y0))    -- proporcionalno rastojanju od ivice V1-V2
v = E20(x, y) / (E20(x1, y1))    -- proporcionalno rastojanju od ivice V2-V0
w = E01(x, y) / (E01(x2, y2))    -- proporcionalno rastojanju od ivice V0-V1

Preciznije:

u = E12(x, y) / 2A
v = E20(x, y) / 2A
w = E01(x, y) / 2A

gde je 2A = E01(x2, y2) = E12(x0, y0) = E20(x1, y1) = dvostruka povrsina trougla

Ovo je izuzetno vazno iz prakticnog ugla: GPU vec izracunava edge equations za sign test (da li je piksel unutar trougla). Iz istih tih vrednosti, besplatno dobija baricentricne koordinate! Nema dodatnog posla -- iste brojeve koristi i za test pripadnosti i za interpolaciju.

Geometrijska interpretacija

Postoji i lepa geometrijska interpretacija baricentricnih koordinata. Baricentricna koordinata u za teme V0 je jednaka:

u = Povrsina(P, V1, V2) / Povrsina(V0, V1, V2)

Dakle, u je odnos povrsine "manjeg" trougla (formiranog od tacke P i suprotne ivice V1-V2) prema ukupnoj povrsini trougla. Sto je tacka P bliza temenu V0, veci je taj manji trougao (V1, V2, P), jer se P udaljava od ivice V1-V2.

         V0
          /\
         /  \
        / T₀ \      ← Povrsina T₀ = u * ukupna povrsina
       /------\
      /  · P   \     T₀ je trougao (P, V1, V2)
     /    T₁ T₂ \    T₁ je trougao (V0, P, V2) → v
    /____________\   T₂ je trougao (V0, V1, P) → w
  V2              V1

Zasto su baricentricne koordinate fundamentalne?

Baricentricne koordinate su temelj za interpolaciju svih vertex atributa preko povrsine trougla. Ali da bismo to razumeli, moramo prvo da definisemo sta znaci "interpolacija atributa" -- sto je tema sledeceg odeljka.


9.4 Interpolacija Atributa -- Kako Podaci "Teku" Kroz Trougao

Problem

Setite se iz poglavlja 03 i 07 da svaki vertex nosi skup atributa -- poziciju, normalu, UV koordinate, boju, tangent, itd. Vertex shader procesira ove atribute i proizvodi izlazne vrednosti za svako teme trougla.

Ali sta je sa pikselima izmedju temena? Pixel shader treba da zna boju, UV, normalu i ostale atribute za svaki piksel trougla, ne samo za tri temena.

Zamislite ovako: imate tri temena trougla i svako teme ima svoju boju -- V0 je crveno, V1 je zeleno, V2 je plavo. Sta je boja piksela koji je tacno u sredini trougla? Intuitivan odgovor je "mesavina sva tri" -- i to je tacno. Ali koliko svake boje? Tu na scenu stupaju baricentricne koordinate.

Linearna interpolacija pomocu baricentricnih koordinata

Za bilo koji atribut A koji ima vrednosti A0, A1, A2 na temenima V0, V1, V2, interpolirana vrednost u tacki sa baricentricnim koordinatama (u, v, w) je:

A(P) = u * A0 + v * A1 + w * A2

Ovo funkcionise za sve tipove atributa:

Boja:     color(P) = u * color0 + v * color1 + w * color2
UV:       uv(P) = u * uv0 + v * uv1 + w * uv2
Normala:  normal(P) = u * normal0 + v * normal1 + w * normal2
Dubina:   z(P) = u * z0 + v * z1 + w * z2
Tangent:  tangent(P) = u * tangent0 + v * tangent1 + w * tangent2
... i tako za svaki atribut

Vizuelni primer: interpolacija boje

         V0 (crvena)
          /\
         /  \
        / ·  \      ← centar: mešavina sva tri (≈ tamno siva/braon)
       /  C   \
      /________\
V2 (plava)    V1 (zelena)

Tacka blizu V0:     pretezno crvena
Tacka blizu V1:     pretezno zelena
Tacka na ivici V0-V1: mesavina crvene i zelene (zuta)
Centar trougla:      jednaka mesavina sva tri

Ovo je upravo ono sto vidite kada u debug modu posmatrate vertex colors na mesh-u -- GPU automatski interpolira boje izmedju temena, stvarajuci gladak prelaz.

Interpolacija UV koordinata

Ovo je posebno vazno za teksturiranje (poglavlje 04 i 05). Svako teme ima UV koordinate koje govore koji deo teksture se preslikava na to teme. GPU interpolira UV koordinate za svaki piksel trougla, i pixel shader onda koristi te interpolirane UV-ove da uzorkuje teksturu.

         V0 (uv: 0.0, 0.0)
          /\
         /  \
        / ·P \      P(uv) = u * (0.0, 0.0) + v * (1.0, 0.0) + w * (0.0, 1.0)
       /      \     Ako je (u, v, w) = (0.5, 0.25, 0.25):
      /________\    P(uv) = (0.25, 0.25)
V2 (uv: 0.0, 1.0)  V1 (uv: 1.0, 0.0)

Interpolacija normala -- Phong shading

Interpolacija normala je ono sto omogucava Phong shading (ili tacnije, Phong interpolation -- ne mesajte sa Phong lighting modelom). Umesto da ceo trougao ima jednu normalu (flat shading), svako teme ima svoju normalu i GPU ih interpolira, stvarajuci iluziju glatke povrsine.

Ali ovde postoji jedna zamka: interpolirana normala nije automatski normalizovana! Ako V0 ima normalu (0, 1, 0) i V1 ima normalu (1, 0, 0), interpolacija na sredini ivice daje (0.5, 0.5, 0), cija duzina je ~0.707, a ne 1. Zato pixel shader mora da renormalizuje interpoliranu normalu pre upotrebe u proracunima osvetljenja:

// U pixel shader-u:
float3 normal = normalize(input.Normal);  // OBAVEZNO!

Bez ove normalizacije, osvetljenje ce biti pogresno -- povrsine ce izgledati tamnije na mestima gde je normala sub-normalizovana.

Sta se sve interpolira?

Evo tipicnih atributa koji se interpoliraju u modernom rendering-u (posebno u UE5-ovom deferred rendering pipeline-u):

Atribut Tip Koristi se za
Position (World) float3 Proracuni osvetljenja, atmosferski efekti
Normal float3 Osvetljenje, refleksije
Tangent float3 Normal mapping (TBN matrica)
UV0 float2 Primarno teksturiranje
UV1 float2 Lightmap koordinate
UV2-7 float2 Dodatni UV kanali
Vertex Color float4 Blending, maskiranje, vertex painting
View Direction float3 Specular, Fresnel
Fog Factor float Atmosferski fog
Clip-space Z/W float Dubina, perspektivna korekcija
Custom outputs razlicito Sta god vertex shader proizvede

Svaki od ovih atributa se interpolira nezavisno, koristeci iste baricentricne koordinate. GPU ovo radi paralelno za sve atribute -- sto je jedan od razloga zasto je specijalizovan hardver (interpolatori u rasterizer-u) toliko efikasniji od generalne compute logike.


9.5 Perspektivno-Korektna Interpolacija -- Zasto Naivna Interpolacija Nije Dovoljna

Problem sa linearnom interpolacijom u perspektivi

Sve sto smo do sada opisali o interpolaciji je ispravno za ortografsku projekciju (gde nema perspektivnog skracenja). Ali za perspektivnu projekciju -- koja je standardna u igrama -- naivna linearna interpolacija u screen space-u daje pogresne rezultate.

Zasto? Zato sto perspektivna projekcija nije linearna transformacija u smislu koji bi sacuvao ravnomerno rastojanje u dubinu. Objekat koji je dva puta dalje od kamere izgleda dva puta manji, ali njegov deo blizi kameri zauzima vise piksela nego deo dalje od kamere. Linearna interpolacija u screen space-u bi tretirala oba dela jednako, sto dovodi do vizuelnih artefakata.

Klasicni primer: tekstura na podu

Zamislite sah tablu na podu, gledanu iz perspektive:

Kako treba da izgleda:           Kako izgleda sa naivnom interpolacijom:
┌─────────────────┐              ┌─────────────────┐
│ ▪ ▫ ▪ ▫ ▪ ▫ ▪ ▫│              │ ▪  ▫  ▪  ▫  ▪  │  ← dalji deo
│  ▪  ▫  ▪  ▫  ▪ │              │  ▪  ▫  ▪  ▫  ▪  │     (treba da
│   ▪   ▫   ▪   ▫│              │   ▪   ▫   ▪   ▫ │      bude stisnuti)
│    ▪    ▫    ▪  │              │    ▪    ▫    ▪   │
│     ▪     ▫     │              │     ▪     ▫      │  ← blizi deo
└─────────────────┘              └─────────────────┘
  KOREKTNO                         POGRESNO (afino)
  (perspektivno-korektno)          (tekstura "pliva")

Razlika je dramaticna: sa naivnom interpolacijom, tekstura "pliva" i ne prati perspektivno skracenje. Ovaj artefakt je bio vidljiv na ranim konzolama (npr. originalni PlayStation 1) koje su koristile afinu interpolaciju umesto perspektivno-korektne.

Matematicko objasnjenje

Problem je sledeci: u screen space-u, linearna interpolacija izmedju dva temena daje:

Linearna u screen space:
uv_screen(t) = (1-t) * uv0 + t * uv1

Ali UV koordinate treba da budu linearne u world space-u, ne u screen space-u. Zbog perspektivne projekcije (deljenje sa w), ono sto je linearno u world space-u nije linearno u screen space-u.

Resenje: 1/w korekcija

Kljucno otkrice je sledece: iako atributi nisu linearni u screen space-u nakon perspektivne projekcije, atributi podeljeni sa w jesu linearni u screen space-u. Takodje, 1/w sam po sebi je linearan u screen space-u.

Dakle, umesto da interpoliramo atribut A direktno, interpoliramo A/w i 1/w, pa rekonstruisemo A:

Za svako teme, pripremimo:
  A0' = A0 / w0
  A1' = A1 / w1
  A2' = A2 / w2
  
  inv_w0 = 1 / w0
  inv_w1 = 1 / w1
  inv_w2 = 1 / w2

Za piksel sa baricentricnim koordinatama (u, v, w_bary):
  A_interp = u * A0' + v * A1' + w_bary * A2'      // interpoliramo A/w
  inv_w_interp = u * inv_w0 + v * inv_w1 + w_bary * inv_w2  // interpoliramo 1/w
  
  A_final = A_interp / inv_w_interp                  // rekonstrukcija

Ovo daje matematicki korektnu perspektivnu interpolaciju.

Implementacija u hardveru

Moderan GPU hardver ovo radi automatski. Vi ne morate da razmisljate o 1/w korekciji u vasem shader kodu -- rasterizer to obavlja za vas. Svaki atribut koji vertex shader prosledi kao izlaz (osim onih eksplicitno oznacenih sa noperspective modifikatorom u HLSL/GLSL) se automatski perspektivno-korektno interpolira.

struct VSOutput
{
    float4 Position : SV_POSITION;    // Pozicija u clip space (obavezno)
    float2 UV : TEXCOORD0;            // Automatski perspektivno-korektno interpoliran
    float3 Normal : NORMAL;           // Automatski perspektivno-korektno interpoliran
    
    noperspective float2 ScreenUV : TEXCOORD1;  // Linearno (BEZ perspektivne korekcije)
};

noperspective modifikator se koristi retko -- uglavnom za UI elemente ili screen-space efekte gde perspektivna korekcija nije potrebna.

Zasto je ovo vazno za UE5?

U Unreal Engine 5, perspektivno-korektna interpolacija je posebno vazna za:

  1. Teksturiranje: Bez nje, teksture na podovima, zidovima i svakoj velikoj povrsini bi "plovile" i izgledale pogresno.

  2. Normal mapping: Normala i tangent vektori moraju biti korektno interpolirani da bi normal mape izgledale ispravno. Pogresna interpolacija bi dovela do artefakata u osvetljenju.

  3. Parallax mapping i displacement: Ove tehnike su izuzetno osetljive na preciznost interpolacije jer koriste dubinsku informaciju za pomeranje UV koordinata.

  4. Decali: Decal projekcija zavisi od korektne interpolacije world-space pozicije.

Srecom, GPU ovo radi automatski, pa u 99% slucajeva ne morate o tome da razmisljate. Ali vazno je razumeti zasto to postoji -- posebno ako radite sa custom shaderima ili debugging-ujete artefakte u materijalima.


9.6 Early-Z i Hi-Z -- Spasavanje GPU-a Od Nepotrebnog Posla

Problem koji Early-Z resava

Zamislite ovaj scenario: imate zid blizu kamere, i iza njega je kompleksan grad sa hiljadama objekata. Standardni pipeline bi radio ovako:

  1. Rasterizuj trougao zgrade iza zida
  2. Pokreni skup pixel shader za svaki piksel te zgrade (teksture, osvetljenje, refleksije...)
  3. U output merger-u, uporedi dubinu sa depth buffer-om
  4. Shvati da je zid ispred -- odbaci piksel
  5. Sav posao pixel shader-a je bio uzaludan

Mnozite ovo sa hiljadama objekata iza zida i imate ogroman gubitak GPU resursa. Pixel shader je najskuplja programabilna faza pipeline-a (kao sto smo videli u poglavlju 07, sekcija 6.3), i pokretanje tog shader-a za piksele koji ce ionako biti odbaceni je cist rastur.

Early-Z -- resenje

Early-Z (ili Early Depth Test) je optimizacija gde se depth test pomera ispred pixel shader-a umesto iza njega:

Standardni redosled:
  Rasterizer → Pixel Shader → Depth Test → Framebuffer
                  ↑                ↑
              SKUP posao      Moze odbaciti piksel
                              (posao uzalud!)

Sa Early-Z:
  Rasterizer → Depth Test → Pixel Shader → Framebuffer
                   ↑              ↑
              Jeftina provera  Pokrece se SAMO
              (odbaci rano!)   za vidljive piksele

Sa Early-Z, GPU prvo proveri da li je fragment ispred onoga sto je vec u depth buffer-u. Ako nije -- odmah ga odbaci, bez pokretanja pixel shader-a. Ovo moze eliminisati ogromne kolicine nepotrebnog posla.

Koliko Early-Z pomaze?

Zamislite scenu sa prosecnim overdraw-om od 3x (svaki piksel se u proseku iscrta 3 puta). Bez Early-Z, pixel shader se pokrece za svaki fragment, dakle 3x vise nego sto je potrebno. Sa Early-Z i front-to-back sortiranjem (najblizi objekti se crtaju prvi), vecina kasnijih fragmenata biva odbijena pre pixel shader-a.

U praksi, Early-Z moze da umanji pixel shader workload za 50-80% u tipicnim scenama. To je ogromna usteda -- razlika izmedju 30 FPS i 60 FPS u sceni sa mnogo preklapanja.

Kada Early-Z NE radi?

Postoje situacije u kojima GPU ne moze da koristi Early-Z, jer ne moze unapred da zna da li ce fragment preziveti pixel shader:

1. Alpha test / clip() / discard

Ako pixel shader koristi clip() ili discard da bi odbacio fragmente na osnovu alpha vrednosti (npr. vegetacija sa alpha mask-om), GPU ne moze da uradi Early-Z jer ne zna da li ce fragment biti odbacen u shader-u:

// Ovaj shader ONEMOGUCAVA Early-Z!
float4 PS_Main(VSOutput input) : SV_Target
{
    float4 color = Texture.Sample(Sampler, input.UV);
    clip(color.a - 0.5);  // Odbaci piksel ako je alpha < 0.5
    return color;
}

GPU ne moze da zna alpha vrednost pre pokretanja pixel shader-a (jer je alpha u teksturi), pa ne moze da odluci da li fragment treba odbaciti. Mora da pokrene pixel shader, pa tek onda da radi depth test.

Prakticna posledica za UE5: Masked materijali (koji koriste Opacity Mask) su skuplji od Opaque materijala upravo iz ovog razloga. Ne samo da pixel shader ima dodatnu clip() instrukciju, vec gubi Early-Z optimizaciju. Ovo je razlog zasto treba koristiti Masked materijale samo kada je zaista potrebno (vegetacija, ogradje, itd.) i zasto je UE5-ov rendering tako agresivan u sortiranju opaque objekata front-to-back.

2. Pixel shader piše dubinu (SV_Depth)

Ako pixel shader eksplicitno menja dubinu fragmenta:

// Ovaj shader TAKODJE ONEMOGUCAVA Early-Z!
float4 PS_Main(VSOutput input, out float depth : SV_Depth) : SV_Target
{
    depth = CustomDepthCalculation(input);  // Shader menja dubinu
    return CalculateColor(input);
}

GPU ne moze da zna dubinu pre nego sto shader zavrsi, pa Early-Z ne moze da se primeni.

3. UAV/RWTexture pristup u pixel shader-u

Ako pixel shader pise u unordered access view (UAV) ili read-write teksturu, GPU mora da pokrene shader za svaki fragment da bi se ti write-ovi desili, cak i ako fragment ne prodje depth test.

Hi-Z (Hierarchical-Z) -- Early-Z na steroidima

Early-Z radi piksel po piksel. Hi-Z (Hierarchical-Z buffer) radi na blokovima piksela -- obicno 8x8, 16x16 ili vece grupe.

Ideja je sledeca: GPU cuva hijerarhijsku (mipmap-like) verziju depth buffer-a. Svaki nivo hijerarhije cuva najdalju dubinu bloka piksela na nizem nivou:

Level 0 (full resolution):
┌──┬──┬──┬──┐
│.2│.3│.5│.6│
├──┼──┼──┼──┤
│.1│.4│.7│.8│
├──┼──┼──┼──┤
│.3│.2│.9│.5│
├──┼──┼──┼──┤
│.4│.6│.3│.4│
└──┴──┴──┴──┘

Level 1 (2x2 blokovi, cuva MAX):
┌─────┬─────┐
│ .4  │ .8  │     .4 = max(.2, .3, .1, .4)
├─────┼─────┤     .8 = max(.5, .6, .7, .8)
│ .6  │ .9  │     itd.
└─────┴─────┘

Level 2 (ceo ekran):
┌───────────┐
│    .9     │     .9 = max svih piksela
└───────────┘

Kada trougao stigne na rasterizaciju, GPU prvo proverava njegov bounding box (koji smo izracunali u triangle setup-u) protiv Hi-Z buffer-a:

  1. Nadje odgovarajuci nivo Hi-Z hijerarhije za velicinu bounding box-a
  2. Procita najdalju dubinu (z_max) u tom regionu
  3. Uporedi sa najblizom dubinom trougla (z_min)
  4. Ako je z_min trougla dalji od z_max u Hi-Z bufferu → ceo trougao je sakriven! Preskoci ga kompletno.

Ovo je izuzetno mocna optimizacija jer moze da odbaci cele trouglove (ili cele blokove piksela trougla) jednom proverom, umesto da testira svaki piksel pojedinacno.

Hi-Z test:

Trougao sa z_min = 0.7:

Hi-Z buffer za taj region: z_max = 0.5

0.7 > 0.5 → trougao je IZA svega u tom regionu → PRESKOCI!
(ne treba pokretati ni rasterizaciju ni pixel shader)

Hi-Z u Unreal Engine 5

UE5 intenzivno koristi Hi-Z za occlusion culling na GPU-u. Depth prepass (prvi prolaz koji samo popuni depth buffer) generise Hi-Z piramidu koja se onda koristi za:

  1. Hardware Hi-Z (rasterizer level): Automatska optimizacija koju radi GPU hardver
  2. Software Hi-Z occlusion (engine level): UE5 cita Hi-Z buffer sa prethodnog frejma i koristi ga za culling objekata pre nego sto uopste posalje draw call-ove GPU-u (temporalni Hi-Z reprojection)

Ovo je jos jedan primer sinergije izmedju CPU (engine) i GPU optimizacija -- depth buffer generisan u jednom frejmu pomaze da se eliminise nepotreban posao u sledecem.

Depth prepass -- strategija za maksimizovanje Early-Z

Depth prepass (ili Z-prepass) je tehnika gde se scena renderuje u dva prolaza:

  1. Z-prepass: Renderuj sve opaque objekte sa minimalnim pixel shader-om (ili potpuno praznim) -- samo popuni depth buffer
  2. Color pass: Renderuj sve opaque objekte sa punim materijalima, ali sada Early-Z odbacuje sve sto je sakriveno

Prednost: u color pass-u, nijedan piksel ne prolazi nepotrebno kroz skup pixel shader jer je depth buffer vec potpuno popunjen i Early-Z precizno odbacuje sve sakrivene fragmente.

Mana: z-prepass dodaje overhead -- sva geometrija se procesira dva puta kroz vertex shader i rasterizer. Ovo se isplati samo ako je pixel shader dovoljno skup i ako ima dovoljno overdraw-a da Early-Z usteda nadmasi overhead duplog geometrijskog procesiranja.

UE5 koristi depth prepass po default-u za deferred rendering, sto je razumna odluka za vecinu scena jer su materijali u modernim igrama dovoljno kompleksni da je usteda od Early-Z-a gotovo uvek veca od overhead-a.


9.7 Depth Buffer (Z-Buffer) Detaljno

Fundamentalni problem vidljivosti

Jedan od najstarijih problema u racunarskoj grafici je problem vidljivosti: kada se vise objekata projektuje na isti piksel ekrana, koji objekat treba prikazati?

Pre depth buffer-a, koriscen je Painter's algorithm -- crtanje objekata od najdaljih ka najbliziim, tako da blizi objekti "premazuju" dalje. Ovo ima dva velika problema:

  1. Sortiranje je skupo: Treba sortirati sve trouglove po dubini, sto je O(n log n) za n trouglova
  2. Nemoguci slucajevi: Dva trougla mogu da se presecaju (interpenetriraju), i tada nijedan redosled crtanja nije ispravan
Problem interpenetrirajucih trouglova:

    ╲   A   ╱
     ╲     ╱
      ╲   ╱    A je ispred u gornjoj polovini,
   ────╲─╱──── B je ispred u donjoj polovini.
      ╱ ╲      Nijedan redosled crtanja nije ispravan!
     ╱   ╲
    ╱  B  ╲

Z-buffer -- elegantno resenje

Z-buffer (depth buffer) je resenje koje je predlozio Edwin Catmull 1974. godine, i od tada je standard u real-time grafici. Ideja je jednostavna:

  1. Pored color buffer-a (koji cuva boju svakog piksela), alociraj depth buffer iste rezolucije koji cuva dubinu svakog piksela
  2. Inicijalizuj depth buffer na maksimalnu dubinu (najdalja moguca vrednost)
  3. Za svaki fragment koji rasterizer proizvede: a. Izracunaj njegovu dubinu (Z vrednost) b. Uporedi sa dubinom vec zapisanom u depth buffer-u na toj piksel lokaciji c. Ako je novi fragment blizi kameri -- zapisi njegovu boju u color buffer i njegovu dubinu u depth buffer d. Ako nije -- odbaci fragment
Depth buffer u akciji:

Korak 1: Depth buffer inicijalizovan na max (1.0)
┌─────────────┐
│ 1.0 1.0 1.0 │
│ 1.0 1.0 1.0 │
│ 1.0 1.0 1.0 │
└─────────────┘

Korak 2: Crta se daleki objekat (z = 0.8)
┌─────────────┐
│ 1.0 0.8 0.8 │    0.8 < 1.0 → zapisuje se
│ 1.0 0.8 0.8 │
│ 1.0 1.0 1.0 │
└─────────────┘

Korak 3: Crta se blizi objekat (z = 0.3)
┌─────────────┐
│ 1.0 0.3 0.8 │    0.3 < 0.8 → prepisuje!
│ 1.0 0.3 0.8 │    Blizi objekat pobjedjuje
│ 1.0 1.0 1.0 │
└─────────────┘

Korak 4: Crta se objekat iza prvog (z = 0.5)
┌─────────────┐
│ 1.0 0.3 0.8 │    0.5 > 0.3 → NE zapisuje!
│ 1.0 0.3 0.8 │    Vec postoji nesto blize
│ 1.0 1.0 1.0 │
└─────────────┘

Prednosti Z-buffer-a:

Preciznost depth buffer-a -- nelinearnost

Evo gde stvari postaju komplikovanije. Dubina u depth buffer-u nije linearna u odnosu na rastojanje od kamere. Razlog je perspektivna projekcija.

Nakon perspektivne projekcije i perspective divide-a, dubina u NDC prostoru (koja se zapisuje u depth buffer) je:

z_ndc = (far * (z_view - near)) / (z_view * (far - near))

gde:
z_view = stvarno rastojanje od kamere
near = near clipping plane
far = far clipping plane

Ova funkcija je hiperbolska (1/z ponasanje), sto znaci:

Distribucija preciznosti standardnog Z-buffer-a:

Near plane                                        Far plane
│                                                        │
│████████████████████▒▒▒▒▒▒▒▒░░░░░░░ ·  ·   ·    ·     │
│                                                        │
 ^^^^^^^^^^^^^^^^^   ^^^^^^^^  ^^^^^^^  ^^^^^^^  ^^^^^^^^
 Ogromna preciznost  Dobra     OK       Slaba    Jako slaba

Z-fighting -- vidljivi artefakt

Kada dva objekta imaju skoro istu dubinu, nedovoljna preciznost depth buffer-a moze dovesti do Z-fighting -- treperenja piksela izmedju dva objekta jer se njihove dubine ne mogu razlikovati:

Z-fighting efekat:

Frame 1:           Frame 2:           Frame 3:
┌──────────┐       ┌──────────┐       ┌──────────┐
│ ████     │       │ ▓▓▓▓     │       │ █▓█▓     │  ← pikseli
│ ████     │       │ ▓▓▓▓     │       │ ▓█▓█     │     trepere
│          │       │          │       │          │
└──────────┘       └──────────┘       └──────────┘
Objekat A          Objekat B          Mesavina A i B
pobjedjuje         pobjedjuje         (z-fighting!)

Z-fighting je cesce daleko od kamere (gde je preciznost slaba) i kada je near plane preblizu (sto "rasipa" preciznost na podrucje blizu kamere).

Prakticni saveti za izbegavanje Z-fighting-a:

Formati depth buffer-a

Depth buffer moze biti razlicitih formata, od cega zavisi preciznost:

Format Bitova Opis Tipicna upotreba
D16 16-bit integer Niska preciznost, mali memorijski otisak Shadow maps (gde je preciznost manje kriticna)
D24S8 24-bit depth + 8-bit stencil Standardni format iz DirectX 9/10 ere Starije aplikacije, mobilni uredjaji
D32F 32-bit floating-point Visoka preciznost, floating-point Moderni rendering, Reverse-Z
D32FS8 32-bit float depth + 8-bit stencil Najveca preciznost sa stencilom UE5 default za deferred rendering

UE5 koristi D32FS8 (32-bit float depth sa 8-bit stencil-om) kao standardni format. Floating-point format je bitan za Reverse-Z tehniku koju cemo sada objasniti.

Reverse-Z -- Zasto UE5 "okrece" depth buffer

Ovo je jedna od najelegantnijih optimizacija u modernom renderingu, i UE5 je koristi po default-u.

Problem: Standardni depth buffer mapira near plane na Z=0 i far plane na Z=1. Kao sto smo videli, preciznost je koncentrisana blizu kamere (oko Z=0), a slaba daleko od kamere (oko Z=1). Ali cak i ta koncentracija nije dovoljna -- hiperbolska distribucija "trosi" vecinu preciznosti na prvih 10% dubine.

Resenje -- Reverse-Z: Obrnite mapiranje! Near plane ide na Z=1, far plane ide na Z=0. Depth comparison funkcija se menja iz Less u Greater (blizi fragment ima vecu Z vrednost).

Zasto ovo pomaze? Zato sto floating-point brojevi imaju logaritamsku distribuciju preciznosti -- vise preciznosti oko nule, manje daleko od nule. Hiperbolska distribucija depth-a ima suprotnu karakteristiku -- vise preciznosti blizu kamere (daleko od nule u standardnom mapiranju).

Kada obrnete Z, te dve distribucije se medjusobno kompenzuju:

Standardni Z (near=0, far=1):
Depth preciznost:    ████████████▒▒▒▒░░░ ·  ·   ·    ·
Float preciznost:    ████████████████████▒▒▒▒▒▒░░░░ · ·
Rezultat:            ████████████▒▒▒▒░░░ ·  ·   ·    ·
                     (float preciznost je vec dobra ovde, ne pomaze)

Reverse-Z (near=1, far=0):
Depth preciznost:    ·   ·    ·  ░░░▒▒▒▒████████████
Float preciznost:    ████████████████████▒▒▒▒▒▒░░░░ · ·
Rezultat:            ████████████████████▒▒▒▒▒▒██████
                     (float preciznost kompenzuje slabu depth preciznost!)

Rezultat: sa Reverse-Z i 32-bit float depth buffer-om, preciznost je skoro uniformna preko celog opsega dubine. Z-fighting artefakti su dramaticno smanjeni, cak i na velikim rastojanjima.

Bonus: Sa Reverse-Z i floating-point depth buffer-om, mozete koristiti beskonacan far plane (far = ∞) bez gubitka preciznosti! Ovo eliminise problem "far plane clipping" gde se udaljeni objekti odsecaju. UE5 koristi veoma velik far plane upravo zahvaljujuci Reverse-Z.

Depth buffer i memorija

Depth buffer zauzima znacajnu kolicinu memorije:

Memorijski otisak depth buffer-a:

1080p (1920×1080):
  D32FS8: 1920 × 1080 × 5 bytes = ~9.9 MB

1440p (2560×1440):
  D32FS8: 2560 × 1440 × 5 bytes = ~17.6 MB

4K (3840×2160):
  D32FS8: 3840 × 2160 × 5 bytes = ~39.6 MB

Na 4K rezoluciji, samo depth+stencil buffer zauzima ~40 MB VRAM-a. Dodajte color buffer (recimo 4 bajta po pikselu za 8-bit RGBA), G-Buffer kanale u deferred renderingu (pozicija, normala, roughness, metallic -- svaki jos jedan buffer), i vidite kako memorija brzo raste sa rezolucijom. O ovome vise u kasnijim poglavljima o memorijskoj optimizaciji.


9.8 Stencil Buffer -- Nevidljivi Alat za Vidljive Efekte

Sta je stencil buffer?

Stencil buffer je pomalo zapostavljen ali izuzetno mocan deo render pipeline-a. To je buffer iste rezolucije kao ekran, ali umesto boja ili dubine, cuva celobrojnu vrednost (tipicno 8-bitni unsigned integer, 0-255) za svaki piksel.

Stencil buffer se tipicno deli memoriju sa depth buffer-om u kombinovanom formatu (D24S8 ili D32FS8), pa ne zahteva dodatnu alokaciju memorije (vec smo ga "platili" u depth buffer-u).

Zamislite stencil buffer kao sablon (otuda ime -- "stencil" je engleski za sablon). Kada slikar koristi sablon, on prvo postavlja sablon sa isecenim oblicima na platno, pa prska boju -- boja ide samo tamo gde su rupi u sablonu. Stencil buffer radi na istom principu: mozete oznaciti regione ekrana gde se nesto sme ili ne sme crtati.

Kako stencil test radi?

Stencil test se odvija u output merger-u, tipicno pre depth testa (mada redosled moze varirati). Za svaki fragment:

  1. Procitaj stencil vrednost iz stencil buffer-a za taj piksel
  2. Uporedi je sa reference vrednošcu koristeci izabranu comparison funkciju
  3. Na osnovu rezultata, izvrsi operaciju na stencil buffer-u

Comparison funkcije su iste kao za depth test: Equal, NotEqual, Less, Greater, LessEqual, GreaterEqual, Always, Never.

Operacije na stencil buffer-u se mogu konfigurisati za tri slucaja:

Slucaj Opis
Stencil FAIL Stencil test pao
Stencil PASS, Depth FAIL Stencil prosao ali depth pao
Oba PASS I stencil i depth prosli

Za svaki slucaj, mozete izabrati jednu od operacija:

Operacija Efekat
Keep Ne menjaj stencil buffer
Zero Postavi na 0
Replace Postavi na reference vrednost
IncrSat Inkrementiraj (sa saturacijom -- ne prelazi 255)
DecrSat Dekrementiraj (sa saturacijom -- ne ide ispod 0)
Invert Bitwise NOT
IncrWrap Inkrementiraj (sa wrap-around -- 255 → 0)
DecrWrap Dekrementiraj (sa wrap-around -- 0 → 255)

Dodatno, mozete definisati stencil mask (read mask i write mask) koji kontrolisu koje bitove stencil buffer-a citrate/pisete. Ovo omogucava koriscenje razlicitih bitova za razlicite namene -- na primer, donjih 4 bita za jednu stvar, gornjih 4 za drugu.

Tipicni use case-ovi stencil buffer-a

1. Maskiranje -- portali i ogledala

Klasicna upotreba: zelite da renderujete scenu "iza" portala samo unutar portala:

Korak 1: Nacrtaj oblik portala u stencil buffer (Reference = 1)
   Stencil:              Color:
   ┌──────────┐          ┌──────────┐
   │ 0 0 0 0  │          │          │
   │ 0 1 1 0  │          │  Portal  │
   │ 0 1 1 0  │          │  oblik   │
   │ 0 0 0 0  │          │          │
   └──────────┘          └──────────┘

Korak 2: Renderuj scenu "iza portala" sa stencil testom (prolazi samo gde je stencil == 1)
   Stencil:              Color:
   ┌──────────┐          ┌──────────┐
   │ 0 0 0 0  │          │          │
   │ 0 1 1 0  │          │ ▓▓▓▓▓▓▓▓│  ← scena iza portala
   │ 0 1 1 0  │          │ ▓▓▓▓▓▓▓▓│     vidljiva samo u portalu
   │ 0 0 0 0  │          │          │
   └──────────┘          └──────────┘

Korak 3: Renderuj normalno okruzenje (gde stencil != 1)
   Finalna slika:
   ┌──────────┐
   │ ░░░░░░░░ │  ← normalan zid
   │ ░▓▓▓▓▓▓░ │  ← portal sa drugom scenom unutra
   │ ░▓▓▓▓▓▓░ │
   │ ░░░░░░░░ │
   └──────────┘

2. Outline efekti

Popularan efekat u igrama -- obris oko selektovanog objekta:

Korak 1: Nacrtaj objekat normalno, stencil = 1
Korak 2: Nacrtaj isti objekat malo VECI, ali samo gde stencil != 1
         → Vidljiv je samo "visak" oko originala = outline!

3. Decal rendering

UE5 koristi stencil buffer za kontrolu gde se dekali primenjuju. Na primer, mozete oznaciti odredjene povrsine (podovi ali ne zidovi) stencil vrednošcu, i dekal ce se primeniti samo na oznacenim povrsinima.

4. Custom Depth/Stencil u UE5

UE5 nudi Custom Depth i Custom Stencil -- odvojeni buffer-i od glavnog depth/stencil-a, koje mozete koristiti za post-process efekte. Na primer:

Ovo je izuzetno korisno za UI highlighting, x-ray efekte, i slicne stvari. Postavlja se per-actor u UE5 editoru (Rendering → Render Custom Depth Pass + Custom Depth Stencil Value).

Stencil buffer i performanse

Stencil test je veoma jeftin -- to je jednostavno celobrojno poredjenje i potencijalni write, sto hardver radi gotovo besplatno. Glavni trosak stencil-a je indirektan:

Ali u vecini slucajeva, stencil-based efekti su znacajno jeftiniji od alternativnih pristupa (npr. renderovanje u odvojenu render target teksturu i compositing).


9.9 Overdraw -- Skriveni Ubica Performansi

Sta je overdraw?

Overdraw je situacija kada se isti piksel na ekranu crta (procesira kroz pixel shader) vise od jedanput u istom frejmu. Svako dodatno crtanje je trosenje GPU resursa -- bandwidth-a, shader vremena, ROP throughput-a -- na posao koji ce mozda biti prepisan.

Zamislite da slikate zid. Prvo nanesete belu boju. Zatim preko nje nanesete plavu. Zatim crvenu. Na kraju ste potrosili tri puta vise boje (i vremena) nego sto je trebalo -- mogli ste odmah da nanesete crvenu.

Overdraw faktor

Overdraw se obicno izrazava kao faktor -- prosecni broj puta koliko je svaki piksel obradivaen:

Overdraw = Ukupan broj obradjenjih fragmenata / Ukupan broj piksela na ekranu

Primer:
  Ekran: 1920 × 1080 = 2,073,600 piksela
  Ukupno obradjenih fragmenata: 6,220,800
  Overdraw = 6,220,800 / 2,073,600 = 3.0x

Overdraw od 1.0x bi znacio da je svaki piksel obradivaen tacno jednom -- savrsena efikasnost. U praksi, overdraw od 2-3x je tipican za modernu igru. Overdraw iznad 4-5x signalizira problem.

Uzroci overdraw-a

1. Preklapanje neprovidnih objekata (bez front-to-back sortiranja)

Ako se objekti crtaju od najdaljih ka najbliziim (back-to-front), svaki novi objekat prepisuje piksele prethodno nacrtanih objekata. Sa Front-to-back sortiranjem i Early-Z, ovo se velikim delom eliminise.

Back-to-front (los redosled):      Front-to-back (dobar redosled):
1. Daleki objekat: puni ekran       1. Blizi objekat: puni ekran
2. Blizi objekat: prepisuje         2. Daleki objekat: Early-Z odbacuje!
   → pikseli dalekog objekta           → nema nepotrebnog rada
     obradeni UZALUD

2. Transparentni objekti

Transparentni objekti su najgori krivac za overdraw. Moraju da se crtaju back-to-front (za korektno blending), ne mogu da koriste Early-Z (jer ne pisu u depth buffer), i cesto se znacajno preklapaju:

Particle sistem sa dimom:
  50 overlapping quad-ova
  Svaki quad pokriva 100x100 piksela
  = 50 × 10,000 = 500,000 fragment obrada
  Za regione gde se svi quad-ovi preklapaju: overdraw 50x!

Ovo je razlog zasto particle sistemi mogu biti toliko skupi -- nije samo broj particla, vec koliko se preklapaju. Deset velikih particla koji se preklapaju moze biti skuplje od stotinu malih koji se ne preklapaju.

3. Vegetacija

Listovi drveca su obicno realizovani kao quad-ovi sa alpha mask-om (Masked materijal). Svaki list:

Ovo je jedan od razloga zasto je vegetacija (posebno drvece sa gustim krosnjama) jedan od najskupljih elemenata u igrama po pitanju pixel shader-a.

4. Decali

Dekali se projketuju na povrsine i uvek se crtaju preko postojeceg sadrzaja, dodajuci minimalno 1x overdraw za svaki dekal. Mnogo dekala na istom mestu (npr. na zidu sa mnogo rupa od metaka) moze dovesti do znacajnog overdraw-a.

Veza sa fill rate-om

Fill rate je brzina kojom GPU moze da procesira fragmente -- koliko piksela po sekundi moze da prodje kroz pixel shader i zapise u framebuffer. Fill rate je ogranicen sa tri strane (kao sto smo pomenuli u poglavlju 08, sekcija 8.10):

  1. Pixel shader throughput: Koliko brzo GPU moze da izvrsi shader program
  2. ROP throughput: Koliko brzo se mogu obaviti depth/stencil testovi i framebuffer write-ovi
  3. Memory bandwidth: Koliko brzo se mogu citati teksture i pisati u buffere

Overdraw direktno multipluje zahteve za fill rate-om. Ako imate scenu koja zahteva fill rate od X pri overdraw-u 1.0, ista scena sa overdraw-om 3.0 zahteva fill rate od 3X. Ako GPU nema dovoljno fill rate-a, frame rate pada.

Fill rate bottleneck primer:

GPU fill rate: 100 Gpixel/s
Ekran: 4K (8,294,400 piksela)
Target: 60 FPS

Dostupno piksela po frejmu: 100,000,000,000 / 60 = ~1,666,000,000

Sa overdraw 1.0x: 8,294,400 piksela × 1 = 8.3M → OK!
Sa overdraw 3.0x: 8,294,400 piksela × 3 = 24.9M → OK!
Sa overdraw + kompleksan shader (100 instrukcija):
  24.9M × 100 = 2.49G instrukcija → Zavisi od shader throughput-a

Ako je shader throughput 2G instrukcija/frejm → ne stiže na 60 FPS!

Kako meriti overdraw

U Unreal Engine 5

UE5 ima ugradjeni Shader Complexity vizualizacioni mod:

  1. U editoru: View Mode → Optimization Viewmodes → Shader Complexity
  2. Alternativno: viewmode ShaderComplexity u konzoli

Ovaj mod prikazuje piksele obojene prema kolicini posla:

Za specifican overdraw prikaz, koristite Quad Overdraw vizualizacioni mod koji prikazuje koliko puta je svaki piksel procesuiran.

Eksterni alati

Strategije za smanjenje overdraw-a

1. Front-to-back sortiranje za opaque objekte

Ovo je najosnovnija strategija i UE5 je primenjuje automatski. Opaque objekti se sortiraju od najbliziih ka najdaljim, tako da Early-Z moze da odbaci sve sto je sakriveno.

2. Depth prepass

Kao sto smo objasnili u sekciji o Early-Z -- prvo popunite depth buffer, pa onda renderujte sa punim materijalima. UE5 ovo radi po default-u.

3. Occlusion culling

Ne crtajte objekte koji su potpuno sakriveni iza drugih objekata. UE5 koristi kombinaciju hardware occlusion queries i software occlusion (Hi-Z based) za ovo. Detaljnije u kasnijim poglavljima o culling-u.

4. Smanjenje velicine transparentnih objekata

Manji transparentni objekti = manje preklapanja = manje overdraw-a. Za particle sisteme:

5. LOD za vegetaciju

Za daleku vegetaciju, umesto stotina individualnih listova sa alpha mask-om, koristite billboard ili imposter sa jednim quad-om. Drasticno smanjuje overdraw.

6. Precomputed Visibility u UE5

UE5 ima sistem za precomputed visibility koji moze da oznaci objekte kao nevidljive iz odredjenih delova mape, eliminisijuci ih pre nego sto uopste stignu do GPU-a.


9.10 Fragment vs Piksel -- Terminoloska Razlika Koja Zapravo Znaci Nesto

Zasto postoje dva termina?

U svakodnevnom razgovoru, developeri cesto koriste "piksel" i "fragment" kao sinonime. Ali postoji vazna konceptualna razlika, i razumevanje te razlike pomaze da razumete pipeline.

Sta je piksel?

Piksel (picture element) je konacna, nepromenljiva boja na ekranu. Svaki piksel na vasem monitoru ima jednu boju u svakom trenutku. Na ekranu od 1920x1080, imate tacno 2,073,600 piksela.

Piksel je krajnji rezultat rendering procesa -- ono sto vidite na ekranu.

Sta je fragment?

Fragment je "kandidat" za piksel. To je rezultat rasterizacije -- potencijalni piksel koji mozda hoce, a mozda nece zavrsiti na ekranu.

Fragment nosi sve podatke potrebne za izracunavanje boje piksela: interpolirane atribute (UV, normala, boja...), dubinu, stencil informacije. Ali fragment jos nije piksel jer:

  1. Moze pasti depth test (nesto je ispred njega)
  2. Moze pasti stencil test (nije u dozvoljenoj oblasti)
  3. Moze biti discard-ovan u pixel shader-u (alpha test)
  4. Moze biti blendovan sa postojecim pikselom (transparentnost)

Analogija

Zamislite restoransku kuhinju:

Svako jelo bačeno pre nego sto stigne do gosta je uzaludan rad -- kuvar je radio, koristio sastojke, ali gost nista nije dobio. Isto tako, svaki fragment koji ne zavrsi kao piksel na ekranu je uzaludan GPU rad.

Zasto je ova razlika prakticno bitna?

Razlika izmedju fragmenta i piksela je temelj za razumevanje:

  1. Overdraw-a: Vise fragmenata se "takmici" za isti piksel. Samo jedan prezivljava.
  2. Early-Z optimizacije: Cilj je da sto manje fragmenata uopste udje u pixel shader.
  3. Fill rate-a: GPU mora da procesira fragmente, ne piksele. Ako imate 2M piksela ali 6M fragmenata, GPU radi na 6M entiteta.
  4. Pixel shader-a: Uprkos imenu, pixel shader zapravo procesira fragmente. Fragment shader je precizniji naziv (OpenGL ga i zove "Fragment Shader"), ali DirectX terminologija ("Pixel Shader") je prevladala.

Terminologija u UE5

UE5 koristi DirectX terminologiju, pa cete videti "Pixel Shader" svuda u dokumentaciji i kodu. Ali interno, svi mehanizmi rade sa fragmentima -- Early-Z odbacuje fragmente, depth test filtrira fragmente, blending kombinuje fragment sa postojecim pikselom.

Kada vidite SV_Target u HLSL kodu, to je izlaz pixel shader-a -- boja fragmenta koji ce mozda postati piksel.


9.11 Quad Rendering (2x2 Pixel Groups) -- Zasto GPU Procesira Piksele u Cetvorke

Sta su quad-ovi?

GPU ne procesira piksele pojedinacno -- procesira ih u grupama od 2x2 piksela, koje se zovu quad-ovi (ili pixel quad-ovi). Ovo je fundamentalna arhitektonska odluka modernih GPU-ova (obe generacije -- NVIDIA i AMD), i ima duboke posledice za performanse.

Ekran se deli na 2x2 blokove:
┌──┬──┬──┬──┬──┐
│▪▫│▪▫│▪▫│▪▫│▪▫│
│▫▪│▫▪│▫▪│▫▪│▫▪│   Svaki blok je jedan "quad"
├──┼──┼──┼──┼──┤
│▪▫│▪▫│▪▫│▪▫│▪▫│   GPU pokrece pixel shader za
│▫▪│▫▪│▫▪│▫▪│▫▪│   ceo quad istovremeno
├──┼──┼──┼──┼──┤
│▪▫│▪▫│▪▫│▪▫│▪▫│
│▫▪│▫▪│▫▪│▫▪│▫▪│
└──┴──┴──┴──┴──┘

Svaki quad se izvrsava kao 4 thread-a unutar istog warp-a (setite se iz poglavlja 08 -- warp je grupa od 32 niti na NVIDIA, 32 ili 64 na AMD). Dakle, jedan warp procesira 8 quad-ova (32 piksela = 8 × 4).

Zasto 2x2? -- Derivati (ddx/ddy)

Glavni razlog za quad-ove su derivati -- sposobnost pixel shader-a da izracuna brzinu promene (rate of change) bilo kog atributa u X i Y pravcu ekrana.

U HLSL-u, ovo su funkcije ddx() i ddy() (derivat po x i derivat po y):

float2 uv = input.UV;
float dUVdx = ddx(uv);  // Koliko se UV menja sa pomeranjem za 1 piksel udesno
float dUVdy = ddy(uv);  // Koliko se UV menja sa pomeranjem za 1 piksel nadole

Kako GPU izracunava ove derivate? Jednostavno: oduzima vrednost atributa iz susednog piksela u quad-u:

Quad (2x2):
┌────────┬────────┐
│ P(0,0) │ P(1,0) │
│ uv=A   │ uv=B   │
├────────┼────────┤
│ P(0,1) │ P(1,1) │
│ uv=C   │ uv=D   │
└────────┴────────┘

ddx na P(0,0) = B - A     (piksel desno minus ovaj piksel)
ddy na P(0,0) = C - A     (piksel dole minus ovaj piksel)

ddx na P(1,0) = B - A     (isti rezultat!)
ddy na P(1,0) = D - C     (ili C - A, zavisno od implementacije)

Ovo je razlog zasto je quad 2x2 minimum -- potrebna su vam bar 2 piksela u svakom pravcu da biste izracunali derivat. Ne postoji nacin da izracunate "koliko se UV menja sa pomeranjem" ako imate samo jedan piksel.

Zasto su derivati vazni? -- Mipmap selekcija

Najvaznija upotreba derivata je automatska selekcija mipmap nivoa pri uzorkovanju tekstura (vidite poglavlje 05 za detalje o mipmapama).

Kada pixel shader uzorkuje teksturu sa Texture.Sample(), GPU mora da odluci koji mipmap nivo da koristi. Ako je tekstura daleko od kamere i pokriva malo piksela, treba koristiti manji mipmap (da izbegne aliasing). Ako je blizu, treba koristiti pun mipmap.

Kako GPU odlucuje? Koristi derivate UV koordinata:

Ako ddx(UV) i ddy(UV) su MALI:
  → UV se polako menja izmedju piksela
  → Tekstura je "razvucena" na ekranu (blizu kamere)
  → Koristi velik mipmap (vise detalja)

Ako ddx(UV) i ddy(UV) su VELIKI:
  → UV se brzo menja izmedju piksela
  → Tekstura je "sabijenja" na ekranu (daleko od kamere)
  → Koristi mali mipmap (manje detalja, bez aliasinga)

Bez derivata, GPU ne bi mogao automatski da bira mipmap nivo. Morali biste eksplicitno da koristite Texture.SampleLevel() sa rucno izracunatim LOD-om, sto je i nezgodno i neefikasno.

Derivati u drugim primenama

Osim mipmap selekcije, derivati se koriste za:

  1. Anizotropno filtriranje: Smer i stepen "istezanja" teksture na ekranu se odredjuje iz derivata
  2. Screen-space efekti: Rekonstrukcija normala iz depth buffer-a koristi ddx(depth) i ddy(depth)
  3. Proceduralne teksture: Anti-aliasing proceduralnih paterna (npr. fwidth() = abs(ddx()) + abs(ddy()) za odredjivanje odgovarajuce sirine filtera)
  4. Parallax mapping: Derivati pomazu u odredjivanju koraka za ray-marching
  5. Material blending: Gladak prelaz izmedju materijala moze koristiti derivate za anti-aliasing tranzicije

Quad overdraw -- problem malih trouglova

I evo gde quad rendering postaje problem. Kada trougao pokriva samo deo quad-a, GPU i dalje pokrece pixel shader za ceo quad (4 piksela). Pikseli van trougla su helper lanes -- oni se izvrsavaju (jer su potrebni za derivate) ali njihov rezultat se nikada ne upisuje u framebuffer.

Trougao koji pokriva samo 1 piksel u quad-u:
┌────────┬────────┐
│ HELPER │ HELPER │     Pixel shader se pokrece za sva 4 piksela,
├────────┼────────┤     ali samo 1 piksel zapravo proizvodi rezultat.
│ AKTIVAN│ HELPER │     Iskoriscenost: 25%
└────────┴────────┘

Trougao koji pokriva 2 piksela u quad-u:
┌────────┬────────┐
│ AKTIVAN│ AKTIVAN│     2 od 4 piksela su korisni.
├────────┼────────┤     Iskoriscenost: 50%
│ HELPER │ HELPER │
└────────┴────────┘

Ovo je quad overdraw (ili quad utilization loss) -- "overdraw" koji nije uzrokovan preklapanjem objekata, vec samom arhitekturom GPU-a.

Kvantifikacija problema

Razmislite sta se desava sa mesh-om koji ima mnogo sitnih trouglova na ekranu:

Primer: Mesh sa 10,000 trouglova, svaki pokriva ~2 piksela

Korisni pikseli: 10,000 × 2 = 20,000
Quad pikseli:    10,000 × 4 = 40,000    (svaki trougao aktivira bar 1 quad)
Iskoriscenost:   20,000 / 40,000 = 50%

Ali cesto je GORE jer trougao moze da prelazi granice quad-ova:

Trougao koji prelazi 2 quad-a (pokriva 3 piksela):
┌────┬────┬────┬────┐
│    │AKTV│AKTV│    │    2 quad-a aktivirana za 3 piksela
├────┼────┼────┼────┤    Shader niti: 8
│    │    │AKTV│    │    Korisni pikseli: 3
└────┴────┴────┴────┘    Iskoriscenost: 37.5%

Za trouglove koji pokrivaju manje od ~8 piksela, quad overhead postaje znacajan. Za trouglove koji pokrivaju 1-2 piksela (sto se desava sa udaljenim mesh-ovima bez LOD-a), overhead je katastrofalan -- GPU radi 2-4x vise posla nego sto je potrebno.

Ovo smo vec pomenuli u poglavlju 08 (sekcija 8.10) kao "Small Triangle Problem". Ponovicemo kljucnu poruku: LOD je obavezan za mesheve koji ce ikada biti daleko od kamere. Bez LOD-a, ne samo da trosiste vertex processing, vec trosiste i ogromne kolicine pixel shader rada zbog quad overhead-a.

Nanite i quad overdraw

Nanite u UE5 delimicno resava ovaj problem na dva nacina:

  1. Automatski LOD: Nanite dinamicki prilagodjava nivo detalja tako da trouglovi budu otprilike velicine piksela, minimizujuci quad gubitke za daleke objekte (jer koristi upravo odgovarajuci LOD)

  2. Software rasterizer: Za veoma sitne trouglove (manje od 1 piksela), Nanite koristi software rasterizer koji ne koristi 2x2 quad-ove i moze efikasnije da obradi sub-pixel trouglove. Hardware rasterizer se koristi za vece trouglove gde je quad overhead zanemarljiv

Ovo je jedan od fundamentalnih razloga zasto je Nanite revolucionaran -- resava arhitektonski problem GPU hardvera softverskim pristupom.

Helper lanes i performanse

Helper lanes (pikseli u quad-u koji su van trougla) imaju dodatne posledice:

  1. Trose compute resurse: Izvrsavaju isti shader kao aktivni pikseli, koristeci ALU i registre
  2. Trose bandwidth: Mogu citati teksture (sto koristi bandwidth i cache)
  3. Trose occupancy: Zauzimaju slot u warp-u koji bi mogao da se koristi za aktivan piksel
  4. NE upisuju u framebuffer: Makar ne trose write bandwidth

Jedina korist helper lanes-a je sto omogucavaju izracunavanje derivata. Bez njih, ddx(), ddy(), fwidth() i automatska mipmap selekcija ne bi funkcionisali.

Prakticni saveti vezani za quad overdraw

  1. Ciljajte minimum 8 piksela po trouglu: Ovo daje prihvatljivu quad iskoriscenost
  2. Koristite LOD agresivno: Posebno za mesh-eve sa gustom geometrijom
  3. Izbegavajte dugacke, tanke trouglove: Trougao velicine 1x100 piksela aktivira ~50 quad-ova, ali pokriva samo 100 piksela. Iskoriscenost: 100/200 = 50%. Isti broj piksela u trouglu 10x10 bi aktivirao ~25 quad-ova sa boljom iskoriscenoscu.
  4. Razmislite o topologiji: Ravnomerniji trouglovi (blizi equilateralnom obliku) daju bolju quad iskoriscenost nego izduzeni (poglavlje 03 o mesh topologiji)
  5. Pratite GPU metrike: Koristite profajlere da proverite quad utilization -- niske vrednosti ukazuju na problem malih ili izduzenih trouglova

9.12 Rasterizacija u Kontekstu Unreal Engine 5 Pipeline-a

Gde se rasterizacija desava u UE5?

UE5 koristi Deferred Rendering pipeline po default-u (Forward rendering je takodje dostupan za specificne slucajeve). U deferred pipeline-u, rasterizacija se desava u vise prolaza:

UE5 Deferred Rendering Pipeline (pojednostavljen):

1. Depth Prepass
   └─ Rasterizacija → SAMO depth write (popuni Z-buffer)
   └─ Cilj: Popuniti Hi-Z za Early-Z u kasnijim prolazima

2. Base Pass (G-Buffer)
   └─ Rasterizacija → Pixel shader pise u G-Buffer kanale:
      - GBufferA: World Normal
      - GBufferB: Metallic, Specular, Roughness
      - GBufferC: Base Color
      - GBufferD: Custom data
      - Depth/Stencil buffer

3. Lighting Pass
   └─ Rasterizacija light volumes → Izracunava osvetljenje
      koristeci G-Buffer podatke (fullscreen ili per-light)

4. Translucency Pass
   └─ Rasterizacija transparentnih objekata → Forward rendering
      za transparentne objekte (back-to-front sortiranje)

5. Post-Process
   └─ Fullscreen quad-ovi → Bloom, tone mapping, AA, itd.

U svakom od ovih prolaza, rasterizer radi isti posao -- pretvara trouglove u fragmente, evaluira edge equations, interpolira atribute. Ali tip posla koji pixel shader radi i sta se zapisuje u buffer-e se razlikuje.

Nanite i rasterizacija

Nanite u UE5 ima sopstveni rasterizer koji radi paralelno sa hardware rasterizerom:

UE5 sa Nanite:

Nanite meshevi:
  └─ GPU-driven culling → Software rasterizer (compute shader)
  └─ Rezultat: Visibility buffer (trokut ID + baricentricne koord.)
  └─ Material evaluation: Pixel shader se pokrece jednom po pikselu
                          koristeci visibility buffer podatke

Non-Nanite meshevi:
  └─ Standardni hardware rasterizer (kao gore)

Nanite-ov pristup je fundamentalno drugaciji -- umesto da pixel shader radi za svaki fragment koji rasterizer proizvede (sa svim overdraw-om i quad overhead-om), Nanite prvo rasterizuje u visibility buffer (koji samo identifikuje koji trougao je vidljiv na svakom pikselu), pa tek onda pokrece materijal evaluaciju tacno jednom po pikselu. Ovo eliminise overdraw za materijal evaluaciju i znacajno smanjuje GPU trosak za kompleksne scene.

Praktican uticaj na optimizaciju

Razumevanje rasterizacije direktno utice na sledece prakticne odluke u UE5:

  1. Izbor izmedju Opaque i Masked materijala: Masked materijali gube Early-Z → vise overdraw-a → uvek koristite Opaque kada je moguce

  2. Velicina trouglova i LOD strategija: Mali trouglovi trose GPU resurse kroz quad overhead → LOD je kriticna optimizacija za svaki mesh koji ce biti vidljiv na razlicitim rastojanjima

  3. Particle sistemi: Veliki, overlapping particli su skupi zbog overdraw-a → koristite manje particla, smanjite velicinu, razmorite sheet particla

  4. Depth prepass konfiguracija: UE5 ima opcije za kontrolu depth prepass-a (Project Settings → Rendering) → uglavnom ga ostavite ukljucenog, ali za forward rendering moze imati drugaciji trade-off

  5. Custom Depth/Stencil: Koristite za specijalne efekte, ali budite svesni da svaki objekat sa Custom Depth zahteva dodatni rendering pass (dodatna rasterizacija + depth write)

  6. Overdraw monitoring: Redovno koristite Shader Complexity i Quad Overdraw vizualizacione modove da identifikujete problematicne oblasti


Sumarni Pregled: Kljucni Termini

Pojam Znacenje
Rasterizacija Proces pretvaranja vektorske geometrije (trouglova) u fragmente (potencijalne piksele) na diskretnoj mreži ekrana
Triangle Setup Fixed-function faza koja priprema trougao za rasterizaciju -- izracunava bounding box, edge equations i koeficijente za interpolaciju
Edge Equation Linearna funkcija E(x,y) = Ax + By + C koja za svaku tacku odredjuje na kojoj strani ivice trougla se nalazi; pozitivan/negativan znak → unutra/spolja
Sign Test Metod odredjivanja da li je tacka unutar trougla: tacka je unutar ako sve tri edge equations imaju isti znak
Top-Left Rule Pravilo rasterizacije koje odredjuje kome pripada piksel na ivici dva trougla -- pripada trouglu cija je ta ivica "top" ili "left"
Barycentric Coordinates Sistem tezina (u, v, w) gde u+v+w=1, koji opisuje poziciju tacke unutar trougla kao tezinski zbir pozicija tri temena
Interpolacija atributa Proces izracunavanja vrednosti vertex atributa (UV, normala, boja...) za svaki piksel trougla koristeci baricentricne koordinate
Perspektivno-korektna interpolacija Metod interpolacije koji kompenzuje perspektivnu distorziju deljenjem atributa sa w pre interpolacije i mnozenjem posle, dajuci vizuelno korektne rezultate
1/w korekcija Tehnika gde se interpolira A/w i 1/w umesto A direktno, jer su ti kolicnici linearni u screen space-u nakon perspektivne projekcije
Early-Z Optimizacija gde se depth test izvrsava PRE pixel shader-a, stedeci rad pixel shader-a za sakrivene fragmente
Hi-Z (Hierarchical-Z) Hijerarhijski depth buffer (nalik mipmap-i) koji omogucava brzo odbacivanje celih blokova piksela ili celih trouglova jednom proverom
Depth Prepass (Z-Prepass) Tehnika gde se scena prvo renderuje samo sa depth write-om (bez materijalnog posla), popunjavajuci depth buffer za maksimalno efikasan Early-Z u drugom prolazu
Z-Buffer (Depth Buffer) Buffer iste rezolucije kao ekran koji cuva dubinu (rastojanje od kamere) svakog piksela, omogucavajuci resavanje problema vidljivosti
Z-Fighting Vizuelni artefakt (treperenje) koji nastaje kada dva objekta imaju gotovo istu dubinu i depth buffer nema dovoljno preciznosti da ih razlikuje
Reverse-Z Tehnika mapiranja near plane na Z=1 i far plane na Z=0, koja u kombinaciji sa floating-point depth buffer-om daje znatno ravnomerniju distribuciju preciznosti
Stencil Buffer 8-bitni celobrojni buffer (obicno deo depth/stencil formata) koji sluzi za maskiranje, obelezavanje regiona ekrana i razlicite specijalne efekte
Overdraw Situacija kada se isti piksel procesira (kroz pixel shader) vise od jednom u istom frejmu, troseci GPU resurse na posao koji ce biti prepisan
Fill Rate Brzina kojom GPU moze da procesira fragmente -- ogranicena shader throughput-om, ROP-ovima i memory bandwidth-om
Fragment Potencijalni piksel koji je proizvela rasterizacija; nosi interpolirane atribute i moze biti odbacen depth/stencil testom ili discard-om u shader-u
Piksel Konacna boja na ekranu -- fragment koji je preziveo sve testove i upisan je u framebuffer
Quad (2x2) Minimalna grupa od 4 piksela (2x2) koje GPU procesira zajedno; potreban za izracunavanje derivata (ddx/ddy)
Helper Lane Piksel u quad-u koji je van trougla ali se izvrsava radi izracunavanja derivata; trose GPU resurse ali ne upisuju rezultat
ddx / ddy Funkcije u shader-u koje vracaju derivat (brzinu promene) atributa u X i Y pravcu ekrana; koriste se za mipmap selekciju, anizotropno filtriranje i anti-aliasing
Quad Overdraw Gubitak efikasnosti uzrokovan helper lanes-ima u quad-ovima, posebno izrazit za male i izduzene trouglove
Visibility Buffer Tehnika (koristi je Nanite) gde se rasterizuje samo ID trougla i baricentricne koordinate, a materijal evaluacija se radi naknadno, jednom po pikselu

Preporuceno Citanje i Resursi

Knjige

Online resursi

UE5 Dokumentacija


U sledecem poglavlju, prelazimo na temu draw call-ova i batching-a -- kako CPU komunicira sa GPU-om, zasto je broj draw call-ova vazan, i kako UE5 minimizuje overhead te komunikacije. Ako ste razumeli rasterizaciju, bice vam jasno zasto je organizacija posla koji se salje GPU-u jednako vazna kao i sam posao.