Tutorial: Dinamikus normal mapok az arcon Unreal 4-ben

Sziasztok!

Megígértük, hogy magyarul is elkészítjük az arcráncolós-normal mapos tutorialt, és bár sok időbe telt, most itt van.

***

Kezdetben szükségetek lesz egy kész arcra, amin a morph targeteket csontok irányítják. Válogassátok ki, melyik morph targeteknek kellene befolyásolnia a ráncolódást. A mi esetünkben például Salnek vannak ráncai a szemöldökfelhúzásra (két szemöldök külön-külön), szemöldökráncolásra, "aggódásra" (szemöldök belső része fel), és a nevetésre. Egy ember esetében természetesen sokkal többféle ráncra is szükség lehet - a szem körüli nevetőráncokra, stb. Viszont Salnek szőre van, emiatt mi keveset használunk, és ezeket is csak halványan.
Ami azt illeti, minden bizonnyal a NeoFurrel fogjuk kombinálni ezt a technikát, szóval ez egyelőre tényleg csak demonstrációként szolgál.
Ebben a tutorialban Blendert és GIMPet használunk, de bármilyen 3D modellező, illetve képszerkesztő program megfelel erre a célra. (De a Blender és a GIMP ingyenes, szóval bárki számára elérhető!)
Akkor kezdjünk is neki.


A meshről szedjetek le minden részt, ami más matérián van, vagy nem releváns. Nálunk csak a fej és a nyak maradt, szemek és haj nélkül.


Duplikáljátok le a fejet - minden egyes ráncolódó morph targetre jusson egy fej.


Ezután újra duplikáljátok le mindet külön, és ezekből a verziókból töröljetek ki tényleg mindent, ami nem kell - nyak, fej hátoldala, fül; mindent, amin nem fog változni a normal map. Ha megvan, tegyetek rá egy Multires modifier-t, mondjuk négyszereset, hogy részletesen tudjátok szerkesztgetni a sculpt toollal.


Ideje szobrászkodni! Dolgozzátok ki az arcredőket, amiket az adott morph target aktiválódása esetén látni akartok az arcon. Semmi másra nincs szükség, csak a ráncokra, mivel az ezekről készült normal mapot hozzá fogjuk adni az arc alap normal mapjához, szóval minden bőrrészlet (esetünkben szőrszál) látható marad alatta. Ügyeljetek arra, hogy ne csússzanak egymásra a külön morphok ráncai, mert az összesből egyetlen normal mapot fogunk összeállítani.


Ha készen vagytok a szobrászkodással, bake-eljetek normal mapot az új, ráncos arcról az eredeti fejre (csináltunk egy tutorialt a Blenderben való bake-elésről régebben, itt megnézhetitek. Angolul van, de reméljük, a lényeg érthető). Bake-elhettek ambient occlusion mapokat is, hogy nagyobb mélységérzetet adjon, de mi ebben a tutorialban nem csináltunk. Ugyanezzel a szisztémával tudjátok azokat is működtetni egyébként.


Miután kész a sütés, a képmanipuláló szoftveretekben kombináljátok össze a külön normalmap-részeket egy normalmapba. Itt ki tudjátok radírozgatni az egymásra lógó részeket, ha kell.


Következő lépésként maszkokat kell csinálnotok a normal mapra. A legtakarékosabb ezeket a maszkokat egy kép három külön channelére elmenteni (R, G, B, illetve az alpha channelt is használhatjátok erre a célra). Mivel a maszkok színtelenek, csak a feketétől fehérig terjedő szürkéket használják, így egy képbe akár 4 különböző maszk is belefér. A legjobb képformátum erre a célra a Targa (.tga), mivel az mossa össze a channeleket a legkevésbé.
Erre a célra is megfelel a GIMP, vagy a Photoshop, de meglepne, ha bármilyen másik képszerkesztő szoftver nem lenne képes channelekre bontani valamit.


Nekünk hat különböző ráncunk van, így hat maszkot használunk a normal maphoz - ez két képen elfér a színchanneleket használva. Fekete alapon kezdjétek, és a ráncok helyét fehérrel színezzétek be! Ez jelzi a material editornak, hogy melyik helyen kell láthatóvá tennie a ráncokat.


Miután kész, adjátok össze a channeleket egy kész képpé, és mentések el .tga-ként.


Kész vagyunk ezzel a résszel, most jöhet az Unreal 4!


Importáljátok be az új normal mapot és a maszkokat, és tegyétek be őket a karakteretek matériájába. A maszkokat tegyétek egymás alá és tegyetek mindegyik mellé egy multiply node-ot. A multiply node másik inputjába kössetek külön scalar parametereket - ezeket tudjuk majd Blueprintből irányítani. Nevezzétek el őket úgy, hogy logikus legyen, illetve meg tudjátok jegyezni; pl. a bal szemöldök felhúzásához készült ráncoknak a maszkjánál nálunk BrowUp_L a paraméter neve. Ügyeljetek arra, hogy mindegyiknél maradjon nulla a default érték.

Ezek után az összes maszknak csináljatok egy LinearInterpolation (Lerp) node-ot, és kössétek őket sorba az A inputjaiknál. A legelső Lerp node A inputjához csináljatok egy három értékkel rendelkező texture sample node-ot (hármas gomb lenyomva + bal katt), aminek a harmadik, azaz blue értékét 1-re állítjátok. Ehelyett használhattok egy üres normal mapot is, lényegében ugyanaz. Kössétek be az A inputba.

A lerpek alpha inputjába a mellettük lévő multiply node-ok outputját kössétek be, a lerpek B inputjába pedig az új ráncnormalunkat.
Az utolsó lerp outputja egy BlendAngleCorrectedNormals node AdditionalNormal inputjába csatlakozzon. Ugyanennek a node-nak a BaseNormal-jába kössétek be az eredeti normalmap-ot. Végül ennek a node-nak az outputját kössétek be a material input node Normal inputjába (a végső, alap material node-ba).

Végeztünk a material editorral, most bíbelődjünk egy kicsit a skeletonnal.

Mivel a morphjainkat csontok vezérlik, ki tudunk kerülni egy fontos hibát. Jelenleg az egyetlen 'get morph target' blueprint node mindig nullát ad vissza értékként (ha valamit én csináltam rosszul, szóljatok!), szóval használhatatlan. Viszont mivel a morph targetekkel együtt a csontok is forognak, így ha egy socketet teszünk a csontokra, le tudjuk olvasni a morph targetek weight-jét.
Tehát a következő lépés az, hogy minden ráncot irányító morph target csontjára teszünk egy-egy socketet, és logikusan elnevezzük őket.
A legjobb megoldás az, ha úgy forgatjuk a socketeket, hogy minden socketen ugyanaz a tengely legyen, amit le kell olvasnunk - szóval, ha például Sal felhúzza a jobb szemöldökét, a hozzá tartozó socket az X tengelyen elfordul 90 fokot. Ez macerás, de az átláthatóság miatt fontos.


Az ideális eset a következő: ha a morph target értéke 0, a hozzá kapcsolódó socket rotációja az X tengelyen 0. Ha a morph értéke 1, a socket 90 foknál, ha a morph értéke -1, a socket -90 foknál áll. Ez azért fontos, mert ha például a morph 0 lenne a 90 fok, a morph -1 lenne a 0 és a morph 1 lenne a 180, akkor ha a morph értéke 1 fölé csúszik valamilyen okból, a socket rotációja átfordul -180 fokba.
Ennek az eredménye egy átmenet nélkül eltűnő ráncmap, ami sosem ideális. A legtisztább megoldás a -90, 0 és 90 fok.

A socket rotációját a Skeleton ablakban le tudjátok olvasni, miközben egy animációt játszotok le. De akár az animblueprintben ki is printelhetitek a képernyőre egy print node-dal, hogy ellenőrizzétek, illetve a blueprinten belül jobb kattal 'watch'olhatjátok a kívánt értéket, így játék közben szemmel tarthatjátok magában a blueprintben is.

Most jön az AnimBlueprint, ami irányítja a ráncokat!

Elég nagy a kép, lehet, hogy jobban jártok, ha letöltitek

Lehet, hogy elég lesz ránéznetek erre a képre, és simán meg tudjátok csinálni, de mi mindent elmagyarázunk a kép alatt.

A karakter animation blueprintjében az event graph Update Animation eventjét kell kibővítenünk ezekkel a node-okkal. Az event legelejére tettem egy bool-t, amivel ki lehet kapcsolni az egész ráncolódást, ha éppen nincs rá szükség. Ez után leellenőrzöm, hogy üres-e a matériát tartalmazó változó (ha igen, és nem foglalkoznánk vele, akkor errorokat gyártana). Ha üres, akkor készít a megfelelő material elementből egy dinamikus matériát, és azt fogja használni ezentúl.
Az arcon a matériának dinamikusnak kell lennie, mivel csak így tudjuk változtatni a matériában létrehozott paramétereket, miközben a játék megy. A dinamikus matériákat pedig csak Blueprintben lehet létrehozni, így ideális esetben az első induláskor létrejön a dinamikus matéria, és utána már megy minden, mint a karikacsapás.

Ezután jön a socketes matek. A legegyszerűbb példa: azt szeretnénk tudni, milyen esetben kell aktiválnunk a bal szemöldök felhúzásakor keletkező ráncokat. Ehhez le kell olvasnunk a hozzá tartozó socket rotációját. Fontos: component space-ben kell a rotáció, mivel az a csont alaphelyzetéhez képest méri a rotációt, és nem world space-ben, ami a pálya 0,0,0 rotációjához képest méri, és emiatt mindig más eredményt ad a karakter forgásától függően. Szóval: kell egy Get socket transform node, amiben beállítjuk a component space-t.

A material parameter a következőképpen működik esetünkben: ha a parameter 0, a ránc nem látszódik; ha a parameter 1, a ránc teljesen látszódik, minden köztes érték részleges láthatóságot eredményez. Tehát el kell érnünk, hogy a 90 fok 1-es értéket jelentsen, a 0 fok pedig 0-s értéket.
Ezt a legegyszerűbben egy 'normalize to range' node-dal érhetjük el. A node működése egyszerű: a bevitt számból a minimum és maximum között gyakorlatilag százalékot csinál.
Például ha a minimum 0, a maximum 100, akkor egy 36-os value 0,36 lesz a node outputjában.

Miután megkaptuk ezt a 0 és 1 közti számot, egyszerűen csak be kell állítanunk a material parameterünket erre az értékre, és kész is vagyunk. Még érdemes a normalize to range előtt betenni egy clamp-et, hogy még véletlenül se csússzon az érték 0 alá vagy 90 fölé.

Hogyha a morph targetet plusz és minusz értéken is használjuk (és mindkettőhöz társul ránc), csak minimálisan nehezebb a dolgunk. Minden ugyanaz, csak be kell illesztenünk egy abszolútérték node-ot közvetlenül a socket rotáció node után, hogy a negatív értékek is pozitívra váltsanak.

A harmadikféle megoldás, amit használunk, az a több morph target által különböző mértékben aktivált ránc a szájon. Itt minden morph target más mértékben ad hozzá a ránc láthatóságának értékéhez - például a mosolynál kevesebb látszik, mint a nagyra tátott szájnál. Itt egyszerűen annyi a dolgunk hogy a végső értékeket megszorozzuk valamilyen 1-nél kisebb számmal, attól függően, hogy mennyire akarjuk, hogy hatással legyen a ráncra a morph, majd az összes szorzott értéket összeadjuk, és a set node előtt clampeljük 0 és 1 közé.

Tesztelésre ajánlatos egy morph target teszt animációt létrehozni, ahol sorban aktiváljátok a morph targeteket egyenként, hogy látszódjon, működnek-e rendesen a ráncok. Nálunk nagyon sok próbálkozás volt, mire megtaláltuk a leggyorsabb és legegyszerűbb módját mindennek, szóval reméljük, hogy segítettünk ezzel a tutoriallal. A legfontosabb, ha a végén nem akartok szenvedni a socketekkel, hogy pontosan leírjátok a rotációs értékeket, vagy mindet ugyanarra állítjátok be.

Halvány ráncok a homlokon
Kevésbé halvány nevetőráncok
Ha van kérdésetek vagy észrevételetek, bátran jelezzétek kommentben!

No comments:

Post a Comment