Poglavlje 20: Proceduralni materijali

Poglavlje 20: Proceduralni materijali


Uvod

Zamislite da pravite ogromnu kamenu tvrdjavu u svom nivou. Imate jednu teksturu kamena -- recimo 2048x2048 piksela -- i naneli ste je na zidove. Izgleda pristojno izbliza, ali cim se udaljite ili pogledate veci deo zida, pojavljuje se ocigledna ponavljajuca shema: isti kamen, ista pukotina, ista fleka, iznova i iznova u pravilnoj resetki. To je tiling artifact, jedan od najcescih problema u game art-u. Mozda ste ga vec primetili u prethodnim poglavljima kad smo govorili o teksturama (Poglavlje 05) i UV mapiranju (Poglavlje 04).

A sada zamislite drugaciji pristup: umesto da koristite sliku kamena, vi opisete kamen matematicki. Kazete materijalu: "Napravi Voronoi pattern za oblik kamenih blokova, dodaj Perlin noise za varijacije u boji, pojacaj ivice sa gradient funkcijom, i pospi sve sitnim detaljima koristeci fraktalni sum." Rezultat? Tekstura koja nikad ne ponavljaa isti pattern. Koja izgleda ostra na bilo kojoj udaljenosti. Koju mozete menjati pomocu par parametara -- velicina kamenja, dubina fuga, stepen istrosenosti -- bez ikakvog ponovnog crtanja u Photoshop-u.

To su proceduralni materijali -- i u ovom poglavlju cemo ih rastaviti na delove, razumeti matematiku iza njih, i nauciti kako da ih pravimo u Unreal Engine 5.

Napomena o terminologiji: Kao i u prethodnim poglavljima, koristimo srpski jezik sa latinicnim pismom, dok tehnicke termine ostavljamo na engleskom jeziku jer su tako ustaljeni u industriji. "Noise" ostaje "noise," "shader" ostaje "shader," a "UV" ostaje "UV."


20.1 Sta su proceduralni materijali?

20.1.1 Definicija

Proceduralni materijal (procedural material) je materijal ciji vizuelni sadrzaj nije definisan unapred napravljenim slikama (pre-authored textures), vec se generise algoritmom u realnom vremenu (ili tokom build procesa). Umesto piksela u .png ili .tga fajlu, proceduralni materijal koristi matematicke funkcije, noise algoritme i logicke operacije da stvori boje, normale, roughness i druge podatke.

Zamislite razliku izmedju dva pristupa:

Aspekt Tradicionalna tekstura Proceduralni materijal
Izvor podataka Slika (bitmap) Matematicka funkcija
Rezolucija Fiksna (npr. 2048x2048) Beskonacna (infinite resolution)
Memorija Zauzima VRAM Minimalna (samo shader instrukcije)
Tiling Vidljivi pattern-i ponavljanja Nema ponavljanja (ili kontrolisano)
Parametrizacija Ogranicena (tint, UV scale) Potpuna kontrola svih aspekata
Kreiranje Rucno u 2D programima Programiranje/node graf
Performanse Brz texture lookup Racunski zahtevniji (compute-heavy)

20.1.2 Zasto "beskonacna rezolucija"?

Kad koristite bitmap teksturu, svaki piksel sadrzi fiksnu informaciju o boji. Kad se kamera priblizi, taj piksel se rasteze -- postaje mutniji. Mogli biste koristiti mipmap-e i anisotropic filtering da ublazite problem (setite se Poglavlja 05), ali sushtinski, informacija je ogranicena.

Proceduralna funkcija, s druge strane, se izracunava za svaki piksel ekrana (ili za svaki fragment, da budemo precizniji). Kad se priblizite, funkcija se evaluira sa preciznijim koordinatama i proizvodi nove detalje. Nema rastezanja, nema mutnoce -- jer ne postoji fiksna slika koja se rasteze. Postoji samo formula koja za svaku tacku u prostoru kaze: "ovde je boja X, roughness Y, normal Z."

Ovo je posebno korisno za:

20.1.3 Parametrizacija: moc jednog slider-a

Mozda je najveca prednost proceduralnih materijala njihova parametrizacija (parameterization). Zamislite da imate materijal za ciglu (brick). Kod bitmap teksture, ako klijent kaze "zelim da cigle budu vece," morate da se vratite u Photoshop, promenite sliku, ponovo eksportujete, ponovo importujete u engine. Ako kaze "zelim da fuge budu tamnije," opet isti proces.

Kod proceduralnog brick materijala, imate parametre:

Brick Width:     0.25     [slider 0.05 - 1.0]
Brick Height:    0.08     [slider 0.02 - 0.5]
Mortar Width:    0.01     [slider 0.001 - 0.05]
Mortar Color:    (0.2, 0.2, 0.18)  [color picker]
Brick Color A:   (0.6, 0.15, 0.1)  [color picker]
Brick Color B:   (0.7, 0.2, 0.12)  [color picker]
Color Variation: 0.15     [slider 0.0 - 1.0]
Roughness Range: 0.6-0.85 [two sliders]
Wear Amount:     0.3      [slider 0.0 - 1.0]

Jedan slider -- i odmah vidite rezultat u viewportu. Nema cekanja na umetnika. Nema ponovnog eksportovanja. Ovo je neverovatno mocno za iteraciju dizajna i za kreiranje varijacija. Od istog proceduralnog materijala mozete napraviti stotine unikatnih zidova.

20.1.4 Kompromisi

Naravno, proceduralni materijali nisu magicno resenje za sve. Postoje realni kompromisi:

Performanse na GPU-u: Svaki piksel zahteva izracunavanje matematickih funkcija umesto prostog citanja iz memorije. Noise funkcije, posebno u vise oktava, mogu biti skupe. Na slabijim GPU-ovima ovo moze biti problem.

Kompleksnost kreiranja: Crtanje teksture u Photoshop-u ili Substance Painter-u je intuitivno -- vidite sta radite. Kreiranje proceduralnog materijala zahteva razumevanje matematike, noise funkcija i shader programiranja. Kriva ucenja je strmija.

Specificni detalji: Proceduralni materijali su odlicni za organske, ponovljive, i apstraktne pattern-e. Ali ako vam treba specifican natpis, logo, ili rucno nacrtan detalj -- bitmap tekstura je jedini put.

Kontrola umetnika: Neki umetnici preferiraju "painterly" pristup gde direktno crtaju svaki detalj. Proceduralni pristup daje manje direktne kontrole nad tacnim izgledom svake tacke.

U praksi, najbolji materijali u modernim igrama koriste hibridni pristup: proceduralnu osnovu za pattern-e i varijacije, kombinovanu sa bitmap teksturama za specificne detalje. Upravo ovo smo poceli da istrazujemo u Poglavlju 19, a sada idemo dublje.

20.1.5 Gde se proceduralni materijali koriste u UE5?

Proceduralne tehnike se u Unreal Engine 5 koriste na vise nacina:

  1. Direktno u Material Editor-u -- koristeci noise node-ove, matematicke operacije i pattern generatore koji se izvrsavaju u realnom vremenu na GPU-u. Ovo je fokus ovog poglavlja.

  2. Substance Designer/Substance 3D -- eksterni alat specijalizovan za proceduralno generisanje tekstura. Rezultat se moze koristiti kao baked tekstura ili kao Substance plugin u UE5.

  3. Houdini i drugi DCC alati -- za proceduralno generisanje geometrije i tekstura koje se zatim eksportuju u engine.

  4. Runtime proceduralno generisanje -- za igre koje generisu sadrzaj u realnom vremenu (procedural generation), poput Minecraft-a ili No Man's Sky.

  5. Nanite Tessellation (UE5.x) -- gde proceduralni displacement moze da radi na sub-pikselnom nivou zahvaljujuci Nanite tehnologiji.

Mi cemo se fokusirati na prvu tacku -- kreiranje proceduralnih materijala direktno u UE5 Material Editor-u, koristeci node-based sistem koji smo upoznali u Poglavlju 17.


20.2 Noise funkcije -- srce proceduralnih materijala

Ako su proceduralni materijali kuca, onda su noise funkcije (noise functions) njihov temelj, zidovi i krov. Gotovo svaki proceduralni efekat -- od oblaka do mramora, od rdje do pukotina -- koristi neku vrstu noise funkcije kao osnovu.

Ali sta je uopste "noise" u ovom kontekstu? Nije to statik sa TV ekrana (white noise) -- to bi bilo potpuno nasumicno i neupotrebljivo. Proceduralni noise je kontrolisana, glatka pseudo-nasumicnost koja proizovdi pattern-e slicne onima u prirodi.

Hajde da upoznamo glavne tipove.

20.2.1 Value Noise

Value noise je najjednostavniji oblik koherentnog noise-a. Algoritam radi ovako:

  1. Definisete pravilnu resetku (grid) u prostoru -- 2D, 3D ili koliko god dimenzija zelite.
  2. Na svakom cvoru resetke dodelite nasumicnu vrednost (obicno izmedju 0 i 1, ili -1 i 1).
  3. Za tacke izmedju cvorova izracunate vrednost interpolacijom susednih cvorova.

Kljucna rec je "interpolacija." Ako biste samo uzeli najblizi cvor, dobili biste blokove (kao Minecraft teksturu). Umesto toga, koristite glatku interpolacijski funkciju:

Linearna interpolacija (lerp): Najjednostavnija, ali proizvodi vidljive ivice na granicama celija.

lerp(a, b, t) = a + t * (b - a)

Hermite interpolacija (smoothstep): Glatka tranzicija, nema naglih preloma.

smoothstep(t) = t * t * (3 - 2 * t)

Quintic interpolacija (Perlin-ova poboljsana verzija): Jos glatcija, nema diskontinuiteta ni u prvom ni u drugom izvodu.

quintic(t) = t * t * t * (t * (t * 6 - 15) + 10)

Value noise je jednostavan za implementaciju, ali ima jedan znacajan problem: tendenciju da proizvodi "blokast" izgled (blocky appearance). Posto interpoliramo izmedju nasumicnih vrednosti na regularnoj resetki, rezultat cesto ima vidljivu strukturu resetke -- pattern koji se "poravnava" sa osama koordinatnog sistema. Ovo se ponekad naziva "grid-aligned artifacts."

U UE5, value noise mozete koristiti pomocu Noise node-a sa odgovarajucim podesavanjima, mada se u praksi cesce koriste bolji algoritmi.

Kada koristiti Value Noise?

20.2.2 Perlin Noise

Perlin noise je mozda najpoznatija noise funkcija u kompjuterskoj grafici. Razvio ga je Ken Perlin 1983. godine dok je radio na filmu Tron. Bio je toliko frustriran vestackim izgledom tadasnje kompjuterske grafike -- sve je izgledalo plasticno i "masinski" -- da je osmislio algoritam koji proizvodi prirodnije, organskije pattern-e. Za ovaj doprinost dobio je Academy Award za tehnicka dostignuca 1997. godine.

Kako radi Perlin Noise?

Kljucna razlika izmedju Perlin noise-a i value noise-a je u tome sta se cuva na cvorovima resetke:

Algoritam za 2D Perlin noise funkcionise ovako:

  1. Za datu tacku (x, y), odredite u kojoj celiji resetke se tacka nalazi.
  2. Pronadjite cetiri ugla te celije.
  3. Za svaki ugao, imate pre-definisan nasumicni gradijentni vektor (gradient vector).
  4. Izracunajte vektor od ugla do nase tacke (distance vector).
  5. Izracunajte dot product gradijentnog vektora i distance vektora za svaki ugao. Ovo daje "uticaj" svakog ugla na nasu tacku.
  6. Interpolirajte cetiri dot product-a koristeci smoothstep ili quintic funkciju.

Rezultat je glatciji i prirodniji od value noise-a jer:

Perlin Noise u UE5:

U Material Editor-u, Perlin noise je dostupan kroz Noise node sa Function podesenim na Perlin (ili varijante kao Gradient). Tipicni parametri:

Prakticni savet: Perlin noise sa jednom oktavom izgleda kao meke, zaobljene planine ili oblaci. Sa vise oktava, dobijate detaljnije, fraktalnije pattern-e. Pocnite sa jednom oktavom da razumete osnovu, pa dodajte kompleksnost.

20.2.3 Simplex Noise

Simplex noise je takodje razvio Ken Perlin, 2001. godine, kao poboljsanje svog originalnog algoritma. Zasto mu je bilo potrebno poboljsanje kad je vec dobio Oskar za originalni?

Zato sto Perlin noise ima nekoliko slabosti:

  1. Dimenzionalna skalabilnost: Perlin noise u 2D koristi cetiri ugla (kvadrat). U 3D koristi osam uglova (kocka). U 4D koristi sesnaest uglova (hiperkocka). Svaka nova dimenzija udvostrucuje broj tacaka za interpolaciju -- to postaje jako skupo.

  2. Vizuelni artifakti: Perlin noise u visim dimenzijama pokazuje blage "ose-poravnane" (axis-aligned) vizuelne artifakte koji mogu da remete organsku estetiku.

  3. Anizotropija: Perlin noise ne izgleda potpuno isto u svim pravcima -- postoje blage preferencije ka osama koordinatnog sistema.

Simplex noise resava sve ove probleme koristeci drugaciji geometrijski pristup:

Umesto hiperkocke koristi simplex -- najjednostavniji oblik koji ispunjava prostor u datoj dimenziji:

Ovo znaci da je simplex noise:

Vazna napomena o patentima: Ken Perlin je patentirao specificnu implementaciju 3D simplex noise-a (US Patent 6,867,776). Ovaj patent je istekao 2022. godine, sto znaci da je simplex noise sada slobodan za koriscenje. Pre isteka patenta, mnogi programeri su koristili alternativne implementacije kao sto je OpenSimplex noise (koji koristi drugaciju matematiku da izbegne patent). U vreme pisanja ove knjige, patent vise nije prepreka.

Simplex Noise u UE5:

UE5 nudi simplex noise kroz razlicite node varijante. Mozete ga koristiti direktno ili kroz custom HLSL node. Za vecinu prakticnih slucajeva, razlika izmedju Perlin-a i Simplex-a je suptilna na ekranu, ali simplex noise je efikasniji -- posebno ako koristite 3D ili 4D noise (npr. za animirane efekte gde je vreme cetvrta dimenzija).

20.2.4 Worley (Cellular) Noise

Worley noise (takodje poznat kao cellular noise ili Voronoi noise) funkcionise na potpuno drugacijem principu od Perlin-a i Simplex-a. Umesto interpolacije gradijenta, Worley noise se bazira na rastojanju do najblize tacke (feature point).

Algoritam:

  1. Rasporedite nasumicne tacke (feature points) u prostoru. U praksi se koristi resetka sa jednom ili vise nasumicnih tacaka po celiji.
  2. Za svaki piksel, pronadjite najblizu feature tacku i izracunajte rastojanje.
  3. To rastojanje postaje vrednost noise-a za taj piksel.

Rezultat je karakteristican celijski pattern -- izgleda kao:

Varijacije Worley noise-a:

Mozete koristiti razlicite metrike rastojanja za razlicite efekte:

Razlicite metrike rastojanja:

Worley Noise u UE5:

U Material Editor-u, Worley noise je dostupan kroz VoronoiNoise ili CellNoise node-ove, ili kroz Noise node sa odgovarajucim Function podesavanjem. Takodje mozete pristupiti i Voronoi Distance i Voronoi ID informacijama, sto vam daje:

20.2.5 FBM (Fractal Brownian Motion)

Do sada smo videli pojedinacne noise funkcije. Ali u prirodi, detalji postoje na svim skalama istovremeno. Pogledajte planinu: ima veliku formu (kilometer-ske krivine), srednje detalje (stene, litice), sitne detalje (kamenicicis, zemljiste) i mikroskopske detalje (zrnca peska). Jedna noise funkcija daje samo jednu skalu detalja.

Fractal Brownian Motion (FBM), takodje poznat kao fraktalni noise ili turbulence, resava ovaj problem tako sto kombinuje vise slojeva (oktava) iste noise funkcije na razlicitim frekvencijama i amplitudama.

Algoritam:

result = 0
amplitude = 1.0
frequency = base_frequency
total_amplitude = 0

for each octave (i = 0 to num_octaves - 1):
    result += amplitude * noise(position * frequency)
    total_amplitude += amplitude
    frequency *= lacunarity      // obicno 2.0
    amplitude *= persistence     // obicno 0.5

result /= total_amplitude  // normalizacija

Kljucni parametri:

Octaves (Oktave): Broj slojeva noise-a koji se kombinuju. Svaki sloj dodaje detalje na manjoj skali. Tipicne vrednosti su 4-8. Vise oktava = vise detalja, ali i vise racunanja.

Lacunarity (Lakunarnost): Koliko se frekvencija povecava sa svakom oktavom. Tipicna vrednost je 2.0, sto znaci da svaka sledeca oktava ima duplo vecu frekvenciju (duplo sitnije detalje).

Persistence (Perzistencija): Koliko svaka sledeca oktava doprinosi ukupnom rezultatu. Tipicna vrednost je 0.5, sto znaci da svaka sledeca oktava ima duplo manju amplitudu.

Vizualno objasnjenje FBM-a:

Zamislite da crtate planinu:

Oktava 1 (f=1, a=1.0):   /\      ___/\___
                         /  \    /        \
                        /    \__/          \___

Oktava 2 (f=2, a=0.5):   /\/\  /\/\  /\/\  /\/\
                         

Oktava 3 (f=4, a=0.25):  /\/\/\/\/\/\/\/\/\/\/\/\

Zbir:                    Realisticna planina sa
                         krupnim i sitnim detaljima

FBM u UE5:

U Material Editor-u, FBM je dostupan na dva nacina:

  1. Noise node sa Levels > 1 -- node automatski kombinuje vise oktava sa podesivim Lacunarity i Persistence parametrima.

  2. Rucno slaganje -- koristeci vise Noise node-ova sa rastucim Scale vrednostima i opadajucim amplitudama, sto vam daje potpunu kontrolu nad svakom oktavom (mozete koristiti razlicite noise tipove po oktavi).

20.2.6 Kako noise stvara prirodne pattern-e

Zasto matematicke funkcije proizvode nesto sto izgleda "prirodno"? Kljuc je u tome sto priroda sama funkcionise po fraktalnim principima. Oblaci, planine, reke, drvorede, pukotine na zemlji -- svi oni pokazuju samoslicnost (self-similarity) na razlicitim skalama. FBM tacno replicira ovu osobinu.

Evo kako noise funkcije kreiraju razlicite prirodne efekte:

Oblaci:

Teren:

Vatra i dim:

Voda:

Drvo:

Kamen/Stena:


20.3 Proceduralno generisanje tekstura

Sada kad razumemo noise funkcije, hajde da vidimo kako ih kombinujemo sa drugim matematickim operacijama da kreiramo konkretne pattern-e.

20.3.1 Generisanje pattern-a iz matematike

Mnogi korisni pattern-i se mogu izraziti cistom matematikom bez ikakvig noise-a:

Checkerboard (sahovska tabla):

float checker = frac(UV.x * TileCount) > 0.5 
              ^ frac(UV.y * TileCount) > 0.5;
// ^ je XOR operator
// Rezultat: 0 ili 1, naizmenicno

U UE5 Material Editor-u, ovo mozete postici koristeci Checker node ili kombinacijom Floor, Frac i Add node-ova.

Gradijenti:

// Horizontalni gradijent
float horizontal = UV.x;

// Radijalni gradijent
float radial = distance(UV, float2(0.5, 0.5)) * 2.0;

// Dijamantski gradijent
float diamond = abs(UV.x - 0.5) + abs(UV.y - 0.5);

// Kutni (angular) gradijent
float angular = atan2(UV.y - 0.5, UV.x - 0.5) / (2 * PI) + 0.5;

Stripes (linije):

// Horizontalne linije
float stripes = sin(UV.y * Frequency * 2 * PI) > 0 ? 1.0 : 0.0;

// Sa glatkim prelazom (ne ostrim)
float soft_stripes = sin(UV.y * Frequency * 2 * PI) * 0.5 + 0.5;

// Sa kontrolisanom sirinom
float adjustable = smoothstep(0.4, 0.5, frac(UV.y * Frequency));

Brick pattern (cigla pattern):

Ovo je klasican proceduralni pattern koji zasluzuje detaljniji pregled:

// Parametri
float BricksX = 4.0;    // broj cigala horizontalno
float BricksY = 8.0;    // broj cigala vertikalno
float MortarWidth = 0.05; // sirina fuge

// Korak 1: Skaliraj UV
float2 scaledUV = UV * float2(BricksX, BricksY);

// Korak 2: Odredi red (row)
float row = floor(scaledUV.y);

// Korak 3: Pomeri svaki drugi red za pola cigle
float offset = frac(row * 0.5); // 0 ili 0.5
scaledUV.x += offset;

// Korak 4: Odredi poziciju unutar cigle
float2 brickUV = frac(scaledUV);

// Korak 5: Napravi fuge (mortar)
float mortarX = smoothstep(0.0, MortarWidth, brickUV.x) 
              * smoothstep(0.0, MortarWidth, 1.0 - brickUV.x);
float mortarY = smoothstep(0.0, MortarWidth, brickUV.y) 
              * smoothstep(0.0, MortarWidth, 1.0 - brickUV.y);
float mortar = mortarX * mortarY; // 0 = fuga, 1 = cigla

// Korak 6: Dodaj ID cigle za nasumicne boje
float brickID = row * BricksX + floor(scaledUV.x);

U UE5, ovaj pattern mozete izgraditi koristeci:

Tiles (plocice):

// Heksagonalne plocice
float2 hexUV = UV * float2(1.0, sqrt(3.0));
float2 hexGrid = floor(hexUV);
float2 hexFrac = frac(hexUV);

// Alternativno: koristite Voronoi sa regularnim rasporedom
// za pravilne sesterokutne celije

20.3.2 Kombinovanje noise-a sa pattern-ima

Cisti matematicki pattern-i izgledaju vestacki -- previse pravilni, previse savrseni. Priroda nikad nije savrsena. Kombinovanjem noise-a sa pattern-ima dobijamo organski, uverljiv izgled.

Tehnike kombinovanja:

Noise kao varijacija boje:

Umesto jedne boje za cigle, koristite noise za blagu varijaciju:

float3 baseBrickColor = float3(0.6, 0.15, 0.1);
float colorNoise = PerlinNoise(UV * 10.0) * 0.15; // +/- 15%
float3 variedColor = baseBrickColor + colorNoise;

Noise kao modulacija roughness-a:

float baseRoughness = 0.65;
float roughnessNoise = PerlinNoise(UV * 20.0) * 0.2;
float finalRoughness = saturate(baseRoughness + roughnessNoise);

Noise za deformaciju ivica:

Umesto savrseno pravih fuga izmedju cigala, koristite noise da ih iskrivite:

// Dodaj noise na poziciju pre provere fuge
float edgeNoise = PerlinNoise(UV * 50.0) * 0.01;
float2 distortedUV = scaledUV + edgeNoise;
// Sad racunaj fuge sa distortedUV

Noise za aging/weathering:

float wearNoise = FBM(UV * 8.0, 4); // 4 oktave
float wearMask = smoothstep(WearThreshold - 0.1, WearThreshold + 0.1, wearNoise);
// wearMask = 1 gde je istrosenost, 0 gde je cisto
// Koristite za blend izmedju ciste cigle i prljavog kamena

20.3.3 Domain Warping

Domain warping je jedna od najvaznijih i najlepsih tehnika u proceduralnom generisanju. Koncept je elegantno jednostavan: koristite izlaz jedne noise funkcije da deformisete ulazne koordinate druge noise funkcije.

// Osnovni domain warping
float2 warpedUV = UV + float2(
    PerlinNoise(UV * warpScale),
    PerlinNoise(UV * warpScale + 100.0) // offset da X i Y budu nezavisni
) * warpAmount;

float result = PerlinNoise(warpedUV * detailScale);

Efekat je zapanjujuci -- dobijate organske, tecne, zakrivljene pattern-e koji podsecaju na:

Viseslojni domain warping:

Za jos dramaticnije efekte, mozete primeniti domain warping u vise koraka:

// Korak 1: Prvi sloj warping-a
float2 warp1 = float2(
    FBM(UV + float2(0.0, 0.0)),
    FBM(UV + float2(5.2, 1.3))
);

// Korak 2: Drugi sloj warping-a (warp the warp!)
float2 warp2 = float2(
    FBM(UV + 4.0 * warp1 + float2(1.7, 9.2)),
    FBM(UV + 4.0 * warp1 + float2(8.3, 2.8))
);

// Finalni rezultat
float result = FBM(UV + 4.0 * warp2);

Ovaj "warping warped noise" pristup je popularizan kroz rad Inigo Quilez-a, jednog od najuticajnijih ljudi u oblasti proceduralnog generisanja. Njegov clanak "Warping by FBM" (dostupan na iquilezles.org) je obavezno stivo za svakoga ko zeli da dublje razume ovu temu.

Domain Warping u UE5 Material Editor-u:

Implementacija je direktna:

  1. Kreirajte Noise node za warp (npr. Perlin, Scale = 2.0)
  2. Append dve noise vrednosti u float2 vektor (jedna za X offset, jedna za Y offset)
  3. Multiply sa faktorom jacine (WarpAmount, npr. 0.1 - 0.5)
  4. Add rezultat na originalne UV koordinate
  5. Koristite ove deformisane koordinate kao ulaz za drugi Noise node

Eksperimentisanje je kljucno -- male promene u parametrima mogu dramaticno promeniti izgled.

Animiran domain warping:

Ako na warp noise dodate Time node (pomnozen malim faktorom), dobijate hipnoticne animirane efekte -- tecni mramor, magicne energije, lava tokove. Ovo se koristi za:


20.4 Tiling i anti-tiling tehnike

Jedan od vecitih problema u game art-u je tiling -- ponavljanje teksture preko velike povrsine. Proceduralni materijali nude elegantna resenja za oba aspekta ovog problema: kako osigurati da noise ispravno tile-uje, i kako eliminisati vidljivo ponavljanje kod bitmap tekstura.

20.4.1 Zasto proceduralni noise tile-uje prirodno

Vecina noise implementacija je inherentno periodalna (periodic) -- noise funkcija se ponavlja nakon odredjenog perioda. Ovo je zapravo pozeljno, jer znaci da mozete primeniti noise na veliku povrsinu bez da brinete o seamovima.

Perlin noise, na primer, koristi hash funkciju koja mapira celobrojne koordinate resetke na nasumicne gradijente. Posto hash funkcija daje iste rezultate za iste ulaze, noise na poziciji (0, 0) je isti kao noise na poziciji (256, 0) ako je period 256. Ovo znaci da se noise prirodno tile-uje na tim granicama.

Za slucajeve gde vam treba eksplicitna kontrola nad periodom tiling-a, mozete koristiti tileable noise varijante:

// Tileable Perlin noise - koristi modulo operaciju na grid koordinatama
float tileableNoise(float2 pos, float2 period) {
    // Grid koordinate se "wrappaju" na period
    float2 gridPos = fmod(floor(pos), period);
    // Ostatak algoritma je isti kao obican Perlin noise
    // ali svaki grid lookup koristi wrapped koordinate
}

U UE5, mozete koristiti frac() i modulo operacije na noise koordinatama da kontrolisete period tiling-a. Noise node takodje ima opciju za Tiling koja eksplicitno kontrolise ponavljanje.

20.4.2 Anti-tiling za bitmap teksture

Dok se proceduralni noise tile-uje "beskonacno" bez vidljivih seam-ova, bitmap teksture imaju ocigledne probleme sa tiling-om. Cak i kad tekstura ima savrsene seamove (leva ivica se spaja sa desnom, gornja sa donjom), ponavljanje istog pattern-a je ocigledno ljudskom oku.

Postoji nekoliko tehnika za borbu protiv ovog problema:

Random offset, rotation, scale per tile:

Ideja: umesto da uvek prikaze istu teksturu na isti nacin, svaka "instanca" dobija nasumicnu transformaciju.

// Za svaku celiju u tiling gridu:
float2 cellID = floor(UV * TileCount);
float randomSeed = hash(cellID); // Nasumican broj baziran na celiji

// Nasumicna rotacija
float angle = randomSeed * 2.0 * PI;
float2 rotatedUV = rotate(frac(UV * TileCount) - 0.5, angle) + 0.5;

// Nasumicni offset
float2 offset = float2(hash(cellID + 1.0), hash(cellID + 2.0));
float2 offsetUV = frac(UV * TileCount + offset);

// Nasumicni scale (blaga varijacija)
float scale = 0.9 + hash(cellID + 3.0) * 0.2; // 0.9 do 1.1
float2 scaledUV = (frac(UV * TileCount) - 0.5) * scale + 0.5;

Problem sa ovim pristupom: na granicama celija se pojavljuju vidljivi seamovi jer se susedne celije rotiraju/skaliraju razlicito.

20.4.3 Stochastic Sampling / Hex Tiling

Stochastic sampling (stohasticko uzorkovanje) je elegantnija tehnika koja resava problem seamova koristeci preklapajuce trougaone ili heksagonalne regione umesto pravougaonih celija.

Hex tiling radi ovako:

  1. Podelite povrsinu na heksagonalnu resetku
  2. Svaki heksagon dobija nasumicnu transformaciju (rotacija, offset) teksture
  3. Na granicama heksagona, blendujte izmedju susednih transformacija koristeci glatku tezinsku funkciju
  4. Rezultat: bez vidljivih seamova, bez ponavljajucih pattern-a
// Pojednostavljen koncept hex tiling-a:
// 1. Odredite u kom heksagonu se piksel nalazi
// 2. Pronadjite 3 najbliza centra heksagona
// 3. Za svaki od 3 centra, uzorkujte teksturu sa nasumicnom transformacijom
// 4. Blendujte 3 uzorka koristeci barycentric weights
float3 hexTile(Texture2D tex, float2 uv, float scale) {
    // ... hex grid math ...
    float3 sample1 = tex.Sample(uv_transformed_1) * weight1;
    float3 sample2 = tex.Sample(uv_transformed_2) * weight2;
    float3 sample3 = tex.Sample(uv_transformed_3) * weight3;
    return sample1 + sample2 + sample3;
}

Prednosti hex tiling-a:

Mane:

Hex tiling u UE5:

UE5 ne dolazi sa ugradjenim hex tiling node-om, ali ga mozete implementirati:

  1. Custom node sa HLSL kodom za hex tiling
  2. Material Function koja enkapsulira logiku
  3. Razlicite implementacije dostupne na UE Marketplace-u i u community resursima
  4. Epic-ov Substrate (Strata) sistem u novijim verzijama UE5 polako integrise napredne anti-tiling opcije

20.4.4 Bombing / Scattering tehnike

Texture bombing (ili scattering) je tehnika gde se mala tekstura (element/dekal) nasumicno rasipa po povrsini, kao da "bombardujete" povrsinu tim elementom.

Tipicna upotreba:

Kako radi:

// Texture Bombing - konceptualni pseudokod
float3 bomb(Texture2D element, float2 uv, float density) {
    float3 result = float3(0, 0, 0);
    
    // Za svaku celiju u okolini
    for(int x = -1; x <= 1; x++) {
        for(int y = -1; y <= 1; y++) {
            float2 cellID = floor(uv * density) + float2(x, y);
            
            // Nasumicna pozicija elementa unutar celije
            float2 elementPos = hash2D(cellID) + cellID;
            elementPos /= density;
            
            // Nasumicna rotacija i scale
            float angle = hash(cellID + 100.0) * 2.0 * PI;
            float scale = 0.5 + hash(cellID + 200.0) * 0.5;
            
            // Transformisi UV relativno na element
            float2 localUV = rotate(uv - elementPos, angle) / scale + 0.5;
            
            // Ako smo unutar bounds-a elementa
            if(all(localUV > 0.0 && localUV < 1.0)) {
                float4 elementSample = element.Sample(localUV);
                // Alpha blend
                result = lerp(result, elementSample.rgb, elementSample.a);
            }
        }
    }
    return result;
}

Praktichni saveti za bombing u UE5:

  1. Koristite atlase -- stavite vise varijacija elementa na jednu teksturu i nasumicno birajte sub-regione. Ovo smanjuje monotoniju.

  2. Pazite na overdraw -- svaki element zahteva texture sample. Velika gustina moze biti skupa.

  3. Height-based blending -- umesto prostog alpha blenda, koristite height informaciju za realisiticniji prelaz (npr. kamenicici "sede" na zemlji umesto da plutaju iznad).

  4. Combine sa proceduralnim noise-om -- koristite noise da modulirate gustinu bombing-a po povrsini (vise kamenicica u udubljenjima, manje na glatkim povrsinama).


20.5 World-Aligned teksture

Do sada smo koristili UV koordinate za mapiranje tekstura na povrsine. Kao sto smo naucili u Poglavlju 04, UV-ovi su 2D koordinate "odmotane" sa 3D povrsine objekta, i svaki objekt ima svoj UV prostor.

Ali sta ako zelite da tekstura izgleda kontinualno preko vise objekata? Ili da nemate UV seamove uopste? Ili da dodate detalje na objekte koji nemaju UV-ove (poput BSP geometrije ili runtime generisanih mesh-eva)?

Tu na scenu stupaju world-aligned textures (teksture poravnane sa svetom) -- tehnika gde koristite poziciju u svetu (World Position) umesto UV koordinata za texture mapping.

20.5.1 Osnovna ideja

Umesto:

color = texture.Sample(mesh.UV)

Koristite:

color = texture.Sample(WorldPosition.xy / TextureScale)

WorldPosition je apsolutna pozicija piksela u svetskom prostoru (world space), izrazena u UE5 jedinicama (centimetrima). Deljenjem sa TextureScale kontrolisete koliko je "krupna" tekstura u svetu.

// World-aligned texture na XY ravni
float3 worldPos = GetWorldPosition(); // u centimetrima
float2 worldUV = worldPos.xy / TextureScale; // npr. TextureScale = 100.0 za 1m
float3 color = MyTexture.Sample(worldUV);

20.5.2 Prednosti

Eliminacija UV seamova:

Posto se koordinate baziraju na poziciji u svetu a ne na UV-ovima objekta, ne postoje UV seamovi. Ovo je posebno korisno za:

Konzistentan scale:

Svaki objekat u sceni dobija teksturu istog scale-a. Cigla na jednom zidu ce biti iste velicine kao cigla na drugom, bez obzira na UV layout objekata.

Nezavisnost od UV-a:

Objekti ne moraju imati UV koordinate uopste. Ovo je velika prednost za:

20.5.3 Triplanar Mapping

Osnovni world-aligned pristup (projekcija sa jedne ose) ima ocigledian problem: sta se desava sa povrsinama koje su okrenute u razlicitim pravcima?

Ako koristite samo XY projekciju, horizontalni pod ce izgledati odlicno, ali vertikalni zid ce imati "razvucenu" teksturu jer se Y koordinata ne menja duz zida.

Triplanar mapping resava ovo koristeci tri projekcije -- po jednu za svaku osu -- i blendujeci ih na osnovu normala povrsine:

float3 TriplanarMapping(Texture2D tex, float3 worldPos, float3 worldNormal, 
                         float scale, float sharpness) {
    // Izracunaj tezine na osnovu normala
    float3 weights = abs(worldNormal);
    
    // Pojacaj kontrast tezina (sharpness)
    weights = pow(weights, sharpness);
    
    // Normalizuj tezine da zbir bude 1
    weights /= (weights.x + weights.y + weights.z);
    
    // Uzorkuj teksturu sa tri projekcije
    float3 xProj = tex.Sample(worldPos.yz / scale); // povrsine okrenute ka X
    float3 yProj = tex.Sample(worldPos.xz / scale); // povrsine okrenute ka Y (pod/plafon)
    float3 zProj = tex.Sample(worldPos.xy / scale); // povrsine okrenute ka Z
    
    // Blenduj
    return xProj * weights.x + yProj * weights.y + zProj * weights.z;
}

Kako to izgleda:

Sharpness parametar:

Kontrolise koliko je ostar prelaz izmedju projekcija:

Triplanar Mapping u UE5:

UE5 Material Editor nudi WorldAlignedTexture node koji implementira triplanar mapping. Takodje mozete koristiti:

Koraci za postavljanje u Material Editor-u:

  1. Dodajte WorldPosition node
  2. Podelite sa Constant (scale faktor, npr. 100 za 1m repeat)
  3. Razdvojite na komponente koristeci ComponentMask ili BreakOutFloat3Components
  4. Napravite tri TextureSample node-a sa razlicitim UV parovima (XY, XZ, YZ)
  5. Dodajte VertexNormalWS node
  6. Koristite Abs i Power za tezine
  7. Normalizujte tezine (podelite sa zbirom)
  8. Multiply svaki sample sa odgovarajucom tezinom
  9. Add sve tri projekcije

Ili, jednostavnije: koristite gotov WorldAlignedTexture ili TriplanarMapping Material Function.

20.5.4 Performanse

World-aligned textures imaju performansne implikacije koje treba razumeti:

Triplanar mapping = 3x texture samples:

Umesto jednog citanja teksture, imate tri. Za materijal sa albedo, normal, roughness i AO mapama, to je 12 texture sample-ova umesto 4. Na starijim GPU-ovima i mobile uredjajima, ovo moze biti znacajno.

Optimizacije:

  1. Redukovani triplanar: Za povrsine koje znate da su uglavnom horizontalne (pod), koristite samo 2 projekcije umesto 3.

  2. Lazy evaluation: Prvo izracunajte tezine, pa uzorkujte samo projekcije cija je tezina iznad nekog threshold-a. Ovo moze da eliminise 1-2 texture sample-a na vecini piksela.

// Optimizovani triplanar - preskoci projekcije sa malom tezinom
float3 result = float3(0, 0, 0);
if(weights.x > 0.01) result += tex.Sample(worldPos.yz / scale) * weights.x;
if(weights.y > 0.01) result += tex.Sample(worldPos.xz / scale) * weights.y;
if(weights.z > 0.01) result += tex.Sample(worldPos.xy / scale) * weights.z;

Napomena: Dynamicko grananje (branching) na GPU moze biti samo po sebi skupo -- efikasnost zavisi od koherentnosti piksela. Ako su susedni pikseli u istom "granu" (sto je obicno slucaj za triplanar jer se normala sporo menja), branching je efikasan.

  1. Nizi mip level: Za world-aligned detalje na udaljenosti, koristite nize rezolucije tekstura.

  2. Proceduralnu zamenu: Za jednostavne pattern-e (cigle, beton), razmotrite potpuno proceduralni pristup umesto triplanar texture sampling-a. Proceduralne noise funkcije su cesto brze od tri texture sample-a.

Problemi sa World-Aligned teksturama na pokretnim objektima:

Ako objekat koristi world-aligned teksture i pomera se, tekstura ce "kliziti" po povrsini objekta jer su koordinate vezane za svet, ne za objekat. Ovo je pozeljno za staticne objekte (zidovi, podovi), ali ne za pokretne (vozila, karaktere, predmete).

Resenje: koristite world-aligned samo za staticne objekte, ili prebacite na Object Space poziciju za pokretne objekte:

// Za pokretne objekte - koristite Object Position umesto World Position
float3 objectPos = TransformWorldToObject(WorldPosition);
float2 objectUV = objectPos.xy / TextureScale;

UE5 nudi ObjectPosition node koji daje poziciju relativno na pivot objekta, sto resava problem klizenja.


20.6 Prakticni primeri

Sada cemo primeniti sve sto smo naucili na konkretne primere. Svaki primer ukljucuje konceptualni pregled, matematicku osnovu i uputstva za implementaciju u UE5 Material Editor-u.

20.6.1 Proceduralni zid od cigala

Ovaj primer je klasican "Hello World" proceduralnih materijala. Kombinuje matematicki pattern sa noise-om za uverljiv rezultat.

Cilj: Napraviti materijal za zid od cigala koji:

Korak 1: Brick Pattern

Osnova je matematicki pattern koji smo videli ranije. U Material Editor-u:

  1. Dodajte TextureCoordinate node (UV). Ako zelite world-aligned varijaciju, koristite WorldPosition podeljen sa scale faktorom.

  2. Multiply sa brojem cigala po osi:

    • U axis: BrickCountX (npr. 4)
    • V axis: BrickCountY (npr. 8)
  3. Za svaki drugi red, dodajte offset od 0.5:

    • Uzmite V komponentu, Floor je, Multiply sa 0.5, Frac -- dobijate 0 ili 0.5
    • Add na U komponentu
  4. Koristite Frac na oba axis-a da dobijete poziciju unutar svake cigle (0-1 opseg).

  5. Za fuge koristite Smoothstep: blizu ivica (0 i 1), vrednost pada ka 0 (fuga). U sredini cigle, vrednost je 1.

Korak 2: Brick ID za varijacije

Svaka cigla treba da ima jedinstvenu identifikaciju za nasumicne varijacije.

  1. Floor UV koordinate pre Frac operacije -- ovo daje celobrojne koordinate celije.
  2. Ove koordinate prosledite kroz hash/noise funkciju sa veoma niskom frekvencijom -- dobijate jedinstven "ID" za svaku ciglu.
  3. Taj ID koristite kao seed za:
    • Nasumicnu boju (lerp izmedju dva brick color-a)
    • Nasumicnu roughness varijaciju
    • Nasumicni rotation za detalj teksturu

Korak 3: Color Variation

// Pseudokod za Material Editor logiku:
BrickColor = lerp(BrickColorA, BrickColorB, hash(BrickID))

// Dodatna fine varijacija koristeci noise
ColorNoise = PerlinNoise(UV * DetailScale) * ColorVariation
FinalBrickColor = BrickColor + ColorNoise

// Mortar boja
MortarColor = DarkGray

// Finalna boja
OutputColor = lerp(MortarColor, FinalBrickColor, BrickMask)

Korak 4: Normal Map

Za normalne, koristite gradijent brick mask-a kao height map i konvertujte u normale:

  1. Brick mask (1 za ciglu, 0 za fugu) vec ima glatke prelaze zahvaljujuci Smoothstep-u.
  2. Koristite DDX/DDY node-ove (ili NormalFromHeightMap ako je dostupan) da izracunate normale iz ovog height map-a.
  3. Ili: eksplicitno izracunajte gradijent pomeranjem UV za mali delta i racunajuci razliku.
// Normala iz height mape (proceduralna):
float h = BrickHeight(UV);
float h_dx = BrickHeight(UV + float2(delta, 0));
float h_dy = BrickHeight(UV + float2(0, delta));
float3 normal = normalize(float3(h - h_dx, h - h_dy, delta));
  1. Dodajte Perlin noise na normale za sitnu teksturu povrsine cigle.

Korak 5: Roughness

BaseRoughness = lerp(MortarRoughness, BrickRoughness, BrickMask)
RoughnessNoise = PerlinNoise(UV * 30.0) * 0.1
FinalRoughness = BaseRoughness + RoughnessNoise

Korak 6: Wear / Aging

  1. Generisete FBM noise sa 3-4 oktave na srednjoj frekvenciji.
  2. Koristite Smoothstep sa WearAmount parametrom kao threshold.
  3. Gde noise prelazi threshold, blendujte ka "istrosenoj" verziji:
    • Svetlija boja (isprano)
    • Veca roughness
    • Ostecenost ivica (umanjite brick mask blizu fuga)

Rezultat: Potpuno proceduralni brick materijal sa 10+ podesljivih parametara, beskonacnom rezolucijom, bez tiling-a, koji renderuje identican izgled na objektima bilo koje velicine.

20.6.2 Proceduralno drvo

Drvena tekstura je odlican primer koristenja cilindricnog noise-a -- noise koji se evaluira u cilindricnom koordinatnom sistemu.

Zasto cilindrican?

Godovi drveta su koncentricni krugovi kad gledate presek. U 3D, to su cilindricne ljuske. Ako secete drvo pod uglom, dobijate eliptican pattern. Ako secete paralelno sa stablom, dobijate paralelne linije. Sve ovo prirodno proizilazi iz cilindricnog koordinatnog sistema.

Algoritam:

// Korak 1: Cilindrican pattern za godove
// Koristimo rastojanje od Y ose (ako je Y os stabla)
float radius = sqrt(worldPos.x * worldPos.x + worldPos.z * worldPos.z);

// Korak 2: Skaliranje za gustinu godova
float rings = radius * RingDensity; // npr. RingDensity = 20

// Korak 3: Sinusoidna funkcija za svetle/tamne godove
float ringPattern = sin(rings * 2 * PI) * 0.5 + 0.5;

// Korak 4: Turbulence za nepravilnost
float turbulence = FBM(worldPos * NoiseScale, 3) * TurbulenceAmount;
float distortedRings = sin((rings + turbulence) * 2 * PI) * 0.5 + 0.5;

// Korak 5: Boja
float3 lightWood = float3(0.8, 0.6, 0.35);
float3 darkWood = float3(0.4, 0.25, 0.1);
float3 woodColor = lerp(darkWood, lightWood, distortedRings);

// Korak 6: Vlakna (grain) - elongiran noise u pravcu stabla
float grain = PerlinNoise(float3(worldPos.x * 100, worldPos.y * 2, worldPos.z * 100));
woodColor += grain * 0.05;

Implementacija u UE5:

  1. WorldPosition node -- daje poziciju u svetu
  2. ComponentMask -- izdvojite X i Z komponente
  3. VectorLength ili Distance -- izracunajte rastojanje od ose (radius)
  4. Multiply sa gustinom godova
  5. Noise node sa niskom frekvencijom -- za turbulence
  6. Add turbulence na radius pre sine
  7. Sine -- za pattern godova
  8. Lerp izmedju svetle i tamne boje drveta

Varijacije:

Kontrola secenja:

Promena orijentacije UV/world koordinata simulira razlicite uglove secenja:

20.6.3 Proceduralni mramor

Mramor je mozda najpoznatija primena domain warping-a. Karakteristicne "vene" (veins) u mramoru nastaju geoloski -- minerali koji se deformisu pod pritiskom. Mi to simuliramo koristeci noise koji deformise noise.

Algoritam:

// Korak 1: Osnovni gradijent (pravac "vena")
float basePattern = UV.x; // ili UV.y, ili dijagonala

// Korak 2: Domain warping za deformaciju vena
float warp1 = FBM(UV * WarpScale1, 4) * WarpAmount1;
float warp2 = FBM(UV * WarpScale2 + 50.0, 4) * WarpAmount2;

// Korak 3: Deformisan pattern
float distorted = basePattern + warp1;

// Korak 4: Sine za vene
float veins = sin(distorted * VeinFrequency * 2 * PI);

// Korak 5: Pojacaj vene (ostriji prelaz)
veins = pow(abs(veins), VeinSharpness); // VeinSharpness = 0.3 za siroke, 3.0 za tanke

// Korak 6: Boja
float3 baseMarble = float3(0.95, 0.93, 0.9); // Beli mramor
float3 veinColor = float3(0.2, 0.2, 0.25);    // Tamne vene
float3 marbleColor = lerp(veinColor, baseMarble, veins);

// Korak 7: Drugi sloj vena (finiji)
float fineVeins = sin((distorted + warp2) * VeinFrequency * 4.0 * PI);
fineVeins = pow(abs(fineVeins), VeinSharpness * 2.0) * 0.3;
marbleColor = lerp(marbleColor, veinColor * 0.8, (1.0 - fineVeins) * 0.15);

Implementacija u UE5:

  1. TextureCoordinate ili WorldPosition za pocetne koordinate
  2. Dva Noise node-a sa razlicitim Scale vrednostima za dva nivoa warping-a
  3. Multiply noise sa WarpAmount
  4. Add na originalnu koordinatu (domain warping)
  5. Sine node na deformisanu koordinatu
  6. Abs i Power za kontrolu ostrucine vena
  7. Lerp izmedju bele i tamne boje

Varijacije mramora:

Pro tip: Za fotorealistican mramor, dodajte subsurface scattering (SSS) efekte -- mramor je translucente i svetlost prodire u njega. U UE5, koristite Subsurface shading model i dodajte blagu SSS boju (topla bela ili bledo roza).

20.6.4 Weathering efekti (istrosenost)

Weathering -- vizuelno starenje i habanje materijala -- je kljucan za fotorealizam. Novi, cisti materijali izgledaju vestacki. Pravi svet je pun prasine, rdje, oguljene boje, zakrzljanih ivica i fleka od vode.

Komponente procedural weathering-a:

Edge Wear (habanje ivica):

Ivice objekata se prirodno vise habaju -- tu se ljudi najcesce dodiruju, tu kiša najvise tuce, tu se boja najlakse guli. Za detekciju ivica u shader-u koristimo curvature (zakrivljenost povrsine).

// Curvature iz normala
// DDX/DDY daju promenu normale po ekranskom prostoru
float3 normalDDX = ddx(WorldNormal);
float3 normalDDY = ddy(WorldNormal);
float curvature = length(normalDDX) + length(normalDDY);

// Visoka curvature = ivica
float edgeWear = smoothstep(CurvatureThreshold, CurvatureThreshold + 0.1, curvature);

U UE5, mozete koristiti:

Dirt Accumulation (nakupljanje prljavstine):

Prasina i prljavstina se skupljaju u udubljenjima i na horizontalnim povrsinama. Koristimo:

  1. Ambient Occlusion za udubljenja
  2. World Normal Y komponentu za horizontalnost (prasina pada na gore)
  3. Noise za organske varijacije
// Dirt maska
float upFacing = saturate(WorldNormal.z); // 1 = potpuno horizontalno (gore)
float dirtBase = upFacing * AO; // Horizontalne povrsine + udubljenja
float dirtNoise = FBM(WorldPos * 0.1, 3) * DirtVariation;
float dirtMask = smoothstep(0.3, 0.7, dirtBase + dirtNoise);

// Primena dirt-a
float3 finalColor = lerp(BaseColor, DirtColor, dirtMask * DirtAmount);
float finalRoughness = lerp(BaseRoughness, DirtRoughness, dirtMask * DirtAmount);

Rust (Rdja):

Rdja se tipicno pojavljuje na metalnim povrsinama, narocito gde je boja ostecena i gde se voda zadrzava.

// Rdja se kombinuje iz vise faktora:
float rustBase = 0.0;

// Faktor 1: Edge wear (boja se guli na ivicama)
rustBase += edgeWear * 0.4;

// Faktor 2: Udubljenja gde se voda skuplja
rustBase += (1.0 - AO) * 0.3;

// Faktor 3: Donja strana (voda curi nadole)
rustBase += saturate(-WorldNormal.z) * 0.2;

// Faktor 4: Organska varijacija
float rustNoise = FBM(WorldPos * RustScale, 5);
float rustMask = smoothstep(RustThreshold - 0.15, RustThreshold + 0.05, 
                            rustBase + rustNoise * 0.5);

// Rdja menja sve kanale:
float3 rustColor = float3(0.45, 0.18, 0.07); // Tamno narandzasta
float rustRoughness = 0.85;
float rustMetallic = 0.0; // Rdja nije metal (oksid)

Moisture / Water Stains (fleke od vode):

// Fleke od vode na zidovima
float moistureGradient = 1.0 - saturate(WorldPos.z / MaxMoistureHeight);
float moistureNoise = FBM(WorldPos.xz * 0.05, 4);
float moistureMask = smoothstep(0.3, 0.5, moistureGradient * moistureNoise);

// Vlaga zatamnjuje boju i smanjuje roughness
finalColor *= lerp(1.0, 0.6, moistureMask);
finalRoughness = lerp(finalRoughness, 0.3, moistureMask * 0.5);

Kombinovanje svih weathering efekata:

U praksi, svi ovi efekti se slazu (layer-uju) jedan na drugi:

  1. Base material -- cist, neistrosten materijal
  2. Edge wear -- osvetljava/menja ivice
  3. Dirt -- zatamnjuje udubljenja i gornje povrsine
  4. Rust/Oxidation -- menja metal
  5. Moisture -- zatamnjuje donje delove
  6. Moss/Organic growth -- zelenkasto na vlaznim, zaklonjenm mestima

Svaki efekat ima svoj mask generisan iz kombinacije geometrijskih faktora (curvature, normal direction, AO, height) i proceduralnog noise-a.

Master Weathering parametar:

Elegantno resenje je imati jedan "Age" slider (0-1) koji kontrolise sve efekte:

Ovo se postize mapiranjem Age parametra na threshold-ove svakog efekta:

float edgeWearAmount = smoothstep(0.0, 0.3, Age);
float dirtAmount = smoothstep(0.1, 0.5, Age);
float rustAmount = smoothstep(0.3, 0.8, Age);
float mossAmount = smoothstep(0.5, 1.0, Age);

20.7 Napredne teme i saveti za optimizaciju

20.7.1 Performansna razmatranja

Proceduralni materijali mogu biti zahtevni za GPU. Evo saveta za odrzavanje performansi:

Merenje troskova:

UE5 Material Editor prikazuje Instruction Count u statusnoj traci. Ovo je gruba aproksimacija troska:

Takodje koristite GPU Profiler (Ctrl+Shift+, ili profilegpu komanda) za precizno merenje vremena renderovanja.

Optimizacijske strategije:

  1. Smanjite broj oktava FBM-a. Razlika izmedju 6 i 4 oktave je cesto nevidljiva na ekranu, ali ustedite 33% noise racunanja.

  2. Bake-ujte proceduralne rezultate u teksture kad je moguce. Ako se materijal ne menja u runtime-u, mozete render-to-texture proceduralni noise i koristiti rezultat kao obicnu teksturu. Ovo vam daje proceduralni kvalitet sa bitmap performansama.

  3. LOD za materijale: Koristite Quality Switch ili custom LOD logiku da uprosti materijal na udaljenosti. Blizu kamere: puni proceduralni materijal sa 6 oktava. Daleko: uproscena verzija sa 2 oktave ili cak baked tekstura.

  4. Izbegavajte nepotrebne racunice. Ako noise koristite samo za boju a ne za normale, ne racunajte ga dva puta. Sacuvajte rezultat u Local Variable i reuse-ujte.

  5. Koristite LUT teksture za skupe matematicke funkcije (sin, cos, pow) ako ih evaluirate na fiksnom opsegu. 256x1 texture lookup je brzi od trigonometrijskih operacija.

  6. Shared Material Functions -- enkapsulairajte proceduralne elemente u Material Functions koje mozete ponovo koristiti bez dupliranja koda.

20.7.2 Debugging proceduralnih materijala

Debugging proceduralnih materijala moze biti frustrirajuci -- ne vidite "kood" koji se izvrsava, samo krajnji rezultat. Evo tehnika:

  1. Preview svaki node -- desni klik na node u Material Editor-u i izaberite "Start Previewing Node." Ovo prikazuje izlaz tog node-a na previewu materijala. Koristite ovo da korak po korak pratite sta se desava.

  2. Vizualizujte medjurezultate -- privremeno spojite medjurezultat (npr. noise mask) na Emissive output da vidite sta noise zapravo proizvodi.

  3. Remap na 0-1 opseg -- mnogi noise-ovi izlaze u opsegu -1 do 1. Dodajte 0.5 i pomnozite sa 0.5 da remap-ujete na vidljiv opseg (ili koristite LinearToGamma za bolji vizuelni kontrast).

  4. Koristite boje za debug -- maoirajte razlicite kanale na R, G, B da istovremeno vidite tri razlicita medjurezultata.

  5. Staticki parametri -- koristite Static Switch node-ove da napravite "debug mode" koji mozete ukljuciti/iskljuciti bez recompile-a.

20.7.3 Hibridni pristup: Proceduralno + Bitmap

U praksi, najupecatljiviji materijali koriste hibridni pristup:

Tipican workflow:

  1. Kreirajte proceduralnu osnovu u UE5 Material Editor-u (pattern, noise, weathering)
  2. Dodajte detail teksture iz Substance-a ili Photoshop-a (micro-normal, detail albedo)
  3. Koristite proceduralne maske da kontrolisete gde se detail teksture primenjuju
  4. Parametrizujte sve za laku iteraciju

20.7.4 Substance Designer i UE5

Vredi pomenuti Substance 3D Designer -- industrijski standard za proceduralno kreiranje tekstura. Substance koristi node-based sistem slican UE5 Material Editor-u, ali sa fokusom na offline generisanje tekstura.

Kljucna razlika:

Substance pristup daje neogranicenu kompleksnost (moze koristiti stotine noise slojeva, simulirati eroziju, itd.) bez runtime performansnog troska. UE5 pristup daje beskonacnu rezoluciju i runtime parametrizaciju ali je ogranicen GPU budzetom.

Mnogi profesionalni timovi koriste oba pristupa:


20.8 Veza sa drugim poglavljima

Proceduralni materijali se nadovezuju na mnoge koncepte iz prethodnih poglavlja:

U narednim poglavljima, videcete kako se proceduralni materijali koriste za:


20.9 Rezime poglavlja

U ovom poglavlju smo prosli kroz svet proceduralnih materijala -- od osnovnih noise funkcija do prakticnih primera. Hajde da sumiramo kljucne tacke:

  1. Proceduralni materijali generisu vizuelni sadrzaj matematicki umesto iz bitmap slika. Nude beskonacnu rezoluciju, odsustvo tiling artifakata i mocan sistem parametrizacije.

  2. Noise funkcije su temelj proceduralnih materijala:

    • Value noise interpolira nasumicne vrednosti na resetki
    • Perlin noise koristi gradijente za glatciji rezultat
    • Simplex noise je efikasnija verzija Perlin-a bez dimenzionalnih ogranicenja
    • Worley noise koristi rastojanje do najblize tacke za celijske pattern-e
    • FBM kombinuje vise oktava za fraktalne detalje na svim skalama
  3. Proceduralno generisanje tekstura kombinuje matematicke pattern-e (cigle, plocice) sa noise-om za organske varijacije. Domain warping je kljucna tehnika za prirodne, tecne deformacije.

  4. Anti-tiling tehnike (hex tiling, stochastic sampling, texture bombing) eliminisu vidljivo ponavljanje bitmap tekstura.

  5. World-aligned textures koriste poziciju u svetu umesto UV koordinata, eliminisujuci UV seamove. Triplanar mapping prosiruje ovo na proizvoljno orijentisane povrsine.

  6. Prakticni primeri -- cigla, drvo, mramor i weathering -- demonstriraju kako se ovi koncepti kombinuju u realisticne materijale.


20.10 Tabela kljucnih termina

Termin (engleski) Objasnjenje
Procedural Material Materijal ciji se vizuelni sadrzaj generise algoritmom, ne iz unapred napravljenih slika.
Noise Function Matematicka funkcija koja proizvodi kontrolisanu, glatku pseudo-nasumicnost.
Value Noise Noise baziran na interpolaciji nasumicnih vrednosti na regularnoj resetki.
Perlin Noise Noise baziran na gradijentnim vektorima na cvorovima resetke. Razvio Ken Perlin 1983.
Simplex Noise Poboljsana verzija Perlin noise-a, efikasnija u visim dimenzijama. Takodje Ken Perlin, 2001.
Worley Noise (Cellular Noise) Noise baziran na rastojanju do najblizih feature tacaka. Proizvodi celijske/Voronoi pattern-e.
FBM (Fractal Brownian Motion) Tehnika kombinovanja vise oktava noise-a na razlicitim frekvencijama i amplitudama.
Octave Jedan sloj noise-a u FBM procesu. Svaka oktava dodaje detalje na manjoj skali.
Lacunarity Faktor povecanja frekvencije izmedju uzastopnih oktava u FBM-u. Tipicno 2.0.
Persistence Faktor smanjenja amplitude izmedju uzastopnih oktava u FBM-u. Tipicno 0.5.
Domain Warping Tehnika koristenja izlaza jednog noise-a za deformaciju ulaznih koordinata drugog noise-a.
Tiling Ponavljanje teksture/pattern-a preko povrsine.
Anti-Tiling Tehnike za eliminaciju vidljivog ponavljanja u tiling-u (hex tiling, stochastic sampling, itd.).
Hex Tiling Anti-tiling tehnika koja koristi heksagonalnu resetku i blending preklapajucih regiona.
Texture Bombing Tehnika nasumicnog rasipanja malih tekstura/elemenata po povrsini.
Stochastic Sampling Statisticki pristup uzorkovanju koji eliminise periodiku tiling-a.
World-Aligned Texture Textura mapirana koristeci poziciju u svetu umesto UV koordinata objekta.
Triplanar Mapping Varijanta world-aligned texturing-a koja koristi tri ortogonalne projekcije i blenduje ih na osnovu normala.
Curvature Zakrivljenost povrsine, koristi se za detekciju ivica u weathering efektima.
Weathering Vizuelno starenje i habanje materijala (rdja, prljavstina, habanje ivica, vlaga).
Baking Proces renderovanja proceduralnog rezultata u bitmap teksturu za bolje performanse.
Material Function Enkapsulirana logika materijala u UE5 koja se moze ponovo koristiti u vise materijala.
Hash Function Funkcija koja mapira ulaz na pseudo-nasumican izlaz. Koristi se za generisanje seed-ova u noise algoritmima.
Smoothstep Interpolacijska funkcija sa glatkim prelazom (nulti prvi izvod na krajevima).

20.11 Korisni resursi i dalje citanje

Online resursi:

Knjige:

UE5 specificni resursi:


U sledecem poglavlju cemo istraziti kako se materijali primenjuju na terene (landscapes) i kako proceduralne tehnike iz ovog poglavlja igraju kljucnu ulogu u kreiranju believable okruzenja na velikim skalama.