UE5 Optimizacija — Kompletan Vodič
Ovaj dokument pokriva sve optimizacione tehnike koje smo primenili na archviz UE5 projektu (Simulore_Tirsova), uključujući Python skripte, konzolne komande, i workflow savete za buduće projekte.
Sadržaj
- Dijagnostika — kako profilisati scenu
- Python skripte za analizu i bulk operacije
- Konzolne komande za rendering optimizaciju
- Scalability i INI konfiguracija
- Packaging fix za kopirane projekte
- Mega Audit skripta — sve na jednom mestu
- Saveti i best practices
1. Dijagnostika — kako profilisati scenu
Osnovne stat komande (u konzoli ~)
stat fps — prikazuje FPS
stat unit — Game thread, Render thread, GPU, Frame time
stat gpu — detaljan GPU breakdown po pass-u (NAJKORISNIJI)
stat SceneRendering — draw calls, tri count, light count
stat Nanite — Nanite specifične statistike
stat quad — quad overdraw vizualizacija
ProfileGPU — ili Ctrl+Shift+, za najdetaljniji GPU breakdown
Viewport vizualizacije
U viewportu klikni Lit dropdown → Optimization Viewmodes:
- Shader Complexity — zeleno = jeftino, crveno/belo = skupo
- Light Complexity — koliko lightova utiče na svaki piksel
- Quad Overdraw — koliko puta se piksel renderuje
Kako čitati stat gpu output
Pogledaj Avg kolonu. Najveći brojevi su tvoji bottleneck-ovi. Tipični krivci:
- Shadow Depths — previše shadow-casting lightova ili previše geometrije koja baca senke
- RenderDeferredLighting — previše overlapping dynamic lightova
- Basepass — skupi materijali ili previše geometrije
- Lights — broj i radius lightova
- Translucency — translucent materijali (staklo, fog, itd.)
- LumenScreenProbeGather — Lumen GI cost (compute queue)
- DLSS — DLSS upscaling cost (obično ~0.5ms, zanemarivo)
Workflow
- Pokreni
stat gpu - Identifikuj najskuplji pass
- Pokreni odgovarajuću Python skriptu za analizu
- Primeni fix (skripta ili ručno)
- Ponovo
stat gpusa istog mesta - Ponovi dok nisi zadovoljan
2. Python skripte za analizu i bulk operacije
VAŽNO: Pre pokretanja skripti, proveri da je Python Editor Script Plugin uključen (Edit → Plugins → Python). U Output Log-u prebaci dropdown sa "Cmd" na "Python". Za duže skripte, sačuvaj kao .py fajl i pokreni sa File → Execute Python Script.
2.1 Pronađi sve meshove bez Nanite-a iznad X tria
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
for actor in actors:
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp and comp.static_mesh:
mesh = comp.static_mesh
tris = mesh.get_num_triangles(0)
nanite = mesh.get_editor_property('nanite_settings').enabled
if tris > 5000 and not nanite:
print(f"{actor.get_actor_label()} | {mesh.get_name()} | {tris} tris | Nanite: OFF")
Šta radiš sa outputom: Sve što ima >5k tria bez Nanite-a je kandidat za uključivanje. Izuzeci: translucent meshovi (staklo), animirani meshovi.
2.2 Bulk uključi Nanite na sve meshove >5k tria
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
processed = set()
count = 0
for actor in actors:
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp and comp.static_mesh:
mesh = comp.static_mesh
path = mesh.get_path_name()
if path in processed:
continue
tris = mesh.get_num_triangles(0)
settings = mesh.get_editor_property('nanite_settings')
if tris > 5000 and not settings.enabled:
settings.enabled = True
mesh.set_editor_property('nanite_settings', settings)
processed.add(path)
count += 1
print(f"Enabled Nanite: {mesh.get_name()} ({tris} tris)")
print(f"\nDone. Enabled Nanite on {count} unique meshes.")
Posle pokretanja: Vizuelno proveri staklo i lišće drveća — ako ima artefakata, isključi Nanite samo na tim meshovima.
2.3 Isključi Nanite na translucent meshovima
import unreal
meshes_to_fix = ["platforme_Staklo", "fasade_pt2"] # dodaj svoja imena
for asset_path in unreal.EditorAssetLibrary.list_assets("/Game/", recursive=True):
asset_name = asset_path.split("/")[-1].split(".")[0]
if asset_name in meshes_to_fix:
mesh = unreal.EditorAssetLibrary.load_asset(asset_path)
if isinstance(mesh, unreal.StaticMesh):
settings = mesh.get_editor_property('nanite_settings')
if settings.enabled:
settings.enabled = False
mesh.set_editor_property('nanite_settings', settings)
unreal.EditorAssetLibrary.save_loaded_asset(mesh)
print(f"Nanite OFF + saved: {asset_path}")
2.4 Smanji Keep Triangle Percent na velikim Nanite meshovima
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
processed = set()
count = 0
for actor in actors:
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp and comp.static_mesh:
mesh = comp.static_mesh
path = mesh.get_path_name()
if path in processed:
continue
tris = mesh.get_num_triangles(0)
settings = mesh.get_editor_property('nanite_settings')
if settings.enabled and tris > 50000:
settings.keep_percent_triangles = 50.0
settings.trim_relative_error = 0.03
mesh.set_editor_property('nanite_settings', settings)
processed.add(path)
count += 1
print(f"Reduced: {mesh.get_name()} ({tris} tris) -> 50% keep, 0.03 trim")
print(f"\nAdjusted {count} meshes")
Ako vidiš artefakte: Vrati na 100% samo taj mesh. Za agresivniju redukciju: 30% keep, 0.05 trim.
2.5 Prebaci sve lightove na Stationary
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
count = 0
for actor in actors:
if isinstance(actor, (unreal.PointLight, unreal.SpotLight, unreal.RectLight)):
actor.root_component.set_mobility(unreal.ComponentMobility.STATIONARY)
count += 1
print(f"Set {count} lights to Stationary")
Zašto: Stationary lightovi su mnogo jeftiniji od Movable jer koriste baked shadowmape za statičnu geometriju. Posle pokretanja, uradi Build Lighting.
2.6 Analiza overlapping lightova
import unreal
import math
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
lights = []
for actor in actors:
if isinstance(actor, (unreal.PointLight, unreal.SpotLight, unreal.RectLight)):
loc = actor.get_actor_location()
comp = actor.get_component_by_class(unreal.LocalLightComponent)
if comp:
radius = comp.attenuation_radius
lights.append((actor.get_actor_label(), loc, radius))
print(f"Total local lights: {len(lights)}")
for i, (name, loc, rad) in enumerate(lights):
overlaps = 0
for j, (name2, loc2, rad2) in enumerate(lights):
if i != j:
dist = math.sqrt((loc.x - loc2.x) ** 2 + (loc.y - loc2.y) ** 2 + (loc.z - loc2.z) ** 2)
if dist < rad + rad2:
overlaps += 1
if overlaps > 3:
print(f"WARNING: {name} overlaps with {overlaps} other lights (radius: {rad:.0f})")
Šta radiš sa outputom: Lightovi sa >10 overlapa su problem. Smanji im attenuation radius (za interijer 300-500 je dovoljno umesto 1000+).
2.7 Bulk smanji light radius
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
count = 0
for actor in actors:
if isinstance(actor, unreal.SpotLight):
comp = actor.get_component_by_class(unreal.SpotLightComponent)
if comp and comp.attenuation_radius > 500:
comp.set_editor_property('attenuation_radius', 400.0)
count += 1
elif isinstance(actor, unreal.RectLight):
comp = actor.get_component_by_class(unreal.RectLightComponent)
if comp and comp.attenuation_radius > 2000:
comp.set_editor_property('attenuation_radius', 2000.0)
count += 1
print(f"Adjusted {count} lights")
Ako je pretamno: Povećaj intensity umesto radiusa — manji radius = manje overlapa = drastično jeftiniji rendering.
2.8 Isključi senke na nepotrebnim meshovima
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
no_shadow = [
"klime", "HIDEN_GRP", "NATPIS", "SUPER_SANITARY_WC",
"SUPER_SANITARY_SINKS", "SUPER_SANITARY_SHOWERS",
"SUPER_SANITARY_ACCESSORIES", "SUPER_OPENINGS_PLUMBING",
"SUPER_OPENINGS_HVAC", "SUPER_RAILINGS", "SUPER_DOORS",
"solar_coll", "Background", "playground",
"SUPER_CEILINGS_ARMSTRONG"
]
count = 0
for actor in actors:
name = actor.get_actor_label()
if name in no_shadow:
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp:
comp.set_editor_property('cast_shadow', False)
count += 1
print(f"Shadows OFF: {name}")
print(f"\nDisabled shadows on {count} actors")
Prilagodi listu no_shadow svom projektu. Generalno: sanitarija, plumbing, HVAC, sitni detalji koji se ne vide iz daljine.
2.9 Isključi senke na svim tiny meshovima (<100 tria)
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
count = 0
for actor in actors:
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp and comp.static_mesh and comp.cast_shadow:
tris = comp.static_mesh.get_num_triangles(0)
if tris < 100:
comp.set_editor_property('cast_shadow', False)
count += 1
print(f"Disabled shadows on {count} tiny meshes")
2.10 Sakrij nevidljive/nepotrebne aktore
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
for actor in actors:
name = actor.get_actor_label()
if name in ("HIDEN_GRP", "klime"): # prilagodi
actor.set_is_temporarily_hidden_in_editor(True)
actor.set_actor_hidden_in_game(True)
print(f"Hidden: {name}")
2.11 Pronađi translucent materijale (overdraw krivci)
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
for actor in actors:
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp and comp.static_mesh:
for i in range(comp.get_num_materials()):
mat = comp.get_material(i)
if mat:
try:
blend = mat.get_editor_property('blend_mode')
if blend != unreal.BlendMode.BLEND_OPAQUE:
print(f"{actor.get_actor_label()} | slot {i} | {mat.get_name()} | BlendMode: {blend}")
except:
pass
2.12 Tri budget po mesh tipu — top potrošači
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
mesh_stats = {}
for actor in actors:
if actor.is_hidden_ed() or actor.is_temporarily_hidden_in_editor():
continue
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp and comp.static_mesh:
name = comp.static_mesh.get_name()
tris = comp.static_mesh.get_num_triangles(0)
if name not in mesh_stats:
mesh_stats[name] = {"tris": tris, "count": 0}
mesh_stats[name]["count"] += 1
sorted_stats = sorted(mesh_stats.items(), key=lambda x: x[1]["tris"] * x[1]["count"], reverse=True)
print(f"{'Mesh':<35} {'Tris':>10} {'Count':>6} {'Total':>12}")
print("-" * 65)
for name, s in sorted_stats[:25]:
total = s['tris'] * s['count']
print(f"{name:<35} {s['tris']:>10} {s['count']:>6} {total:>12}")
2.13 Proveri mobility svih meshova
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
count = 0
for actor in actors:
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp and comp.static_mesh:
mob = comp.get_editor_property('mobility')
if mob != unreal.ComponentMobility.STATIC:
print(f"{actor.get_actor_label()} | {comp.static_mesh.get_name()} | Mobility: {mob}")
count += 1
print(f"\n{count} non-static meshes found")
Pravilo: Sve što se ne pomera runtime treba da bude Static. Sky sphere, kamere, fog, day/night sistemi ostaju Movable.
2.14 Draw call estimator
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
total_draws = 0
for actor in actors:
if actor.is_hidden_ed():
continue
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp and comp.static_mesh:
total_draws += comp.get_num_materials()
print(f"Estimated draw calls from static meshes: {total_draws}")
2.15 Skupi collision meshovi
import unreal
editor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = editor.get_all_level_actors()
for actor in actors:
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp and comp.static_mesh:
try:
col = comp.get_editor_property('collision_enabled')
tris = comp.static_mesh.get_num_triangles(0)
if col != unreal.CollisionEnabled.NO_COLLISION and tris > 50000:
print(f"{actor.get_actor_label()} | {tris} tris | Collision: {col}")
except:
pass
3. Konzolne komande za rendering optimizaciju
Shadow optimizacija
r.Shadow.MaxResolution 512
r.Shadow.MaxCSMResolution 1024
r.Shadow.CSM.MaxCascades 1
r.Shadow.RadiusThreshold 0.03
r.Shadow.Virtual.SMRT.RayCountLocal 4
r.Shadow.Virtual.SMRT.SamplesPerRayLocal 4
r.Shadow.Virtual.SMRT.RayCountDirectional 4
r.Shadow.Virtual.SMRT.SamplesPerRayDirectional 4
r.Shadow.Virtual.AllowScreenOverflowMessages 0
Lumen GI i refleksije
r.Lumen.ScreenProbeGather.DownsampleFactor 8
r.Lumen.ScreenProbeGather.TracingOctahedronResolution 16
r.Lumen.ScreenProbeGather.FullResolutionJitterWidth 0.5
r.Lumen.ScreenProbeGather.ScreenSpacePass.HalfRes 1
r.Lumen.Reflections.ScreenSpacePass.HalfRes 1
r.Lumen.Reflections.MaxRoughnessToTrace 0.4
r.Lumen.Reflections.ScreenSpacePass 1
r.LumenScene.Radiosity.HalfRes 1
DLSS
r.NGX.DLSS.Enable 1
r.ScreenPercentage 58 — Balanced mode
DLSS modovi: Quality = 67, Balanced = 58, Performance = 50, Ultra Performance = 33
Post processing
r.SSR.Quality 2
r.BloomQuality 4
r.MotionBlurQuality 0
r.AmbientOcclusionLevels 1
r.Tonemapper.Quality 0
Foliage
foliage.MinimumScreenSize 0.001
foliage.LODDistanceScale 0.8
Ostalo
r.MaxAnisotropy 16 — besplatno na RTX kartama
Scalability grupe (3 = High)
sg.ShadowQuality 3
sg.FoliageQuality 3
sg.EffectsQuality 3
sg.PostProcessQuality 3
4. Scalability i INI konfiguracija
Gde stavljati šta u DefaultEngine.ini
[ConsoleVariables]
r.Shadow.MaxResolution=512
r.Shadow.MaxCSMResolution=1024
r.Shadow.CSM.MaxCascades=1
r.Shadow.RadiusThreshold=0.03
r.Shadow.Virtual.SMRT.RayCountDirectional=4
r.Shadow.Virtual.AllowScreenOverflowMessages=0
r.Lumen.ScreenProbeGather.DownsampleFactor=8
r.Lumen.ScreenProbeGather.TracingOctahedronResolution=16
r.Lumen.ScreenProbeGather.FullResolutionJitterWidth=0.5
r.Lumen.ScreenProbeGather.ScreenSpacePass.HalfRes=1
r.Lumen.Reflections.ScreenSpacePass.HalfRes=1
r.Lumen.Reflections.MaxRoughnessToTrace=0.4
r.Lumen.Reflections.ScreenSpacePass=1
r.LumenScene.Radiosity.HalfRes=1
r.ScreenPercentage=58
r.NGX.DLSS.Enable=1
r.MaxAnisotropy=16
r.SSR.Quality=2
r.BloomQuality=4
r.MotionBlurQuality=0
r.AmbientOcclusionLevels=1
r.Tonemapper.Quality=0
foliage.MinimumScreenSize=0.001
foliage.LODDistanceScale=0.8
DefaultGameUserSettings.ini — scalability defaults
[ScalabilityGroups]
sg.ShadowQuality=3
sg.FoliageQuality=3
sg.EffectsQuality=3
sg.PostProcessQuality=3
VAŽNO — Config priority
Device Profiles (prioritet 0x05) mogu da pregaze [ConsoleVariables] (prioritet 0x04). Ako vidiš da se tvoji cvari resetuju, koristi [SystemSettings] umesto [ConsoleVariables] — ima isti prioritet ali se čita posle.
5. Packaging fix za kopirane projekte
Kad kopiraš UE5 projekat na drugi komp:
- Obriši
Intermediate/,Binaries/,DerivedDataCache/,Saved/StagedBuilds/,Saved/Cooked/ - Desni klik na .uproject → Switch Unreal Engine Version → izaberi svoj UE5
- Proveri
DefaultGame.ini—StagingDirectoryne sme da ima putanju sa starog kompa - Proveri
Saved/Config/WindowsEditor/Game.ini—PackageBuildTargetmora da bude postavljeno - Grep sve .ini fajlove za putanje sa starog kompa:
grep -rn "C:/Users/stariuser" Config/ Saved/Config/ - Ako je BP-only projekat,
BuildTargetu DefaultGame.ini treba da bude prazan ili obrisan - Ako dodaješ C++ Source, moraš prvo da bilduješ i Editor i Game target iz komandne linije pre nego što otvoris editor
6. Mega Audit skripta
Sačuvaj kao .py fajl i pokreni sa File → Execute Python Script. Daje kompletan pregled scene: overview, warnings, light overlap analizu, tri budget, i optimization score.
Fajl: audit.py (već ti je generisan)
7. Saveti i best practices
Nanite
- Uključi na sve meshove >5k tria koji nisu translucent/animated
- Ne radi na translucent materijalima (staklo, voda)
- Masked materijali (lišće) rade ali mogu imati artefakte — proveri vizuelno
Keep Triangle Percentna 50-75% za velike ravne površine (fasade, podovi) je besplatan kvalitetTrim Relative Errorna 0.02-0.05 za agresivniji LOD na daljini
Lightovi
- Stationary umesto Movable gde god možeš — dramatično jeftiniji
- Smanji attenuation radius na minimum koji vizuelno radi (300-500 za interijer)
- Isključi Cast Shadows na lightovima koji ne moraju da bacaju senke
- Manje overlapping lightova = eksponencijalno bolji performans
Senke
- Isključi Cast Shadow na sitnim meshovima (<100 tria)
- Isključi Cast Shadow na meshovima koji se ne vide direktno (plumbing, HVAC, sanitarija)
- Sakriveni aktori i dalje mogu da bacaju senke — proveri i isključi
Mobility
- Static za sve što se ne pomera (zgrade, nameštaj, teren, dekoracije)
- Stationary za lightove
- Movable samo za stvari koje se zaista pomeraju runtime (vrata, liftovi, sky)
Materijali
- Opaque > Masked > Translucent po performansu
- Translucent materijali uzrokuju overdraw — koristi samo kad moraš
- Smanji broj material slotova po meshu gde možeš (svaki slot = draw call)
Build vs Editor
- GPU timings su skoro identični u buildu i editoru
- Editor ima CPU overhead od UI, outliner, hot reload — to se vidi na Game thread
- Shipping config stripuje debug code — manje CPU overhead
- Realno očekuj 15-30% bolji FPS u packaged buildu (uglavnom CPU savings)
DLSS
- Balanced (58%) je dobar tradeoff za archviz Pixel Streaming
- Quality (67%) je bolji ako ti treba oštriji prikaz
- Anisotropic filtering 16x je besplatan na RTX kartama
Lumen noise
- Glavni uzroci: GI noise (ScreenProbeGather), reflection noise, shadow noise
- Povećaj TracingOctahedronResolution (16 umesto 8 default)
- Smanji FullResolutionJitterWidth (0.5 umesto 1.0)
r.Lumen.Reflections.MaxRoughnessToTrace 0.4— limitira Lumen refleksije samo na rough površine- Za shadow noise: povećaj SMRT ray/sample count
Workflow sa Claude-om
- Pokreni
stat gpui pošalji screenshot - Claude identifikuje bottleneck
- Pokreni odgovarajuću analitičku skriptu i pošalji output
- Claude napravi fix skriptu
- Pokreni fix, ponovo
stat gpu, ponovi
Za nove projekte, pokreni Mega Audit skriptu na početku da vidiš gde si i šta treba da se radi.
Rezultati naše optimizacije
| Metrika | Pre | Posle | Poboljšanje |
|---|---|---|---|
| GPU Total | 11.00ms | ~7.00ms | ~36% |
| RenderDeferredLighting | 2.74ms | 0.57ms | 79% |
| Lights | 2.39ms | 0.37ms | 85% |
| Basepass | 1.55ms | 0.97ms | 37% |
| Shadow Depths | 1.39ms | 1.22ms | 12% |
| VSM Non-Nanite Warning | DA | NE | Rešeno |
Šta smo uradili:
- Nanite uključen na 81 unique mesh
- Svi spotlightovi prebačeni na Stationary
- Light radius smanjen (1000 → 400 za spotlights, 5500 → 2000 za rect)
- Senke isključene na sanitariji, plumbingu, tiny meshovima
- ~6.7M tria sakriveno (klime + HIDEN_GRP)
- DLSS Balanced (58% render res)
- Shadow, Lumen, PostProcess konzolne komande