Android Programjaim

Android, Flutter/Dart, Kotlin, Java, Unity, HarmonyOS

Flutter rasterizer irasa, obj 3d model megjelenitese - PRO

2020. január 27. 10:32 - lacas8282

Elore bocsatom, hogy ez a cikk sokkal hosszabb lenne, ezert nem fogok bevagdosni kod reszleteket, hanem linkeket fogok bevagni inkabb es altalanossagban beszelek a classokrol. Ha minden sort kielemezgetnek, akkor itt ulnek hajnalig lehet.

Ez a topic egy kicsit mar PRO-bb, bar annyira azert nem PRO, mint mondjuk FBX-et parse-olni es megjeleniteni csak canvason/CustomPainter-el (ott azert vannak durvabb dolgok, foleg mivel van sok file verzio)

Eloszor is: https://github.com/klaszlo8207/Flutter-OBJ-3D-Viewer (Nyugodtan star-olhatod, ha akarod :D)

Errol a kis projectemrol lesz szo, igyekszek par dolgot elmeselni rola.

Ez ugye egy obj modell file megjelenito, ami kepes haromszogeket megjeleniteni es kirajzolni texturazva is akar, alap fennyel es shading-el. Azert irtam, mert opengl-ezni nem lehet Flutterben, de nekem kellett egy megjelenito, ami kepes statikus vagy animalt modell file-ok megjelenitesere. Ez ugye egyelore csak statikusat tud, kesobb irok az animaltrol, de az mar tenyleg PRO-bb.

Tehat a CustomPainter widget-et fogjuk hasznalni, es ugy rajzolunk ki texturalt haromszogeket, igy celszeru, hogy az obj file-ban is haromszogek legyenek. Ehhez igenybe tudjuk venni pl az Autodesk 3DS MAX-ot. Ingyen elerheto trialban.

Kezdjunk neki: https://github.com/klaszlo8207/Flutter-OBJ-3D-Viewer/blob/master/lib/main.dart

Object3DDetails-ben tarolom a 3d model file reszleteit, tulajdonsagait. Es ezeket pedig egy listaban, minden modellnek mas zoomja es mas forgasa is van ezert ezeket is. Valamint mas grid tile meret tartozhat hozza. (Ezt meg csak belekodoltam, de nyilvan kesobb nem art egy min-max bound alapjan kirajzolni a racsot)

Minden modelnek kulon szinezest es fenyt is adtam.

Mivel a fenyzest/shadinget lerp-el, linearis interpolacioval allitjuk ossze a normalokkal egyetemben, igy celszeru ezeket is allitgatni kesobb, mint lathato az opacity-vel meghataroztam hogy mibol hany szazalekot keverjen majd bele, mi nez ki jol.

ChangeVariants: mivel a setState mindent ujrarajzol, ezert minden ilyen jellegut ki kellett szednem es provider architecturat hasznalnom, errol mar volt szo a blogon, keress ra. A lenyeg, hogy ilyenkor ChangeNotifier-t hasznalunk es nem rajzol mindent ujra, widgeteket. A globalis valtozokat beleraktam ebbe a classba es van nekik getter-e, setter-je. Provider-el es Consumer-el hivogatjuk meg majd oket.

Van egy startTimer, amiben kb a feny poziciojat allitgatjuk es a controller-el beallitjuk. A controller hasonloan mukodik, mint pl egy videoPlayerController, vagy barmi ilyen jellegu cucc.

Az Object3DViewer widget a fo class, ennek egy csomo tulajdonsaga van mar, mindegyiknek a neve a tartalmara mutat, baromi beszedesek, ugyhogy nem is beszelek roluk.

Talan meg a rasterizerMethod-rol annyi, hogy van old es new, az old pixelenkent rajzolt, es doglassu, de ott lehet igazan megtanulni, hogy rajzol egy raszterizer ki dolgokat a kepernyore. A new meg mar a canvas.drawVertices-el rajzol, ami Skia konyvtart hasznal, ami valoszinu OpenGL-ES-t androidon, de vannak mas backendjei is, Metal, Vulkan stb.

Van egy object3DViewerController, amivel kesobb manipulalni lehet az objViewer-ben dolgokat.

De haladjunk, mert kezdek ehes lenni. :)

utils package-ben nyilvan util methodok vannak, nem reszleteznem. widgets-ben meg a ZoomGestureDetector widget.

obj3d-ben vannak a lenyeges cuccok, mint a painter, amiben kirajzol dolgokat, es lathato hogyan mukodik egy resterizer kb.

https://github.com/klaszlo8207/Flutter-OBJ-3D-Viewer/blob/master/lib/obj3d/object3d_model.dart

Nezzuk ezt az osztalyt: sima listak amikben benne vannak a vertexek, normalok, uv-k. Materialokat is es colorokat is kezelt, de en ezeket most nem hasznalom.

Csinaltam egy getMultiplicationValue functiont, ami elvileg 0 es 1 koze konvertalja a vertexeket, hogy kb azonos meretuek legyenek a cuccok.

Valamint egy centerPivot-ot, ami min/max kivalasztas alapjan a pivotjat a modellnek kozepre rakja. (_toCenterPivot)

A parser pedig siman parse-olja a dolgokat, errol mar volt szo egy korabbi posztban.

Vertexeket, normalokat, face-eket, uv-ket, (textura koordinatakat)

https://github.com/klaszlo8207/Flutter-OBJ-3D-Viewer/blob/master/lib/obj3d/object3d_viewer.dart

A kovetkezo nagyobb osztaly maga a Viewer. Itt tulajdonkeppen maga a widget van, valamint hozza egy controller, ami iranyitgatja a rajzolast a state-jen keresztul.

Itt talalhatoak az enumok is, amik megmondjak hogyan rajzoljuk ki a cuccot, es melyik raszterizer method-al rajzoljunk.

A controller alap dolgokat tud: forgatni, egy masik modellt betolteni, refreshelni, wireframe modot es feny poziciot megadni, de kb barmit is kothetunk ide.

Maga a state amit a controller neha meghiv, illetve a kirajzolas is itt tortenik ugye.

Inicializalasnal parse-oljuk a modelt, beallitjuk a texturat.

Az Object3DRenderer osztaly extendalja a CustomPainter-t es a ChangeNotifiert.

Tulajdonkeppen ez fog rajzolni. Nem akarjuk minden buildnel/state valtozaskor ujrarajzolni, uj peldanyt kesziteni belole, ezert egy peldanyt hasznalunk belole. A ChangeNotifier pedig a notifyListener-en keresztul megmondja a renderernek, hogy mi valtozott, mikor kell ujrarajzolni vmit.

6 Paint-et hoztam letre, a kulonbozo dolgok kirajzolasara. Valamint van egy depthBuffer is, de jelen esetben ezt se hasznalom, viszont meg lehet nezni, hogy hogyan mukodik egy depth buffer a kodban.

Az elso painter algoritmusom egy pixelenkenti szamolo cucc volt, iszonyat lassu, nincs tul sok ertelme mar hasznalni. Illetve nem is volt tul szep.

A depth_buffernek csak akkor van amugy szerepe, ha a scanline_paintert hasznaljuk. Kicsit majd refaktoralni is kell a kodot.

_drawTriangle: Kirajzol egy haromszoget 3 vertex, 3 uv es 3 normal alapjan. Maga a function elkulonit 3 reszt, wireframe, shaded, tetxured modokat. Valamint kulon is lehet rarajzolni a modellre a wireframe-et.

Az alap brightness-t szamolo cucc mar nem is nagyon kell bele majd, mivel van fejlettebb cucc benne mar.

_clearDepthBuffer siman kinullazza kb a depth buffert.

_transformVertex: egy vertex poziciojat modositja rotation es scale alapjan (zoom/pan widget-en)

_drawGrids: gondoltam jol jon egy Autodesk 3ds maxhoz hasonlo grid kirajzolasa is a kepre, igy ezt is lefejlesztettem, ez kulon bekapcsolhato a widgeten.

paint: maga a kirajzolasa MINDENNEK itt tortenik.

Elso korben ugye a face indexekbol generalnunk kell indexed vertexeket/normalokat/uv-ket. Tehat gyakorlatilag egy uj listat letrehozunk mind3 esetben.

A vertexeknel kulon transzformaljuk is oket, hogy mar a kirajzolt dolgok keruljenek a listankban. (nagyitott, eltolt, forgatott pontok)

sortedItems: A painter' algorithm szerint sorrendbe kell rendeznunk a haromszogeket Z ertek alapjan, hogy mi legyen amit elopbb, mit amit kesobb rajzoljunk ki (https://en.wikipedia.org/wiki/Painter%27s_algorithm)

Ezt baromi egyszeru egy MathUtils.zIndex(v1, v2, v3) -el tortenik. Ezutan persze sortoljuk order alapjan. Ez a szegeny ember algoritmusa kb, meg apro glitch-ek igyis elofordulnak sajnos a megjelenitonkben.

Vegul a sortedItems-en vegighaladva kirajzoljuk egyenkent a haromszogeket.

drawInfo mindenfele infot jelenit meg a Viewerben.

https://github.com/klaszlo8207/Flutter-OBJ-3D-Viewer/blob/master/lib/obj3d/painter/texture_data.dart

Vegul az utolso nehany osztaly, a TextureData az egyik.

Feltunhet, hogy 2 Image van benne, az egyik az UI-t hasznalja a masik pedig az image package-ben levot, tehat teljesen masok. :) Az egyik itt a scanline-hoz kell, a map is, a masik pedig az uj algoritmushoz.

Ez az osztaly amugy siman tarolja a kepet, es map-elni is tudja. convertABGRtoARGB azert kell, mert az image konyvtar vmiert ABGR-be rakja, nekunk meg ARGB-be kell...

https://github.com/klaszlo8207/Flutter-OBJ-3D-Viewer/blob/master/lib/obj3d/painter/scanline_painter.dart

Ezt az algot siman atirtam innen: https://github.com/davidmigloz/3d-engine/blob/master/src/main/java/com/davidmiguel/engine_3d/utils/DrawUtils.java

Viszont nem tul gyors, igy lehet majd kiszedem kesobb. Ha mar itt tartunk egesz jo kis 3d engine, aki teheti nezze meg. (Csak rohadt lassu az is) /https://github.com/davidmigloz/3d-engine/

A fejlettebb kirajzolas itt tortenik:

https://github.com/klaszlo8207/Flutter-OBJ-3D-Viewer/blob/master/lib/obj3d/painter/vertices_painter.dart

Mint lathato a cucc szinten haromszogeket rajzol ki, esetleg kesobb nem art az egeszet valahogy optimalizalnom.

Es akkor itt van az amikor azt mondjak sokan, hogy matek szinte alig kell a programozashoz. :D Ez sokszor igy van, de azert nem mindig. Nem art tudni mik a vektorok, normalok, lerp, vagy epp matrixok, kvaternionok, stb.

A paintnek keszitunk egy shadert, nyilvan nem akarunk minden korben ujat. A shader a texturedata image-jebol lesz megoldva TileMode.clamp-el.

Csinalunk 2 listat, a valodi vertexeket, amik 3d pontokbol 2 pontokat csinalnak (Offset-et), illetve a textureCoords listat, ami pedig az eredeti uv ertekek alapjan dolgozik a kep mereteit figyelembe veve.

A _calculateNormal function pedig normalertekekbol es a feny poziciojabol general egy normalt, amit clamp-elunk 0.1 es 1 kozotti ertekig. Ezt feljebb tolhatjuk akarmeddig, vagy csak 0-1 koze rakhatjuk. (Nekem a 0.1-1.0 valt be) Nyilvan max 1 legyen.

Vegul szinezni is akarjuk az egesz texturat, ehhez a Color.lerp-et hasznaljuk, ami szepen osszemossa a 2 szint egy t idointervallum szerint, ahol t 0-1 koze esik. Persze a t itt a kiszamitott normalertek lesz.

Vegul drawVertices-el kirajzoljuk az egeszet 1 hivasban, igy eleg gyors lesz. Egyebkent ez a Skia-t hasznalja, tehat valoszinusitem, hogy rogton attolja ez mar OpenGL ES backendnek, azert ilyen gyors.

Az alap szin es texturak valamint feny szin osszemosasat pedig a 2 szin opacity-jenek valtozasabol, valamint a normal ertekek shade-jebol, illetve a BlendMode.colorBurn -al valo kiirajzolasbol nyerjuk. :D

Huh, ennyi mara! :)

Koszonom, hogy elolvastad!

komment
süti beállítások módosítása