Poglavlje 32: World Partition i Level Streaming

Poglavlje 32: World Partition i Level Streaming

U ovom poglavlju suočavamo se sa jednim od najvećih izazova u razvoju igara: kako napraviti ogroman, otvoren svet koji igrač može slobodno da istražuje, a da pri tom engine ne eksplodira od količine podataka? Razumećete zašto ne možete učitati ceo svet odjednom, kako UE5 World Partition sistem deli svet na ćelije i automatski ih učitava i oslobađa, šta su Data Layers i kako vam pomažu da organizujete sadržaj, kako HLOD sistem čuva iluziju ogromnog sveta čak i na velikim rastojanjima, zašto je One File Per Actor revolucionaran za timski rad, i kako da sve to konfigurišete da radi glatko bez zastajkivanja i padova performansi.


Uvod: Iluzija beskonačnog sveta

Zamislite da pravite open-world igru. Svet je ogroman — 8 kilometara sa 8 kilometara. Ima hiljade zgrada, milione stabala, stotine NPC-ova, reke, planine, sela, gradove. Sve to treba da bude dostupno igraču koji može slobodno da hoda, vozi, ili leti u bilo kom pravcu.

A sada zamislite da pokušavate da učitate sve to odjednom u memoriju.

Brzo ćete naleteti na realnost:

Dakle, ne možete učitati ceo svet odjednom. Ovo nije preporuka — ovo je fizička nemogućnost za bilo koji netrivijalan open-world projekat.

Rešenje? Streaming — učitavanje samo onog dela sveta koji je blizu igrača, i oslobađanje svega ostalog. Dok igrač hoda na sever, delovi sveta na severu se učitavaju, a delovi na jugu koji su već daleko iza njega se oslobađaju iz memorije.

Ova ideja postoji decenijama. World of Warcraft je 2004. godine radio level streaming. GTA III je to radio 2001. Ali implementacija je tradicionalno bila bolna. Developeri su morali ručno da:

  1. Podele svet na level-e/zone
  2. Definišu trigger volume-e koji aktiviraju učitavanje
  3. Ručno upravljaju tranzicijama između zona
  4. Rešavaju edge case-ove (šta ako igrač stoji na granici dve zone?)
  5. Koordiniraju rad tima tako da različiti ljudi ne rade na istom level fajlu

UE5 je uveo World Partition — sistem koji automatizuje većinu ovog posla. Umesto da vi ručno delite svet, engine to radi za vas. Umesto da vi pišete trigger volume-e, engine automatski streemuje na osnovu udaljenosti. Umesto jednog ogromnog level fajla, svaki actor je u svom fajlu.

Hajde da zaronimo u detalje.


32.1 Problem velikih svetova — detaljno

Pre nego što pređemo na rešenja, hajde da u potpunosti razumemo problem. Ovo je važno jer ćete tek onda ceniti eleganciju World Partition rešenja.

32.1.1 Memorijski budžet

Pogledajmo tipičnu open-world scenu i koliko memorije zahteva:

Kategorija resursa           | Tipična veličina
-----------------------------|------------------
Mesh podaci (geometrija)     | 2-8 GB
Teksture                     | 4-16 GB
Zvukovi                      | 1-4 GB
Blueprint/logika             | 0.5-2 GB
Navigacioni mesh             | 0.5-2 GB
Fizika / kolizija podaci     | 0.5-1 GB
Animacioni podaci            | 1-4 GB
Level metadata               | 0.2-1 GB
-----------------------------|------------------
UKUPNO za ceo svet           | ~10-38 GB

A ovo su konzervativne procene za "prosečan" open-world. AAA naslovi sa fotorealističnom grafikom mogu lako otići na 50+ GB ukupnih resursa.

PlayStation 5 ima ukupno 16 GB memorije. Od toga, oko 2-3 GB troši operativni sistem. Ostaje vam ~13 GB za igru — za sve: rendering, gameplay, zvuk, mrežu, UI, i streaming podatke. Očigledno je da 38 GB sadržaja ne može da stane u 13 GB memorije.

Na PC-ju imate više RAM-a, ali i dalje ne možete učitati sve. A čak i da možete — ne bi trebalo.

32.1.2 Rendering budžet

Hajde da razmislimo o rendering performansama. Recimo da želite 60 FPS, što znači da imate ~16.6 milisekundi po frame-u.

Ako je ceo svet učitan i svaki actor je "vidljiv" za renderer, rendering pipeline mora da:

  1. Prolazi kroz scene traversal — pregleda svaki actor da utvrdi da li je vidljiv (frustum culling, occlusion culling). Ako imate 500.000 actor-a u svetu, čak i brza provera od 0.1 mikrosekunde po actor-u daje 50 milisekundi samo za culling. To je već 3× vaš ukupni budžet za frame.

  2. Generiše draw call-ove — za svaki vidljiv objekat. Čak i uz batching i instancing, veliko scene sa stotinama hiljada jedinstvenih objekata generiše probleme.

  3. Shadowmap rendering — senke zahtevaju re-rendering scene iz ugla svakog svetla. Ovo se multiplicira.

Rešenje je jednostavno u konceptu: ne stavljajte u rendering pipeline ono što ne treba da bude tamo. Ako je deo sveta 3 km od kamere i potpuno iza planine, zašto bi uopšte postojao u memoriji, a kamoli u rendering pipeline-u?

32.1.3 CPU budžet (Tick)

Svaki "živ" actor u UE5 može imati Tick funkciju — kod koji se izvršava svakog frame-a. To uključuje:

Ako imate 50.000 tick-ujućih actor-a, i svaki troši samo 5 mikrosekundi na tick, to je 250 milisekundi po frame-u. Opet, daleko iznad budžeta.

Realan svet ne treba da tick-uje actor-e na drugom kraju mape. NPC koji patrolira u gradu 4 km od igrača ne treba da ažurira svoju AI logiku 60 puta u sekundi.

32.1.4 Disk I/O i vreme učitavanja

Ogromne količine podataka moraju da se učitaju sa diska. Čak i sa SSD-ovima koji postižu 3-5 GB/s (ili PS5 čiji SSD postiže ~5.5 GB/s raw, ~9 GB/s komprimovano), učitavanje 10 GB podataka traje 1-2 sekunde. A ako igrač brzo putuje kroz svet (vozi auto), novi podaci moraju konstantno da stižu.

Ovo je streaming — i mora da bude dovoljno brz da svet izgleda "uvek tu", a opet dovoljno konzervativan da ne uguši I/O bus.

32.1.5 Rezime problema

Evo zašto je streaming neophodan, u jednoj tabeli:

Resurs Kapacitet Zahtev celog sveta Rešenje
RAM 13-32 GB 10-50+ GB Učitaj samo okolinu igrača
VRAM 8-24 GB 4-16 GB Streaming tekstura i geometrije
CPU (tick) 16.6 ms/frame 100+ ms za sve actore Tick samo bliske actore
Rendering 16.6 ms/frame 50+ ms za celu scenu Renderuj samo vidljivo
Disk I/O 3-5 GB/s Nepredvidiv zahtev Asinhrono učitavanje, prioritizacija

Sa ovim razumevanjem, hajde da vidimo kako World Partition rešava sve ove probleme.


32.2 World Partition sistem u UE5

32.2.1 Šta je World Partition?

World Partition je sistem u Unreal Engine 5 koji automatski deli svet na ćelije (cells) i stremuje ih (učitava/oslobađa) na osnovu udaljenosti od streaming izvora (tipično kamere ili igrača).

Ključna reč je automatski. U prethodnim verzijama Unreal Engine-a (UE4 i ranije), developer je morao da:

  1. Ručno kreira sub-levels
  2. Postavi Level Streaming Volume-e u svetu
  3. Definiše pravila za učitavanje/oslobađanje svakog sub-level-a
  4. Obezbedi da actor-i ne referenciraju druge actor-e u sub-level-ima koji nisu učitani

Sa World Partition, vi radite u jednom persistent level-u. Nema sub-level-a. Nema volume triggera. Vi samo postavljate actor-e u svet, a World Partition automatski odlučuje koji od njih treba da budu učitani u datom trenutku.

Da bi ovo funkcionisalo, World Partition interno deli svet na pravilan grid ćelija. Svaka ćelija obuhvata actor-e koji se nalaze u tom prostornom regionu. Kada je igrač blizu neke ćelije, ona se učitava. Kada se igrač udalji, ćelija se oslobađa.

32.2.2 Omogućavanje World Partition

World Partition se omogućava na nivou World Settings:

  1. Otvorite World Settings panel
  2. Nađite sekciju World Partition
  3. Omogućite Enable World Partition checkbox

Važno: Ovo se radi prilikom kreiranja projekta ili mape. Konverzija postojeće mape u World Partition je moguća (postoji Commandlet za to), ali zahteva pažnju jer menja način na koji se actor-i čuvaju na disku.

Kada kreirate nov projekat sa Open World template-om, World Partition je automatski omogućen.

32.2.3 Grid-based streaming: Kako svet postaje mreža

Koncept je jednostavan ali moćan. Zamislite da imate mapu veličine 4 km × 4 km. World Partition stavlja nevidljivi grid (mrežu) preko cele mape. Ako je veličina ćelije 256 metara, grid ima 16 × 16 = 256 ćelija.

   0    256   512   768  1024  1280  1536  1792  2048  ... 4096 (metara)
   |-----|-----|-----|-----|-----|-----|-----|-----|-----|
   |  C  |  C  |  C  |  C  |  C  |  C  |  C  |  C  |  C  |
   |-----|-----|-----|-----|-----|-----|-----|-----|-----|
   |  C  |  C  |  C  |  C  |  C  |  C  |  C  |  C  |  C  |
   |-----|-----|-----|-----|-----|-----|-----|-----|-----|
   |  C  |  C  |  C  |  C  |  ●  |  C  |  C  |  C  |  C  |
   |-----|-----|-----|-----|-----|-----|-----|-----|-----|
   |  C  |  C  |  C  |  C  |  C  |  C  |  C  |  C  |  C  |
   |-----|-----|-----|-----|-----|-----|-----|-----|-----|

   C = Ćelija (Cell)
   ● = Pozicija igrača (Streaming Source)

Svaki actor u svetu pripada tačno jednoj ćeliji, na osnovu svoje pozicije. Drvo na poziciji (600, 400) pripada ćeliji koja pokriva region (512-768, 256-512).

32.2.4 Streaming Source i distanca učitavanja

Streaming Source je tačka u svetu koja "privlači" ćelije — uzrokuje njihovo učitavanje. Tipično je to pozicija kamere igrača, ali može biti i bilo koji drugi actor kome dodelite ulogu streaming izvora.

Svaki actor (ili klasa actor-a) ima svoju Loading Distance — udaljenost na kojoj će biti učitan. Ovo je ključni koncept:

Vizuelno, zamislite krug oko igrača. Sve ćelije čiji actor-i imaju Loading Distance koji obuhvata tu ćeliju — biće učitane:

                    Loading Distance = 768m
                         ________
                       /          \
                      /   učitano  \
                     /              \
                    |    ________    |
                    |   / 512m  \   |
                    |  | učitano |  |
                    |  |   ●     |  |
                    |  | igrač   |  |
                    |   \______/    |
                    |   (inner     |
                     \   radius)  /
                      \          /
                       \________/

    Različiti actor-i mogu imati različite Loading Distance-e:
    - Stabla: 512m (geometrija + kolizija)
    - Zvukovi ambijenta: 256m (kraći domet)
    - Terenske stene: 1024m (vidljive izdaleka)
    - NPC-ovi: 384m (gameplay domet)

Ovo znači da nemaju svi actor-i isti "domet učitavanja". Velika planina koja treba da bude vidljiva izdaleka ima veću Loading Distance od male žbunaste dekoracije. Ovo je izuzetno važno za performanse i vizuelni kvalitet.

32.2.5 Automatska generacija ćelija vs. ručna konfiguracija

World Partition podržava dva pristupa za dodeljivanje actor-a ćelijama:

Automatski (default):

Ručna konfiguracija (napredno):

Ručna konfiguracija se radi kroz World Partition Editor, koji vizuelno prikazuje grid i omogućava podešavanje:

World Partition Editor
├── Runtime Hash Settings
│   ├── Grid Name: "MainGrid"
│   ├── Cell Size: 25600 (UE units = 256m pri default Scale)
│   ├── Loading Range: 51200 (512m)
│   └── Block Size: ... (optimizacija grupisanja)
└── Streaming Grids
    ├── Default Grid (za većinu actor-a)
    ├── Landscape Grid (veće ćelije za teren)
    └── Gameplay Grid (manje ćelije za gameplay)

32.2.6 Veličina ćelije i njen uticaj na granularnost

Veličina ćelije je kritičan parametar koji utiče na mnoge aspekte:

Manje ćelije (npr. 128m):

Veće ćelije (npr. 512m):

Preporuka Epic-a: Default veličina ćelije od 12800 Unreal units (128 metara) je dobar početak za većinu projekata. Za veoma velike svetove (20+ km²) možete eksperimentisati sa 25600 (256m) ili čak 51200 (512m).

Veličina ćelije   |  Tipična upotreba                    | Napomene
------------------|---------------------------------------|---------------------------
64m               |  Dense urban environments             | Previše ćelija za velike svetove
128m (default)    |  Većina open-world igara              | Dobar balans
256m              |  Veliki svetovi, umerena gustina      | Manje overhead-a, grublje
512m              |  Masivni svetovi, retka vegetacija     | Velike ćelije, retko streaming

Ovo je parametar koji ćete fine-tune-ovati tokom razvoja, na osnovu profiling podataka.

32.2.7 Minimap i vizualizacija u editoru

World Partition Editor nudi izuzetno korisnu minimap vizualizaciju koja prikazuje:

Da biste ga otvorili: Window → World Partition Editor

Ovaj alat je neophodan za debugging streaming problema. Ako igra zastajkuje kada igrač uđe u određeni deo mape, minimap će vam pokazati da ta oblast ima preveliku gustinu actor-a u jednoj ćeliji.


32.3 Data Layers (Slojevi podataka)

32.3.1 Šta su Data Layers?

Data Layers su sistem za logičko organizovanje actor-a u grupe koje se mogu nezavisno učitavati i oslobađati, nezavisno od prostornog grid-a.

Zamislite ovo ovako: grid ćelije su prostorna organizacija (šta je blizu čega). Data Layers su logička organizacija (koji actor-i pripadaju zajedno po funkciji).

Analogija: zamislite da gledate mapu grada. Grid ćelije su blokovi ulica. Ali Data Layers su kao slojevi na mapi — jedan sloj prikazuje zgrade, drugi ulice, treći parkove, četvrti tramvajske linije. Možete uključiti ili isključiti svaki sloj nezavisno.

32.3.2 Kreiranje i upravljanje Data Layer-ima

Data Layer-i se kreiraju u Data Layer Outliner (ili World Partition Data Layers panel):

Data Layers
├── Default Layer (uvek aktivan)
├── Environment_Trees (drveće, žbunovi)
├── Environment_Rocks (stene, kamenje)
├── Gameplay_NPCs (NPC-ovi, neprijatelji)
├── Gameplay_Pickups (sakupljivi predmeti)
├── Cinematic_SetPieces (objekti za cutscene)
├── Audio_Ambient (ambijentalni zvukovi)
└── Debug_Volumes (debug vizualizacije, samo za razvoj)

Da biste dodelili actor-a nekom Data Layer-u:

  1. Selektujte actor-a u level-u
  2. U Details panelu, nađite sekciju Data Layers
  3. Dodajte layer iz dropdown-a

Jedan actor može pripadati više Data Layer-a istovremeno.

32.3.3 Runtime i Editor Data Layers

Postoje dve vrste Data Layer-a:

Editor Data Layers:

Runtime Data Layers:

Runtime Data Layer podešavanja:
├── Layer Name: "Gameplay_NPCs"
├── Is Runtime: ☑ (ovo ga čini runtime layer-om)
├── Initial Runtime State: Activated (učitan na startu)
├── Data Layer Color: Zelena (vizualizacija u editoru)
└── Is Initially Visible: ☑

32.3.4 Praktični primeri upotrebe Data Layer-a

Primer 1: Dan/Noć ciklus

Data Layers:
├── Environment_DayTime
│   ├── NPC_DnevnaStraža
│   ├── NPC_Trgovac
│   ├── Particle_SunDust
│   └── Zvuk_PticePevaju
│
├── Environment_NightTime
│   ├── NPC_NoćnaStraža
│   ├── NPC_Lopov
│   ├── PointLight_BakljeUlice
│   └── Zvuk_CvrčciNoć

Kada dođe noć u igri, vaš Blueprint ili C++ kod deaktivira Environment_DayTime layer i aktivira Environment_NightTime. Svi actor-i u dnevnom layer-u se oslobađaju iz memorije, a noćni se učitavaju. Elegantno i efikasno.

Primer 2: Progresija priče

Data Layers:
├── Story_Act1
│   ├── Blockade_BridgeBarricade (barikada na mostu)
│   ├── NPC_QuestGiver_Initial
│   └── TriggerVolume_Act1Quest
│
├── Story_Act2
│   ├── DestroyedBridge_Meshes (srušeni most)
│   ├── NPC_QuestGiver_Act2
│   └── EnemySpawner_Invasion
│
├── Story_Act3
│   ├── RebuiltBridge (obnovljen most)
│   ├── NPC_Celebration (slavlje)
│   └── Particle_Fireworks

Kako igrač napreduje kroz priču, deaktivirate stare layer-e i aktivirate nove. Most počinje kao blokiran, zatim se ruši, zatim se obnavlja. Svaki "stanje" mosta je u posebnom layer-u.

Primer 3: Multiplayer — selektivno učitavanje

U multiplayer igri, različiti igrači mogu biti na različitim delovima mape. Umesto da svaki klijent učita ceo svet, svaki klijent učitava samo layer-e relevantne za svoju lokaciju i gameplay stanje.

Primer 4: Optimizacija — razdvajanje po tipu

Data Layers:
├── Env_StaticMeshes    (Loading Distance: 1024m)
├── Env_Foliage         (Loading Distance: 512m)
├── Gameplay_Actors     (Loading Distance: 384m)
├── Audio_Sources       (Loading Distance: 256m)
└── FX_Particles        (Loading Distance: 192m)

Svaki tip sadržaja ima različitu Loading Distance. Mesh-evi zgrada su vidljivi izdaleka (1024m), ali čestice dima iz dimnjaka ne moraju da postoje dok igrač nije u blizini (192m). Ovo drastično smanjuje memorijski footprint.

32.3.5 Programsko upravljanje Data Layer-ima

U Blueprint-u ili C++, možete programski kontrolisati Data Layer-e:

Blueprint pristup:

// Aktiviranje layer-a
UDataLayerManager::GetDataLayerManager(World)
    ->SetDataLayerInstanceRuntimeState(
        DataLayerInstance,
        EDataLayerRuntimeState::Activated
    );

// Deaktiviranje layer-a
UDataLayerManager::GetDataLayerManager(World)
    ->SetDataLayerInstanceRuntimeState(
        DataLayerInstance,
        EDataLayerRuntimeState::Unloaded
    );

U Blueprint-u, koristite node-ove:

Moguća stanja su:

Razlika između Loaded i Activated je suptilna ali važna. Loaded znači da su podaci u memoriji ali actor-i nisu "probuđeni" — ne renderuju se i ne izvršavaju logiku. Ovo je korisno za pre-loading: učitate layer unapred da bi, kada treba da postane vidljiv, tranzicija bila trenutna bez loading hitch-a.

Tok pre-loading-a:

Igrač se približava zoni → Učitaj layer (Loaded stanje)
                               ↓
                          Podaci su u memoriji, ali nevidljivi
                               ↓
Igrač uđe u zonu → Aktiviraj layer (Activated stanje)
                               ↓
                          Instant tranzicija, bez zastajkivanja!

32.4 HLOD — Hierarchical Level of Detail

32.4.1 Problem: Šta prikazati u daljini?

Streaming rešava problem memorije — ne učitavate ono što je daleko. Ali ovo uvodi novi problem: šta igrač vidi kada pogleda u daljinu?

Ako prosto ne učitate ćelije koje su daleko, igrač će videti prazno — rupu u svetu. Zamislite da stojite na planini i gledate grad u dolini 3 km daleko. Ako su ćelije grada neučitane jer su izvan Loading Distance, grad jednostavno neće postojati. Igrač će videti ravno tlo umesto panorame grada. To uništava iluziju.

Rešenje je HLOD (Hierarchical Level of Detail) — sistem koji generiše pojednostavljene reprezentacije udaljenih delova sveta. Kada je ćelija predaleko da bi bila potpuno učitana, HLOD prikazuje njenu simplifikovanu verziju — dovoljno dobru da održi iluziju iz daljine, ali daleko jeftiniju za rendering i memoriju.

Ovo je koncepcijski slično LOD sistemu za individualne mesh-eve (koji ćemo detaljno obraditi u poglavlju 45), ali na nivou celih regiona sveta umesto individualnih objekata.

32.4.2 Kako HLOD funkcioniše

HLOD sistem radi u nekoliko faza:

Faza 1: Analiza sadržaja ćelije

Engine prolazi kroz sve actor-e u jednoj ćeliji (ili grupi ćelija) i identifikuje one koji treba da budu zastupljeni u HLOD-u. Tipično, to su Static Mesh actor-i — zgrade, stene, drveće, infrastruktura. Dinamički actor-i (NPC-ovi, pokretni predmeti) se obično ne uključuju u HLOD jer se njihova pozicija menja.

Faza 2: Generisanje HLOD representacije

Za svaku grupu actor-a, engine generiše pojednostavljenu reprezentaciju. Postoje različite strategije:

HLOD strategije generisanja:
├── Mesh Merge
│   └── Svi mesh-evi u ćeliji se spajaju u jedan veliki mesh
│       sa smanjenim brojem trouglova
│
├── Mesh Simplification
│   └── Svaki mesh se pojednostavljuje nezavisno, pa se spajaju
│       u manji broj draw call-ova
│
├── Mesh Approximation
│   └── Generisanje potpuno novog mesh-a koji aproksimira
│       oblik originalne grupe (voxelizacija → remeshing)
│
├── Instancing (za ponavljajuće objekte)
│   └── Ako ćelija ima 500 istih stabala, HLOD može da ih
│       zameni manjim brojem instanci sa LOD mesh-evima
│
└── Impostor Textures (Billboard)
    └── Umesto mesh-a, generiše se tekstura koja prikazuje
        objekat iz različitih uglova. Krajnje jeftino za
        renderovanje (jedan quad), ali ograničeno na velika
        rastojanja gde se ne primećuje 2D priroda

Faza 3: Kreiranje HLOD hijerarhije

HLOD je hijerarhijski — otuda "H" u imenu. Postoji više nivoa:

                  HLOD Nivo 3 (najudaljeniji)
                  Ceo region: jedan mesh, ~5K trouglova
                           |
            ┌──────────────┼──────────────┐
            |              |              |
      HLOD Nivo 2    HLOD Nivo 2    HLOD Nivo 2
      Grupa ćelija   Grupa ćelija   Grupa ćelija
      ~20K tri       ~20K tri       ~20K tri
            |              |              |
     ┌──────┼──────┐      ...           ...
     |      |      |
  HLOD 1  HLOD 1  HLOD 1
  1 ćelija 1 ćelija 1 ćelija
  ~50K tri ~50K tri ~50K tri
     |      |      |
  Pune     Pune   Pune
  ćelije   ćelije ćelije
  ~500K    ~500K  ~500K
  tri      tri    tri

Dok se igrač udaljava, engine prelazi na sve grublje nivoe HLOD-a:

  1. Blizu (0-512m): Pune ćelije su učitane, renderuju se svi originalni actor-i
  2. Srednje (512m-1024m): HLOD Nivo 1, jedna ćelija je zamenjena jednim HLOD mesh-om
  3. Daleko (1024m-2048m): HLOD Nivo 2, grupe od 4+ ćelija su zamenjene jednim HLOD mesh-om
  4. Veoma daleko (2048m+): HLOD Nivo 3, celi regioni su zamenjeni minimalnom reprezentacijom

32.4.3 HLOD generisanje u UE5

HLOD-ovi se generišu kao offline proces (ne u realnom vremenu). Evo koraka:

  1. Konfigurišite HLOD Setup: U World Settings → World Partition → HLOD Setup, definišete HLOD layer-e i njihova pravila

  2. Build HLOD: Pokrenite HLOD build iz menija: Build → Build HLODs (ili iz Commandlet-a za automatizaciju)

  3. Engine procesira: Za svaku ćeliju (ili grupu ćelija), engine:

    • Prikuplja sve relevantne Static Mesh actor-e
    • Primenjuje odabranu strategiju simplifikacije
    • Generiše novi mesh i teksture za HLOD
    • Čuva HLOD kao zasebne asset-e
  4. Rezultat: Novi HLOD actor-i se kreiraju i automatski se koriste u runtime-u

HLOD konfiguracija primer:
├── HLOD Layer 0 (najbliži)
│   ├── Cell Size: 256m
│   ├── Loading Range: 512m - 1024m
│   ├── Strategy: Mesh Merge + Simplify
│   ├── Target Triangle Count: 50,000 po ćeliji
│   └── Texture Size: 2048x2048
│
├── HLOD Layer 1
│   ├── Cell Size: 1024m (4x4 grupe Layer 0 ćelija)
│   ├── Loading Range: 1024m - 4096m
│   ├── Strategy: Mesh Approximation
│   ├── Target Triangle Count: 10,000 po grupi
│   └── Texture Size: 1024x1024
│
└── HLOD Layer 2 (najdalji)
    ├── Cell Size: 4096m (4x4 grupe Layer 1 ćelija)
    ├── Loading Range: 4096m+
    ├── Strategy: Impostor + Simplified Mesh
    ├── Target Triangle Count: 2,000 po grupi
    └── Texture Size: 512x512

32.4.4 HLOD i Nanite

Ovde se dešava interesantan presek sa Nanite sistemom koji smo obradili u poglavlju 30.

Nanite već automatski radi LOD na nivou klastera za individualne mesh-eve. Ako je zgrada Nanite-enabled, ona već ima automatsku redukciju detalja sa udaljenošću. Dakle, zašto vam treba i HLOD?

Odgovor je u draw call-ovima i streaming-u:

  1. Draw call redukcija: Čak i sa Nanite-om, svaki actor u sceni zahteva neku CPU obradu za scene traversal, visibility determination, i instance data. Ako ćelija ima 1000 actor-a, to je 1000 entity-a koje CPU mora da obradi. HLOD zamenjuje tih 1000 actor-a sa jednim HLOD actor-om. Za udaljene regione, ovo je ogromna ušteda CPU vremena.

  2. Streaming efikasnost: Nanite stremuje klastere pojedinačnih mesh-eva. Kada imate hiljadu mesh-eva u udaljenoj ćeliji, Nanite mora da stremuje minimalne klastere za svakog od njih — hiljadu malih streaming operacija. HLOD zamenjuje to jednom streaming operacijom za jedan HLOD mesh.

  3. Non-Nanite sadržaj: Ne svaki mesh u sceni je Nanite-enabled (na primer: folija, transparentni objekti, animirani mesh-evi). HLOD obezbeđuje LOD sistem i za te actor-e.

Dakle, Nanite i HLOD nisu konkurencija — oni su komplementarni sistemi. Nanite upravlja LOD-om za individualne mesh-eve iz blizine, a HLOD upravlja representacijom celih regiona u daljini.

Pogledajte poglavlje 45 za detaljnu diskusiju o LOD hijerarhijama i optimizaciji, uključujući i kombinovanje Nanite sa tradicionalnim LOD sistemima.

32.4.5 Memorija vs. vizuelni kvalitet — kompromisi

HLOD generisanje je uvek kompromis. Evo ključnih parametara i njihovih trade-off-ova:

Parametar Više Manje
Triangle count Bolji kvalitet, više memorije, skuplje renderovanje Lošiji kvalitet, manja memorija, jeftinije
Texture resolution Oštriji HLOD iz blizine, više VRAM-a Mutan HLOD, manje VRAM-a
Broj HLOD nivoa Glatki prelazi, više asset-a na disku Oštriji prelazi, manje asset-a
Mesh Merge vs Approximation Merge čuva oblike, Approximation je jednostavnija Svaki ima trade-off

Praktični savet: Počnite sa default podešavanjima, build-ujte HLOD-ove, a zatim vizuelno proveravajte iz različitih udaljenosti. Obratite pažnju na:

32.4.6 HLOD za specifične tipove sadržaja

Različiti tipovi sadržaja zahtevaju različite HLOD strategije:

Zgrade i arhitektura:

Vegetacija (drveće, trava):

Teren:

Infrastruktura (putevi, mostovi, ograde):


32.5 Level Instances (Instance nivoa)

32.5.1 Problem: Ponavljajući sadržaj

U igrama je ponavljanje neizbežno. Grad sa 200 zgrada od kojih je 30 dizajnirano jedinstveno. Svaka od tih 30 zgrada se koristi na 5-10 lokacija sa malim varijacijama (rotacija, skala, detalji). Dungeon sa hodnicima koji se ponavljaju. Selo sa identičnim kućama.

Tradicionalno, svaka "instanca" zgrade bi bila potpuna kopija svih actor-a — mesh-evi, svetla, kolizija, interaktivni objekti. Ako zgrada ima 50 actor-a i pojavljuje se 10 puta, to je 500 actor-a u svetu. Svaki se zasebno čuva, zasebno učitava, zasebno zauzima memoriju.

Level Instances rešavaju ovo elegantno.

32.5.2 Šta je Level Instance?

Level Instance je reusable komad level-a — grupa actor-a dizajnirana jednom i instancirana (ponovljena) više puta u svetu. Zamislite ga kao "prefab" ali na nivou celog level chunka.

Koncept je analogan instanciranju mesh-eva: umesto da imate 100 kopija mesh podataka u memoriji, imate jednu kopiju i 100 transformacija (pozicija, rotacija, skala). Level Instance radi isto ali za grupe actor-a.

Bez Level Instances:                Sa Level Instances:
                                    
Zgrada_A_Kopija1 (50 actor-a)      LevelInstance_ZgradaA
Zgrada_A_Kopija2 (50 actor-a)        ├── Instanca 1 (Transform samo)
Zgrada_A_Kopija3 (50 actor-a)        ├── Instanca 2 (Transform samo)
Zgrada_A_Kopija4 (50 actor-a)        ├── Instanca 3 (Transform samo)
Zgrada_A_Kopija5 (50 actor-a)        └── Instanca 4 (Transform samo)
= 250 actor-a, 250x podaci           = 50 actor-a + 4 transformacije

32.5.3 Kreiranje Level Instance-a

Proces je sledeći:

  1. Dizajnirajte sadržaj: Kreirajte zgradu (ili bilo koji ponovljivi komad) sa svim potrebnim actor-ima — mesh-evi, svetla, blueprint-ovi, trigger volume-i

  2. Kreirajte Level Instance: Selektujte sve actor-e koji čine "komad", desni klik → Create Level Instance

  3. Ime i putanja: Dajte Level Instance-u ime (npr. "LI_MedievalHouse_A") i izaberite gde će se sačuvati na disku

  4. Postavljanje u svet: Level Instance se pojavljuje kao jedan actor u Outliner-u. Možete ga duplicirati i postavljati gde god želite

  5. Editovanje: Duplim klikom na Level Instance actor otvarate "nested editing" — možete editovati sadržaj Level Instance-a, i sve promene se automatski propagiraju na sve instance u svetu

32.5.4 Packed Level Actors

Packed Level Actor (PLA) je optimizovana verzija Level Instance-a. Kada konvertujete Level Instance u Packed Level Actor:

Level Instance (standardni):
├── Ceo LI se učitava/oslobađa kao celina
├── Ako je deo LI vidljiv, ceo LI mora biti učitan
└── Prostiji, ali manje granularan streaming

Packed Level Actor:
├── Interni actor-i se registruju u World Partition grid
├── Svaki interni actor može da se stremuje nezavisno
├── Deo PLA koji je blizu igrača se učitava, ostatak ne
└── Kompleksniji, ali efikasniji za velike instancirane strukture

Konverzija: desni klik na Level Instance actor → Convert to Packed Level Actor

32.5.5 Memorijske uštede kroz instanciranje

Hajde da izračunamo konkretne uštede:

Scenario: Srednjovekovni grad sa 50 kuća. Postoji 5 tipova kuća, svaki tip se ponavlja 10 puta. Svaka kuća ima 30 actor-a (mesh-evi, svetla, collision).

Bez Level Instances:

Sa Level Instances:

Sa Packed Level Actors:

32.5.6 Ograničenja Level Instances

Level Instances nisu savršeni — postoje ograničenja:

  1. Identičnost: Sve instance su identične. Ako želite varijaciju (drugačija boja jedne kuće), morate ili napraviti novi Level Instance tip, ili koristiti runtime customization mehanizme.

  2. Editovanje: Promene u jednom Level Instance-u se propagiraju na sve instance. Ovo je obično prednost (popravite bug jednom, popravi se svuda), ali može biti problem ako niste pažljivi.

  3. Nested Level Instances: Level Instance unutar Level Instance-a je podržan, ali dodaje kompleksnost. Nemojte preterivati sa dubinom nesting-a.

  4. Blueprint interakcije: Actor-i unutar Level Instance-a ne mogu direktno referencirati actor-e izvan njega (i obrnuto) na uobičajen način. Morate koristiti interfejse ili indirekciju.


32.6 One File Per Actor (OFPA)

32.6.1 Tradicionalni pristup: Jedan fajl za sve

U starijim verzijama Unreal Engine-a (i u mnogim drugim engine-ima), ceo level je bio jedan fajl na disku. Taj .umap fajl sadrži sve actor-e, njihove pozicije, komponente, podešavanja, reference — sve.

Ovo je bilo funkcionalno za male timove i male projekte, ali za open-world igre sa timovima od 50+ developera, ovaj pristup je bio katastrofa za kolaboraciju.

Zamislite ovaj scenario:

Ponedeljak ujutru:
  Ana: "Trebam da dodam drveće u severnom delu mape"
  Bojan: "Trebam da namestim osvetljenje u centru grada"
  Ceca: "Trebam da fixujem bug sa NPC-om na trgu"

Svo troje moraju da edituju isti fajl: MainLevel.umap

Opcija 1: Zaključavanje fajla (Exclusive Checkout)
  → Ana zaključa fajl. Bojan i Ceca čekaju dok Ana ne završi.
  → Potpuno serijalan rad. Neefikasno.

Opcija 2: Paralelni rad + Merge
  → Svo troje edituju svoju kopiju. Na kraju pokušavaju merge.
  → .umap je binarni fajl. Binarni merge je praktično nemoguć.
  → Neko gubi rad. Frustracija. Suze.

Ovo je bio realan, svakodnevan problem u game industriji. Timovi su razvili razne workaround-e — sub-levels (podele mapu na manje fajlove), stroge konvencije o tome ko kada sme da edituje koji deo mape, pa čak i specijalizovane merge alate za UE4 level fajlove. Ali nijedan od tih workaround-a nije bio elegantan.

32.6.2 OFPA: Svaki actor je svoj fajl

One File Per Actor (OFPA) je World Partition-ov pristup ovom problemu, i on je elegantan u svojoj jednostavnosti:

Umesto jednog .umap fajla za ceo level, svaki actor se čuva kao zasebni fajl na disku.

Kada imate World Partition level sa 10.000 actor-a, na disku imate folder sa 10.000 malih fajlova (.uasset), svaki od kojih opisuje jednog actor-a — njegov tip, poziciju, komponente, podešavanja.

Tradicionalni pristup:
Content/
└── Maps/
    └── MainWorld.umap    ← 500 MB, sadrži sve

OFPA pristup:
Content/
└── Maps/
    └── MainWorld/
        ├── MainWorld.umap    ← Mali fajl, samo metadata
        └── __ExternalActors__/
            ├── A/
            │   ├── Actor_0001.uasset
            │   ├── Actor_0002.uasset
            │   └── Actor_0003.uasset
            ├── B/
            │   ├── Actor_0004.uasset
            │   └── Actor_0005.uasset
            └── ...
                └── Actor_9999.uasset

32.6.3 Zašto je OFPA revolucionaran za timski rad

Vratimo se na naš scenario sa Anom, Bojanom i Cecom:

Sa OFPA:
  Ana: Edituje Actor_TreeGroup_North_001.uasset do Actor_TreeGroup_North_050.uasset
  Bojan: Edituje Actor_Light_CityCenter_001.uasset do Actor_Light_CityCenter_020.uasset
  Ceca: Edituje Actor_NPC_TownSquare_001.uasset

  → Svo troje rade paralelno, na potpuno različitim fajlovima
  → Nema konflikata pri commit-u u version control
  → Nema merge problema
  → Svo troje mogu da commit-uju nezavisno

Jedini slučaj konflikta je ako dvoje ljudi edituju istog actor-a — a to je retko i obično lako razrešivo.

32.6.4 OFPA i Version Control integracija

OFPA se posebno dobro slaže sa modernim version control sistemima:

Perforce (najčešći u game industriji):

Git (sve popularniji):

32.6.5 Uticaj na brzinu iteracije

OFPA ima i praktične benefite za svakodnevni rad:

Brže čuvanje:

Brži source control operations:

Selektivno učitavanje u editoru:

Otvaranje level-a:

Tradicionalno: Učitaj MainWorld.umap (500 MB) → 45 sekundi
                Svi actor-i su učitani, editor je spor

Sa OFPA:       Učitaj MainWorld.umap (mali) → 2 sekunde
               Učitaj region koji me zanima → 5 sekundi
               Radim samo sa delom koji mi treba, editor je brz

32.6.6 OFPA — potencijalni problemi

OFPA nije bez izazova:

  1. Broj fajlova na disku: Level sa 100.000 actor-a = 100.000 fajlova. Neki file sistemi (posebno NTFS na Windows-u) mogu biti spori sa velikim brojem malih fajlova. Epic preporučuje SSD za razvoj sa OFPA.

  2. Source control overhead: Checkout/submit operacije sa hiljadama fajlova mogu biti spore na nekim VCS sistemima. Perforce je optimizovan za ovo; Git sa LFS zahteva pažljivo podešavanje.

  3. Referencing između actor-a: Kada jedan actor referencira drugog (npr. trigger volume koji otvara vrata), ta referenca mora biti robusna čak i ako se referencirani actor premesti ili obriše. UE5 koristi stabilne actor GUID-ove za ovo.

  4. Build pipeline: CI/CD sistemi moraju biti svesni OFPA strukture. Cooking i packaging moraju pravilno obraditi hiljade malih fajlova.


32.7 Streaming konfiguracija — detalji

32.7.1 Loading i Unloading distance

Svaki actor (ili klasa actor-a) u World Partition svetu može imati konfigurisane streaming parametre. Dva ključna parametra su:

Loading Distance (rastojanje učitavanja):

Unloading Distance (rastojanje oslobađanja):

Hysteresis primer:

Loading Distance:   500m
Unloading Distance: 600m

Igrač na 490m → Actor se učitava (ispod 500m)
Igrač na 510m → Actor ostaje učitan (ispod 600m, hysteresis)
Igrač na 510m → Igrač se vrati na 490m → Actor je i dalje učitan (nikad nije oslobođen)
Igrač na 610m → Actor se oslobađa (iznad 600m)

Bez hysteresis-a:
Loading = Unloading = 500m
Igrač na 499m → Učitaj!
Igrač na 501m → Oslobodi!
Igrač na 499m → Učitaj!
Igrač na 501m → Oslobodi!
→ Thrashing! Stalno učitavanje/oslobađanje = hitches i disk I/O

32.7.2 Priority nivoi

Nisu svi actor-i jednako važni za streaming. UE5 dozvoljava dodeljivanje prioriteta streaming operacijama:

Streaming Priority:
├── Critical (najviši prioritet)
│   └── Teren, velike zgrade, strukturalni elementi
│   └── Moraju biti učitani pre nego što igrač stigne
│
├── High
│   └── Srednje zgrade, važne stene, vidljive strukture
│   └── Učitavaju se odmah nakon Critical
│
├── Medium (default)
│   └── Vegetacija, dekoracija, ambijentalni objekti
│   └── Učitavaju se kada ima streaming budžeta
│
├── Low
│   └── Sitni detalji, daleki objekti, audio triggeri
│   └── Učitavaju se poslednji
│
└── Lowest
    └── Debug objekti, opcioni sadržaj
    └── Učitavaju se samo ako ima viška resursa

Prioriteti su ključni za sprečavanje vizuelnih problema. Ako igrač brzo putuje kroz svet (vozi automobil), ne želite da prvo vidi drveće a zatim teren ispod njega — to bi izgledalo kao da drveće lebdi u vazduhu. Teren mora imati viši prioritet od vegetacije.

32.7.3 Streaming Sources

Streaming Source je svaka tačka u svetu koja "privlači" učitavanje ćelija. Po default-u, to je kamera igrača (ili Player Controller), ali možete dodati dodatne streaming izvore:

Tipični streaming izvori:

  1. Player Camera (default): Učitava svet oko igrača
  2. Vehicle: Ako igrač vozi, možda želite da streaming source bude malo ispred vozila (u pravcu kretanja) da bi svet bio spreman pre nego što igrač stigne
  3. Cinematic Camera: Tokom cutscene, streaming source je kamera cutscene-a, ne igrača
  4. Teleportation Target: Ako znate da će igrač biti teleportovan na novu lokaciju, možete unapred postaviti streaming source tamo da preloaduje sadržaj
  5. AI Director: U nekim igrama, AI sistema može zahtevati učitavanje specifičnog regiona pre nego što tamo pošalje neprijatelje

Streaming source se dodaje actor-u kroz WorldPartitionStreamingSourceComponent:

// C++ primer: Dodavanje streaming source komponente actor-u
UPROPERTY(VisibleAnywhere)
UWorldPartitionStreamingSourceComponent* StreamingSourceComponent;

// U konstruktoru:
StreamingSourceComponent = CreateDefaultSubobject<UWorldPartitionStreamingSourceComponent>(
    TEXT("StreamingSource")
);
StreamingSourceComponent->SetStreamingSourceEnabled(true);

U Blueprint-u, dodajte World Partition Streaming Source komponentu actor-u kroz Add Component dugme.

32.7.4 Blueprint-kontrolisano streaming

Pored automatskog distance-based streaming-a, možete programski kontrolisati streaming iz Blueprint-a ili C++:

Streaming na zahtev (On-Demand Loading):

Blueprint Logika:
Primer: Igrač otvori mapu sveta → Pre-load region koji igrač odabere

Event: "Player Selected Map Region"
  → Get Data Layer Manager
  → Set Data Layer Runtime State
      Layer: "Region_North"
      New State: Loaded (pre-load, još ne prikazuj)
  → Wait for Layer to be Loaded (async)
  → Set Data Layer Runtime State
      Layer: "Region_North"
      New State: Activated (sada prikaži)
  → Teleport Player to Region_North

Streaming za cutscene:

Blueprint Logika:
Primer: Pre cutscene, osiguraj da je cela lokacija učitana

Event: "Cutscene Trigger Entered"
  → Begin Async Loading:
      Data Layer "Cinematic_SetPieces" → Loaded
      Data Layer "Cinematic_NPCs" → Loaded
  → Wait for All Layers Loaded
  → Activate All Cinematic Layers
  → Start Cinematic Sequence
  → (po završetku cutscene)
  → Deactivate Cinematic Layers

32.7.5 Custom Streaming Logic

Za napredne slučajeve, možete implementirati potpuno custom streaming logiku nasleđivanjem UWorldPartitionStreamingPolicy:

UCLASS()
class UMyCustomStreamingPolicy : public UWorldPartitionStreamingPolicy
{
    GENERATED_BODY()

public:
    // Override za custom logiku odlučivanja šta treba učitati
    virtual bool ShouldLoadCell(
        const UWorldPartitionRuntimeCell* Cell,
        const FWorldPartitionStreamingSource& Source
    ) const override
    {
        // Primer: Učitaj ćeliju samo ako je igrač gleda
        // (direction-based streaming umesto čistog distance-based)
        FVector ToCell = Cell->GetCenter() - Source.GetLocation();
        FVector Forward = Source.GetForwardVector();

        float Dot = FVector::DotProduct(ToCell.GetSafeNormal(), Forward);

        // Učitaj ako je ćelija ispred igrača (Dot > 0) ILI veoma blizu
        float Distance = ToCell.Size();
        return (Dot > 0.0f) || (Distance < MinAlwaysLoadDistance);
    }
};

Ovo je napredna tema, ali illustruje fleksibilnost sistema. Umesto prostog kruga oko igrača, možete implementirati streaming koji favorizuje pravac u kome igrač gleda — učitavajući više sadržaja ispred igrača nego iza njega.


32.8 Performance — praktični aspekti

32.8.1 Streaming budžet: Koliko učitati po frame-u

Ovo je jedan od najvažnijih koncepata za glatko streaming iskustvo.

Učitavanje actor-a nije besplatno. Kada engine učitava ćeliju, mora da:

  1. Pročita podatke sa diska (I/O operacija)
  2. Deserijalizuje podatke (parsira binarne fajlove u objekte)
  3. Kreira actor-e u memoriji (alokacija, konstruktori)
  4. Registruje actor-e u engine sistemima (rendering, fizika, navigacija)
  5. Inicijalizuje actor-e (BeginPlay, inicijalne animacije, itd.)

Svaki od ovih koraka troši CPU vreme. Ako pokušate da učitate previše actor-a u jednom frame-u, desiće se hitch — vidljivo zastajkivanje igre.

Streaming budžet je koliko vremena (u milisekundama) engine sme da potroši na streaming operacije u jednom frame-u:

Tipični streaming budžet:

Target FPS: 60 (16.6 ms po frame-u)

Rendering:    ~8 ms
Gameplay/AI:  ~3 ms
Physics:      ~2 ms
Audio:        ~1 ms
Streaming:    ~1-2 ms  ← Streaming budžet
Ostalo:       ~0.6 ms

Ukupno:       ~16 ms

Ako streaming budžet bude prekoračen (npr. pokušava da učita veliku ćeliju koja zahteva 10 ms), engine mora da:

UE5 interno upravlja ovim budžetom, ali vi možete konfigurisati agresivnost:

Konzolne komande za streaming budget:
  s.AsyncLoadingTimeLimit      — Max ms za async loading po frame-u
  s.AsyncLoadingUseFullTimeLimit  — Da li koristiti ceo budžet
  s.LevelStreamingActorsUpdateTimeLimit — Max ms za actor update
  s.PriorityAsyncLoadingExtraTime — Extra vreme za high priority

32.8.2 Hitches tokom streaming-a: Uzroci i rešenja

Hitch (zastajkivanje) je najčešći problem u streaming sistemima. Evo glavnih uzroka i rešenja:

Uzrok 1: Prevelika ćelija

Problem: Ćelija sadrži 500 actor-a sa ukupno 200 MB podataka.
         Učitavanje traje 150 ms = ogroman hitch na 60 FPS.

Rešenje:
  → Smanjite veličinu ćelije (npr. sa 256m na 128m)
  → Manje actor-a po ćeliji = manje podataka po učitavanju
  → Ili: Razložite actor-e na različite Data Layer-e sa različitim
    Loading Distance-ima, tako da se ne učitavaju svi odjednom

Uzrok 2: Sinhrone operacije

Problem: Neki actor-i zahtevaju sinhrono učitavanje resursa u
         BeginPlay. Na primer, Blueprint koji u BeginPlay
         sinhrono učitava veliku teksturu:
         UTexture2D* Tex = LoadObject<UTexture2D>(...);
         Ovo blokira game thread dok se tekstura ne učita.

Rešenje:
  → Koristite asinhrono učitavanje:
    FStreamableManager::RequestAsyncLoad(...)
  → Ili: Pre-load resurse unapred, pre nego što actor treba
    da se pojavi
  → Nikada nemojte koristiti sinhroni LoadObject u BeginPlay
    za velike resurse

Uzrok 3: Brzo kretanje igrača

Problem: Igrač vozi automobil brzinom 200 km/h. Na toj brzini,
         prolazi 55 metara po sekundi. Ako je ćelija 128m,
         igrač prelazi celu ćeliju za ~2.3 sekunde. Engine mora
         konstantno da učitava nove ćelije i oslobađa stare.

Rešenje:
  → Povećajte Loading Distance (učitavajte dalje ispred)
  → Koristite predictive streaming (stream u pravcu kretanja)
  → Smanjite gustinu actor-a uz put
  → Koristite HLOD da "pokrije" dok se puna ćelija učitava
  → Dodajte streaming source ispred vozila

Uzrok 4: Garbage Collection spike

Problem: Kada se ćelija oslobodi, njeni actor-i se uklanjaju.
         Garbage Collector mora da počisti memoriju. Ako se
         mnogo actor-a oslobodi odjednom, GC spike može
         prouzrokovati hitch.

Rešenje:
  → Postepeno oslobađanje (ne oslobađaj sve odjednom)
  → Incremental GC podešavanja u Project Settings
  → s.UnregisterComponentsTimeLimit — limitira vreme
    za unregistration po frame-u

32.8.3 Asinhrono učitavanje — zašto je kritično

Sinhrono učitavanje znači: "Stani sve, učitaj podatke, pa nastavi." Ovo blokira game thread i garantuje hitch. Nikada ne radite sinhrono učitavanje velikih resursa tokom gameplay-a.

Asinhrono učitavanje znači: "Pokreni učitavanje u pozadini, nastavi sa igrom, obavesti me kad je gotovo." Ovo je fundamentalno za glatko streaming iskustvo.

UE5 async loading pipeline radi ovako:

Game Thread                     Async Loading Thread
    |                                    |
    |  → Zahtev: "Učitaj ćeliju C42"     |
    |─────────────────────────────────────>|
    |                                    |
    |  (nastavlja normalan rad:          | ← Čita sa diska
    |   rendering, gameplay,             | ← Deserijalizuje
    |   fizika...)                        | ← Kreira objekte
    |                                    |
    |  ← Obaveštenje: "C42 je spremna"   |
    |<─────────────────────────────────────|
    |                                    |
    |  Registruj actor-e u scene         |
    |  (ovo mora na Game Thread,         |
    |   ali je brzo jer su podaci        |
    |   već u memoriji)                  |

Ključna stvar: disk čitanje i deserijalizacija se dešavaju na background thread-u i ne utiču na frame rate. Samo finalna registracija actor-a mora da se desi na game thread-u, i to je relativno brzo.

UE5 koristi Priority Async Loading sistem koji inteligentno raspoređuje loading operacije:

  1. Najpre učitava high-priority ćelije (one najbliže igraču, ili sa Critical prioritetom)
  2. Zatim medium-priority
  3. Na kraju low-priority, ali samo ako ima viška budžeta

32.8.4 Memorijsko upravljanje za streaming sadržaj

Streaming unosi dinamičnost u memorijsku upotrebu — memorija se konstantno alocira i oslobađa. Ovo zahteva pažljivo upravljanje:

Memory Budget (Memorijski budžet):

Definisite koliko memorije sme streaming da koristi. Ovo je posebno kritično na konzolama gde je memorija fiksna:

Memorijski budžet primer (PS5):

Ukupan RAM:                 16 GB
OS Reserved:                ~2.5 GB
Engine Core:                ~1.5 GB
Rendering Buffers:          ~2 GB
Audio:                      ~0.5 GB
Gameplay / Blueprint:       ~1 GB
Streaming budžet:           ~8 GB   ← Za dinamički sadržaj
Reserve (safety margin):    ~0.5 GB

Tih 8 GB za streaming mora da pokrije sve stremovane ćelije. Ako igrač stoji na mestu gde se mnogo ćelija poklapa (na vrhu planine gledajući grad), lako se može desiti da zahtev premaši budžet.

Memory Pressure Handling:

Kada memorija postane pretijesna, engine preduzima akcije:

Memorijski pritisak (memory pressure):

Nivo 1 (Upozorenje):
  → Smanjuje Loading Distance za non-critical actor-e
  → Agresivnije oslobađa udaljene ćelije

Nivo 2 (Kritičan):
  → Forsira oslobađanje svih ćelija osim neposredne okoline
  → Spušta HLOD kvalitet
  → Smanjuje texture streaming kvalitet (mip bias)

Nivo 3 (Vanredan):
  → Out of Memory zaštita
  → Može dovesti do vidljivih pop-in artefakata
  → Engine loguje upozorenja

Praktični saveti za memorijsko upravljanje:

  1. Profilirajte memoriju redovno: Koristite stat Memory, stat Streaming, memreport -full console komande

  2. Postavite hard limit: U Project Settings, postavite maximum memory za streaming. Engine će poštovati ovaj limit čak i ako to znači da se neke ćelije ne učitaju.

  3. Optimizujte sadržaj: Smanjite veličinu asset-a (kompresija tekstura, LOD-ovi za mesh-eve, audio kompresija). Ovo je najefektivniji način da smanjite memorijski pritisak.

  4. Testirajte edge case-ove: Idite na lokacije u svetu gde se mnogo ćelija preklapa (planinski vrhovi, otvorene ravnice sa pogledom na mnogo sadržaja). Tu se najčešće javljaju problemi.

32.8.5 Profiling streaming performansi

UE5 nudi nekoliko alata za profiling streaming-a:

Konzolne komande:

stat Streaming           — Opšte streaming statistike
stat StreamingDetails    — Detaljna razrada po ćeliji
stat LevelStreaming      — Level streaming specifično
stat Memory              — Memorijska upotreba
stat AsyncLoad           — Async loading statistike

wp.Runtime.ToggleDrawRuntimeHash2D  — Vizualizacija grid-a u runtime-u
wp.Runtime.OverrideRuntimeSpatialHashLoadingRange — Override loading range

Unreal Insights:

Za detaljnu analizu, koristite Unreal Insights profiler:

World Partition Debugging:

U editoru (PIE mode), koristite World Partition Editor da vizuelno pratite:

32.8.6 Čest problem: Pop-in

"Pop-in" je kada se objekti iznenada pojave u kadru — umesto glatkog prelaza, drvo ili zgrada "iskočI" iz ničega. Ovo se dešava kada:

  1. HLOD ne pokriva tranziciju: Ćelija se učitava, ali HLOD se prerano ukloni, pa postoji kratak period gde nema ni HLOD-a ni pune ćelije.

    • Rešenje: Podešavajte HLOD/ćelija overlap tako da HLOD ostane aktivan dok se puna ćelija potpuno ne učita.
  2. Loading Distance je premali: Actor-i postaju vidljivi pre nego što budu učitani jer je kamera video dalje od Loading Distance-a.

    • Rešenje: Povećajte Loading Distance ili dodajte atmosferski fog koji ograničava vidljivost.
  3. Brzo kretanje: Igrač se kreće brže nego što engine stiže da učitava.

    • Rešenje: Predictive loading, veći Loading Distance, ili ograničavanje brzine igrača u problematičnim oblastima.
Idealan streaming timeline:

Vreme:    t0       t1       t2       t3       t4
          |--------|--------|--------|--------|
HLOD:     [========HLOD aktivan========]
Ćelija:              [===učitavanje===]
Puna:                                 [=====aktivna=====]

Nema "rupe" — HLOD pokriva dok se ćelija učitava.
Tranzicija HLOD → puna ćelija je glatka.


Loš streaming timeline:

Vreme:    t0       t1       t2       t3       t4
          |--------|--------|--------|--------|
HLOD:     [==HLOD==]
Ćelija:                     [===učitavanje===]
Puna:                                        [=====]

RUPA! Između t1 i t3 nema ni HLOD-a ni pune ćelije.
Igrač vidi prazno, pa "pop" — pojava sadržaja.

32.9 Praktični workflow: Od prazne mape do streamovanog sveta

Hajde da prođemo kroz kompletan workflow za postavljanje World Partition projekta. Ovo nije tutorijalski korak-po-korak (za to postoji odlična dokumentacija na Epic-ovom sajtu), ali jeste pregled celog procesa da biste razumeli kako se svi koncepti uklapaju.

32.9.1 Korak 1: Kreiranje projekta

32.9.2 Korak 2: Definisanje grid-a

32.9.3 Korak 3: Kreiranje sadržaja

32.9.4 Korak 4: Organizacija sa Data Layer-ima

32.9.5 Korak 5: Konfigurisanje streaming parametara

32.9.6 Korak 6: Level Instances za ponavljajući sadržaj

32.9.7 Korak 7: HLOD generisanje

32.9.8 Korak 8: Profiling i optimizacija

32.9.9 Korak 9: Iteracija


32.10 World Partition i multiplayer

World Partition ima posebne implikacije za multiplayer igre:

32.10.1 Server-side streaming

U dedicated server arhitekturi, server mora da zna o svim actor-ima u svetu (ili barem o onima relevantnim za gameplay). Ali server nema kameru — nema "vizuelni" streaming source.

Umesto toga, server koristi pozicije svih povezanih igrača kao streaming izvore. Ćelije se učitavaju na serveru za svaku oblast gde se nalazi barem jedan igrač.

Server streaming logika:

Igrač A je na poziciji (1000, 2000) → Učitaj ćelije oko (1000, 2000)
Igrač B je na poziciji (5000, 3000) → Učitaj ćelije oko (5000, 3000)
Igrač C je na poziciji (1100, 2100) → Već pokriveno ćelijama Igrača A

Server ima učitane ćelije = unija svih igrača

32.10.2 Network Relevancy i streaming

UE5 network relevancy sistem se integriše sa World Partition:

32.10.3 Server Streaming Source

Za server bez vizualne reprezentacije, koristite AWorldPartitionReplicationManager da definišete streaming izvore na osnovu gameplay logike umesto vizuelne blizine.


32.11 Migracija sa starog sistema

Ako imate postojeći UE4 ili rani UE5 projekat koji koristi tradicionalni sub-level streaming, možete ga migrirati na World Partition:

32.11.1 Koraci migracije

  1. Backup: Napravite potpun backup projekta. Ovo je jednosmerna migracija.

  2. Commandlet: Pokrenite WorldPartitionConvertCommandlet:

    UnrealEditor.exe ProjectName -run=WorldPartitionConvertCommandlet MapPath
    
  3. Verifikacija: Otvorite konvertovanu mapu i proverite:

    • Da li su svi actor-i pravilno raspoređeni u ćelije
    • Da li su reference između actor-a intaktne
    • Da li streaming radi kao što je očekivano
  4. Podešavanje: Konfigurišite grid veličine, Data Layer-e, Loading Distance-e

  5. OFPA: Konverzija automatski uključuje OFPA — svaki actor postaje zasebni fajl

32.11.2 Potencijalni problemi pri migraciji


32.12 Napredne teme

32.12.1 World Partition i Proceduralni sadržaj

Ako generišete sadržaj proceduralno (na primer, proceduralni teren ili rasporeed drveća), morate biti svesni kako se to integriše sa World Partition:

32.12.2 World Partition i Landscape

Landscape actor u UE5 ima posebnu integraciju sa World Partition:

32.12.3 World Partition i World Composition (legacy)

World Composition je stariji sistem iz UE4 koji je preteča World Partition-a. Ako ga sretnete u legacy projektima:

32.12.4 Large World Coordinates (LWC)

UE5 je uveo Large World Coordinates — korišćenje double-precision (64-bit) floating point umesto single-precision (32-bit) za pozicije u svetu. Ovo je direktno povezano sa World Partition jer omogućava svetove koji su mnogo veći od onoga što 32-bit float može da predstavi bez gubitka preciznosti.

Sa 32-bit float-om, na udaljenostima većim od ~5-10 km od origin-a, počinjete da gubite preciznost — objekti "podrhtavaju" (jittering), fizika se raspada, animacije postaju neravnomerne. LWC rešava ovo, omogućavajući svetove od 100+ km bez problema sa preciznošću.


32.13 Česte greške i kako ih izbeći

Greška 1: Previše actor-a u jednoj ćeliji

Simptom: Hitch kada igrač uđe u specifičnu oblast. Uzrok: Jedna ćelija ima 2000+ actor-a jer je gustina sadržaja previsoka. Rešenje: Smanjite cell size, ili koristite Hierarchical Instanced Static Mesh (HISM) umesto individualnih actor-a za ponavljajuće objekte (travu, kamenje).

Greška 2: Sinhrono učitavanje resursa u BeginPlay

Simptom: Kratak freeze pri učitavanju ćelija. Uzrok: Blueprint ili C++ kod koji sinhrono učitava asset-e. Rešenje: Uvek koristite async loading (AsyncLoadAsset, StreamableManager).

Greška 3: Ignorisanje HLOD-a

Simptom: Praznine u daljini — delovi sveta "nestaju" sa udaljenošću. Uzrok: HLOD-ovi nisu generisani ili su loše konfigurisani. Rešenje: Build-ujte HLOD-ove i vizuelno verifikujte iz svih perspektiva.

Greška 4: Isti Loading Distance za sve

Simptom: Nepotrebno visoka memorijska upotreba ili vizualni pop-in. Uzrok: Svi actor-i imaju isti Loading Distance. Rešenje: Diferencijalne udaljenosti po tipu. Teren i zgrade daleko, detalji blizu.

Greška 5: Prevelik Loading Distance

Simptom: Konstantno visoka memorijska upotreba, potencijalni crash na konzolama. Uzrok: Loading Distance je toliko velik da je previše ćelija učitano istovremeno. Rešenje: Smanjite Loading Distance i koristite HLOD za prikaz u daljini.

Greška 6: Zaboravljen hysteresis

Simptom: Konstantno "blinkanje" objekata na granici Loading Distance-a. Uzrok: Loading i Unloading distance su identični. Rešenje: Unloading distance treba da bude 10-20% veći od Loading Distance-a.

Greška 7: Ne testiranje na target hardveru

Simptom: Sve radi na development PC-ju, ali crashuje na konzoli. Uzrok: Development PC ima 64 GB RAM-a; PS5 ima 16 GB. Rešenje: Redovno testirajte na najslabijem target hardveru. Profilirajte memoriju.


32.14 Rezime poglavlja

World Partition je jedan od najvažnijih sistema u UE5 za razvoj open-world igara. Hajde da rezimiramo šta smo naučili:

  1. Problem: Veliki svetovi ne mogu da stanu u memoriju odjednom. RAM, VRAM, CPU i GPU imaju konačne resurse.

  2. World Partition Grid: Engine automatski deli svet na ćelije i stremuje ih na osnovu udaljenosti od streaming source-a. Veličina ćelije utiče na granularnost i overhead.

  3. Data Layers: Logičko organizovanje actor-a u grupe koje se mogu nezavisno učitavati/oslobađati. Koriste se za dan/noć cikluse, progresiju priče, optimizaciju po tipu sadržaja.

  4. HLOD: Hierarchical Level of Detail — pojednostavljene reprezentacije udaljenih regiona koje održavaju iluziju ogromnog sveta bez punog učitavanja. Komplementaran sa Nanite-om (poglavlje 30), detaljno o LOD sistemima u poglavlju 45.

  5. Level Instances: Reusable komadi level-a za ponavljajući sadržaj. Packed Level Actors dodaju fino granulirano streaming.

  6. OFPA: One File Per Actor — svaki actor je zasebni fajl na disku, eliminišući merge konflikte i dramatično poboljšavajući kolaboraciju u timu.

  7. Streaming konfiguracija: Loading/Unloading distance, prioriteti, streaming sources, programska kontrola. Hysteresis sprečava thrashing.

  8. Performance: Streaming budžet, async loading, memorijsko upravljanje, i borba protiv hitch-eva i pop-in artefakata.


Tabela ključnih termina

Termin (EN) Opis
World Partition UE5 sistem za automatsko deljenje sveta na streamable ćelije
Cell Jedna ćelija u World Partition grid-u, sadrži actor-e u tom prostornom regionu
Streaming Source Tačka u svetu (obično kamera) koja determiniše koje ćelije se učitavaju
Loading Distance Udaljenost na kojoj se actor/ćelija počinje učitavati
Unloading Distance Udaljenost na kojoj se actor/ćelija oslobađa (veća od Loading Distance za hysteresis)
Hysteresis Razlika između Loading i Unloading distance-a koja sprečava thrashing
Data Layer Logička grupa actor-a koja se može nezavisno učitavati/oslobađati
Runtime Data Layer Data Layer koji se može programski kontrolisati tokom igre
HLOD (Hierarchical Level of Detail) Pojednostavljena reprezentacija udaljenih regiona sveta
Mesh Merge HLOD strategija: spajanje mesh-eva u jedan
Mesh Approximation HLOD strategija: kreiranje novog mesh-a koji aproksimira grupu
Impostor / Billboard HLOD strategija: 2D tekstura umesto 3D mesh-a za veoma daleke objekte
Level Instance Reusable komad level-a koji se može instancirati više puta
Packed Level Actor (PLA) Optimizovana verzija Level Instance-a sa finijim streaming-om
OFPA (One File Per Actor) Sistem gde se svaki actor čuva kao zasebni fajl na disku
Streaming Budget Maksimalno vreme (ms) dodeljeno streaming operacijama po frame-u
Async Loading Asinhrono učitavanje podataka na background thread-u bez blokiranja game thread-a
Pop-in Vizuelni artefakt gde se objekti iznenada pojavljuju umesto glatke tranzicije
Hitch Kratko zastajkivanje (frame spike) uzrokovano skupom operacijom
Thrashing Konstantno učitavanje/oslobađanje istog sadržaja zbog nedostatka hysteresis-a
Large World Coordinates (LWC) 64-bit double precision pozicije za podršku velikih svetova bez jittering-a
Memory Pressure Stanje u kome memorijska upotreba preti da premaši dostupne resurse
Streaming Grid Konfigurabilan grid u World Partition sa specifičnom veličinom ćelija
Runtime Hash Sistem koji u runtime-u mapira pozicije na ćelije za streaming odluke
Scene Traversal Proces prolaska kroz sve actor-e u sceni za rendering ili logiku

Preporučeno čitanje i resursi

Zvanična Epic dokumentacija

Epic GDC prezentacije

Poglavlja u ovoj knjizi

Community resursi


U sledećem poglavlju prelazimo na novi deo engine-a. Do sada smo pokrili rendering, osvetljenje, materijale, i sada upravljanje velikim svetovima. Znanje iz ovog poglavlja — posebno razumevanje streaming-a, HLOD-a, i memorijskog upravljanja — biće fundamentalno za svaki ozbiljan open-world projekat koji budete radili u UE5.