Vulkan, wykorzystanie pełni możliwości graficznych urządzeń mobilnych

25.03.2022 | Jakub Biliński

Wstęp

Zdecydowana większość aplikacji mobilnych na rynku składa się wyłącznie z dwuwymiarowych elementów. Nie jest to nic złego i w połączeniu z dobrze zaprojektowanym interfejsem użytkownika mogą one być niezwykle użyteczne oraz miłe dla oka. Problem pojawia się jednak wtedy, kiedy zwiększa się konkurencja w danym sektorze. W takiej sytuacji, żeby wyróżnić się z tłumu i zostać wybranym przez użytkownika, trzeba pójść o krok dalej i wyprzedzić konkurencję. Moim zdaniem dodanie trójwymiarowych akcentów jest w stanie poprawić doświadczenia użytkownika i spowodować, że wybierze naszą aplikację zamiast konkurencyjnej. Oczywiście dodanie trójwymiarowych elementów nie jest proste, jeżeli chcemy zrobić to dobrze. W tym artykule postaram się poruszyć tematykę od strony programistycznej.

 

Przykłady wykorzystania

Dodając elementy trójwymiarowe trzeba przemyśleć, będą one faktycznie wartością dodaną, a nie tylko “wodotryskiem”, który nic nie zmieni dla użytkownika. Dodanie elementów trójwymiarowych bez potrzeby może nieść więcej szkody, niż pożytku. Zespół UI/UX powinien więc bardzo dokładnie przemyśleć każdy pomysł przed wdrożeniem go przez zespół deweloperski. Co więcej, takie elementy powinny zostać dokładnie przemyślane przez zespół biznesowy, czy implementacja takiej rzeczy przyniesie wystarczającą wartość biznesową w stosunku do poniesionych kosztów.

Niektóre aplikacje wyglądają jak arkusz kalkulacyjny, a przy wykorzystaniu dobrze zaprojektowanych elementów trójwymiarowych, można by je zastąpić dziecinnie prostym interfejsem. Przykłady? Proszę bardzo, oto one:

  • Aplikacja do zarządzania samochodem - trójwymiarowy samochód, którym można zarządzać poprzez interakcję z jego trójwymiarową reprezentacją;
  • Aplikacja lotnicza - trójwymiarowy model samolotu w którym możemy wybrać miejsce i zobaczyć reprezentację 1:1, gdzie faktycznie będziemy siedzieć;
  • Aplikacja do budowy nieruchomości - możliwość obejrzenia tysięcy mieszkań na telefonie poprzez możliwość zobaczenia wszystkich domów/mieszkań na zewnątrz oraz wewnątrz w 3D;
  • Aplikacja bankowa - trójwymiarowa karta płatnicza, która zwiększy zaangażowanie interakcji z aplikacją mobilną.

To tylko kilka pomysłów, na które udało mi się wpaść. Możliwości są ograniczone tylko przez naszą wyobraźnię (i budżet). Zwróćmy jednak uwagę, że wszystkie te opisy to tylko ściana tekstu. Dobrze było móc przeczytać te pomysły, ale czy to faktycznie ma sens? Moim zdaniem tak i pokażę to na dwóch przykładach.

Zacznijmy od pierwszego przykładu, którym jest aplikacja mobilna do zarządzania samochodem. Aplikacje tego typu są często dość skomplikowane i mają dużą liczbę ekranów. Jest to jak najbardziej zrozumiałe z uwagi na wielość możliwości, które oferują. Co jednak, gdyby można było zarządzać samochodem w bardziej naturalny dla nas sposób? Moim zdaniem byłby to krok w dobrym kierunku.

 

 

Źródło: opracowanie własne

 

Drugi przykład jest nowinką i pochodzi z naszej aplikacji ING Business Mobile, która niedawno została zaktualizowana o trójwymiarową kartę płatniczą. Faktyczna implementacja tej trójwymiarowej karty nieco inna niż to, co zawarłem w dalszej części tego artykułu. Są to jednak detale implementacyjne, na których nie warto się skupiać, żeby nie wprowadzić niepotrzebnego zamieszania. Ogólne koncepcje oraz matematyka za tym stojąca jest niezmienna. Uważam, że ten przykład jest nawet lepszy od poprzedniego, bo pochodzi z realnej, a nie tylko abstrakcyjnej koncepcji.

 

Źródło: ING Business (Android/iOS)

 

Optymalizacja - a po co mi to?

W IT często stosuje się podejście, że nie warto czegoś optymalizować, bo szkoda na to czasu. Jeżeli tworzymy aplikację backendową to przecież można dołożyć więcej zasobów. Teraz pewnie wiele osób śmieje z tego podejścia, ale ma ono bardzo dużo sensu. Pomimo spowodowanego przez globalną pandemię wzrostu cen elektroniki, jest ona nadal tańsza od pracy zespołu IT (programiści, analitycy, testerzy, itd.). Oznacza to, że mimo wszystko lepiej wykorzystać zespół do naprawiania błędów lub rozwoju nowych funkcjonalności, niż poświęcać dużo czasu na procesy optymalizacji. Podejście to ma więc wiele sensu od strony biznesowej. Problem pojawia się, kiedy nie kontrolujemy środowiska uruchomieniowego, ponieważ aplikacja działa na telefonie klienta, a nie na naszym serwerze. Co wtedy?

Żeby zobaczyć co dzieje się, kiedy nasza aplikacja wymaga dużej ilości zasobów do uruchomienia, wystarczy sprawdzić opinie innych aplikacji mobilnych, wymagających nowoczesnych telefonów do płynnego działania. Ludzie ze słabszymi urządzeniami będą wysoce niezadowoleni i to niezadowolenie zostanie uwidocznione w komentarzach. Dlatego właśnie warto zoptymalizować naszą aplikację mobilną. W przypadku elementów trójwymiarowych, które możemy dodać do naszej aplikacji, jest to jeszcze ważniejsze, ponieważ są one z natury bardzo “zasobożerne”. Bez dokładnej kontroli nad całym procesem rysowania takich elementów na ekranie może dojść do sytuacji, gdzie nasza aplikacja będzie wymagać flagowych telefonów do jej uruchomienia, a tego zdecydowanie nie chcemy.

 

Niskopoziomowe API

Na tym etapie wiemy już naprawdę sporo. Widzieliśmy kilka przykładów użycia trójwymiarowych elementów i jesteśmy świadomi, że należy je dobrze zoptymalizować. W przeciwnym wypadku nasza aplikacja będzie nieużywalna. Nie wiemy jednak jak to zrobić.

Tutaj ścieżki dla różnych systemów operacyjnych się rozchodzą. W przypadku tworzenia rozwiązania dla systemu iOS musimy skorzystać z Metal API, a w przypadku systemu Android będzie to Vulkan API. Niestety, nie opiszę tutaj implementacji tego na system operacyjny iOS, ponieważ rozmiar tego artykułu zmieniłby go w książkę. Koncepcje tutaj przedstawione są ogólnie wymienne między różnymi API. Główne różnice wynikają z implementacji tych API przez ich twórców, jednakże sprzęt, na którym operują jest do siebie na tyle zbliżony, że muszą udostępniać podobną warstwę abstrakcji.

Bez względu na wszystko należy zaznaczyć, że zarówno Metal, jak i Vulkan są niskopoziomowymi API. Można by się zastanowić, czy nie lepiej wykorzystać coś dużo bardziej wysokopoziomowego, jak jeden ze znanych silników do gier. Unity, Unreal Engine, czy Godot to tylko kilka z nich. Na pierwszy rzut oka wybór wydaje się dobry. Stworzenie czegoś na tak rozbudowanych silnikach jest o wiele prostsze, niż tworzenie czegoś od zera. Tutaj można jednak dostrzec drugą stronę tych rozwiązań - są one ciężkie i zużywają dużo zasobów. Dzieje się tak dlatego, że gry to więcej, niż tylko trójwymiarowy model z prostym modelem oświetlenia. Silniki do gier składają się z: fizyki, dźwięku i o wiele bardziej zaawansowanego modelu oświetlenia. Oczywiście to, co wymieniłem to tylko czubek góry lodowej. Silniki do gier to ogromne narzędzia, które są zbudowane z niewiarygodnej ilości małych systemów, których rezultatem jest to, co otrzymujemy. Niestety, użycie tak rozbudowanych narzędzi kłóci się trochę z optymalizacją, która była opisywana wcześniej w tym artykule. Dlatego właśnie wybór silnika do gier wydaje się być błędną decyzją, jeżeli budujemy aplikację, w której elementy trójwymiarowe mają być tylko dodatkiem, a nie jej rdzeniem.

Odrzuciliśmy wysokopoziomowe opcje w postaci silników do gier. Co natomiast z opcją pośrednią, czyli OpenGL. Jest to pytanie, na które na  pewno należy odpowiedzieć. Wybór OpenGL jest bardzo dobrym pomysłem, ale musimy wziąć pod uwagę kilka jego mankamentów. Wszystkie jego problemy wynikają z jednego powodu -  OpenGL jest stary. Określenie to jest śmiesznie niewystarczające, ponieważ ma on prawie 30 lat! W IT 30 lat jest prawie prehistorią. W związku ze swoimi długimi korzeniami dzierży ze sobą wiele problemów. Po pierwsze OpenGL to ogromna maszyna stanów. Najłatwiej wytłumaczyć to na życiowym przykładzie. Wyobraźcie sobie, że jesteście w sterowni ogromnej fabryki. Znajdują się tam tysiące przycisków, przełączników i pokręteł, za pomocą których ustawiacie ich stan, czyli co i jak ma zostać wykonane. Po ustawieniu naciskacie wielki przycisk “WYKONAJ” i w tym momencie, na podstawie tego, jakie stany zostały ustawione, takie akcje zostają wykonane. Wszystko bardzo dobrze funkcjonuje do momentu, w którym chcielibyście przyspieszyć działanie fabryki, umieszczając więcej osób w sterowni. Niestety, nie jest możliwe, aby pracowała tam więcej niż jedna osoba, ponieważ prowadziłoby to do wzajemnego modyfikowania sobie stanów. W konsekwencji maszyny działające w fabryce wykonywałyby zadania niezgodnie z oczekiwaniami tamtych osób. Rozwiązaniem tego problemu mogłaby być praca na zmianę, to znaczy w danym momencie tylko jedna osoba ustawiałaby stany według swoich oczekiwań i naciskała przycisk "WYKONAJ".

Takie podejście nie pozwalałoby wykorzystać pełnego potencjału, który drzemie w fabryce. Jak to rozwiązać? Nie ma na to pytanie prostej odpowiedzi. Podobnie jest z OpenGL, tylko że tutaj rolę osób w sterowni pełnią wątki w procesorze, a sprzętem w fabryce jest nasz układ graficzny. OpenGL jest właśnie taką ogromną maszyną stanów, a raczej jego sterownią, gdzie przycisk "WYKONAJ" jest zastąpiony przyciskiem "RYSUJ" albo "OBLICZ". Niestety przez takie podejście nie możemy wykorzystać wielowątkowości, która na urządzeniach mobilnych jest tak ważna. Dodatkowo sama architektura OpenGL nie odzwierciedla sprzętu, który znajduje się w naszych telefonach, a co za tym idzie marnujemy sporo potencjału w nich drzemiącego. Oznacza to, że o wiele lepszym rozwiązaniem jest wybranie nowszego i bardziej wydajnego rozwiązania w postaci Vulkana.

 

Jak narysować kwadrat - teoria

Posiadamy już wiedzę kiedy i dlaczego warto wybrać niskopoziomowe API graficzne. Myślę, że naturalnym krokiem jest teraz zastanowienie się jak tę wiedzę wykorzystać. Zanim przejdziemy jednak do faktycznej implementacji warto prześledzić sobie ścieżkę rysowania kwadartu na ekranie. Jest to dobry start i wprowadza wiele koncepcji, które są wykorzystywane także przy bardziej zaawansowanych modelach.

Żeby narysować coś na ekranie najpierw musimy to opisać komputerowi (telefon to taki minikomputer). Zaczynamy od zdefiniowania wierzchołków. Zabrzmię pewnie głupio, ale weźcie do ręki kartkę i długopis. Poważnie, weźcie! Tak będzie łatwiej zrozumieć o czym piszę. Narysujcie na tej kartce kwadrat i zaznaczcie na nim wszystkie cztery wierzchołki. Następnie musimy je jakoś nazwać. Ja przyjąłem nazwę v0, v1, v2 oraz v3. Litera “v” pochodzi od angielskiego słowa “vertex”, czyli “wierzchołek”.

Wróćmy do naszej kartki. Mamy na niej narysowany kwadrat z zaznaczonymi wierzchołkami. Teraz odwróćcie kartkę, narysujcie same wierzchołki dla tego kwadratu za pomocą dużych kropek i nazwijcie każdy z nich.

 

Z tych narysowanych wierzchołków można stworzyć kwadrat, ale nie tylko. Jeżeli połączymy wierzchołki w innej kolejności to powstaną inne figury. Kolejność połączenia wierzchołków jest kluczowa i musimy poinformować komputer jak ma je połączyć, aby stworzyć kwadrat, a nie jakiegoś potworka. Komputery uwielbiają trójkąty (podobno niektórzy ludzie również). Dlaczego? Rysując wielokąty łatwiej jest zrobić coś źle, natomiast przy zaledwie trzech wierzchołkach trudno jest popełnić błąd. Inżynierowie podjęli więc decyzję, że głównym sposobem rysowania będzie przekazywanie właśnie trójkątów. Zaczynamy od narysowania trójkąta, łącząc wierzchołki v0, v3 oraz v1 (zgodnie z oznaczeniem przyjętym na poprzednim rysunku). Możemy podawać trójkąty zgodnie z ruchem wskazówek zegara bądź przeciwnie do ruchu wskazówek (jak tutaj). Ważne jest to, żeby podawać je w takiej kolejności, w jakiej zostało skonfigurowane w API graficznym.

 

Powinniście mieć na kartce narysowany jeden trójkąt. Czas na zagadkę dla was - jak zbudować teraz kwadrat? Chwila przemyśleń… Najłatwiej rysując drugi trójkąt poprzez połączenie ze sobą wierzchołków v0, v2, v3. Dzięki temu otrzymacie dwa trójkąty, które współdzielą ze sobą jedną krawędź i w płynny sposób tworzą jedną figurę - kwadrat.

 

Ostatnim etapem, który musimy wykonać jest rasteryzacja. Popatrzcie na kartkę, macie jakieś linie, ale kwadrat nie jest wypełniony. Musimy pokolorować kwadrat, wypełniając najpierw jeden trójkąt, a następnie drugi. Specjalny program odpalony na GPU (Fragment/Pixel Shader) wypełnia każdy piksel w celu otrzymania zadanego efektu. W tym przypadku kolorujemy każdy punkt na ekranie na kolor ING, czyli #FF6200.

 

To zasadniczo tyle, jeżeli chcemy narysować kwadrat. Bardziej skomplikowane modele to po prostu zbiór setek, czy raczej dziesiątek tysięcy trójkątów. Może to być trudne do wyobrażenia, dlatego poniżej znajduje się obrazek przedstawiający Suzanne, czyli prymityw z programu Blender.

 

Jak narysować kwadrat - praktyka

Jesteśmy już nasyceni wiedzą teoretyczną, teraz pora na praktykę. Przed kontynuowaniem chciałem poinformować, że do zrozumienia poniższych przykładów potrzebna jest dobra znajomość programowania ze szczególnym uwzględnieniem Javy/Kotlina oraz C++, ponieważ opisuję poniżej dość zaawansowane koncepty i nie chciałbym skupiać się na detalach samych języków.

Rysowanie w Vulkanie ma swój cykl życia. Ten cykl wygląda w miarę podobnie w większości aplikacji, a ewentualne różnice wynikają ze specyficznych potrzeb danej aplikacji. Pewne kroki są stałe i wykonuje się je praktycznie zawsze. Na samym początku tworzymy instancję Vulkana, czyli tak jakby podłączamy się do API graficznego. Następnie musimy (zależnie od systemu) pobrać powierzchnię, na której będziemy rysować, czyli cały ekran lub jakiś mały jego wycinek. Kolejne kroki to inicjalizacja zasobów potrzebnych do rysowania, których jest… sporo. Na koniec w pętli wykonujemy cały proces rysowania, który polega na nagrywaniu zadań dla naszego układu graficznego. Nagrywamy komendy dla GPU tak długo, jak widoczny jest widok, na którym rysujemy. Poniżej w języku angielskim przedstawiam cykl życia naszego procesu rysującego.

Źródło: opracowanie własne 

 

W tym artykule skupimy się tylko na kilku wybranych etapach z powyższego cyklu życia, a dokładnie na tych, które są kluczowe dla Androida. Cały proces renderowania możecie znaleźć w oficjalnych przykładach użycia grupy Khronos na platformie GitHub, gdzie w bardzo przejrzystych przykładach ukazane jest jak możecie zrobić najróżniejsze rzeczy z użyciem Vulkana.

Na samym początku musicie utworzyć projekt w Android Studio w wybranym języku (Java/Kotlin) oraz dodać wsparcie dla natywnego języka C++. Jeżeli macie już projekt to nie jest to problem, bo wsparcie dla C++ możecie dodać również do istniejących projektów. Nie zapomnijcie jeszcze o zainstalowaniu niezbędnych rzeczy w SDK Manager!

Przejdźmy teraz do samego kodu. Pierwszym z etapów, który musimy wykonać to utworzenie w kodzie natywnym powierzchni do rysowania. Warto do tego celu stworzyć plik w C++, który będzie odpowiadał za całe rysowanie. Ja nazwałem go przykładowo “VulkanGraphicsEngine.cpp”. W tym pliku musimy zainicjować powierzchnię. W tym celu wypełniamy specjalną strukturę “VkAndroidSurfaceCreateInfoKHR”, a następnie wywołujemy metodę “vkCreateAndroidSurfaceKHR”, która stworzy nam odpowiednią instancję powierzchni do rysowania. Powinniśmy dodać też chociaż podstawową obsługę błędów. Właśnie w tym celu musimy przypisać wartość zwróconą przez funkcję “vkCreateAndroidSurfaceKHR” i sprawdzić, czy jest ona równa “VK_SUCCESS”. Jeżeli nie, to znaczy, że wystąpił błąd i powinniśmy go obsłużyć. Etapy, które trzeba wykonać, mogą brzmieć przerażająco, ale jak możecie zobaczyć na zdjęciu poniżej wcale tak nie jest. Dodatkowo etapy, które trzeba wykonać są bardzo schematyczne i będziemy je powtarzać przy wykonywaniu większości czynności w Vulkanie.

Mamy więc kod w C++, ale jak go teraz wywołać? W tym celu musimy stworzyć jeszcze jeden plik w C++, który będzie swego rodzaju mostem. Ja nazwałem go “VulkanSurfaceBridge.cpp”. Tworzymy w nim metody, a następnie oznaczamy je jako dostępne z poziomu Javy. Metody w tym pliku będą odpowiadać za wywoływanie wcześniej napisanego kodu, np. do stworzenia powierzchni do rysowania.

Teraz powinniśmy dopisać inicjalizację pozostałych elementów w C++, czyli wierzchołków, indeksów i wielu innych. Pominę ten etap, ponieważ jest to bardzo długi proces, który można samemu wywnioskować na podstawie przykładów konsorcjum Khronos. My teraz skupmy się na tym, jak wywołać ten kod natywny z poziomu Kotlina. Musimy stworzyć klasę, która dziedziczy po klasie “SurfaceView” oraz implementuje interfejs “SurfaceHolder.Callback2”. Następnie w tej nowej klasie ładujemy bibliotekę natywną i tworzymy metody oznaczone jako “extern”, które możemy wykorzystać do komunikacji właśnie z natywnym kodem C++. Podłączamy w Kotlinie odpowiednie funkcje do odpowiednich ich odpowiedników natywnych.

Teraz możemy po prostu dodać nasz widok w pliku XML naszej aktywności, tak jak dowolny inny element.

 

Mamy już gotowy mechanizm tworzenia powierzchni do rysowania w natywnej aplikacji androidowej, a więc pora na implementację tego kodu we Flutterze. Do wykonania tego potrzebujemy cały poprzedni krok poza modyfikacją widoku XML. Do tego musimy jednak dodać kilka elementów we Flutterze, żeby móc je wyrenderować.

We Flutterze mamy dwie możliwości wyświetlania widoków natywnych z Androida. Pierwsza nazywa się “Virtual Display”, a druga “Hybrid Composition”. Od Androida 10 podejście “Hybrid Composition” ma poważne problemy wydajnościowe. Z tego powodu poniżej opiszę podejście “Virtual Display”, które nie posiada tych problemów. Więcej na ten temat możecie przeczytać tutaj.

We Flutterze wszystko jest widgetem, także na początku musimy utworzyć widget, który zwraca “AndroidView”. Szczególnie ważnym parametrem w konstruktorze widgetu “AndroidView” jest “viewType”, ponieważ literał, który tam wprowadzamy będzie służy do połączenia go z kodem Javy/Kotlina. Należy zwrócić szczególną uwagę, żeby nie popełnić w nim literówki.

Kolejny krok to utworzenie swego rodzaju pojemnika po stronie Kotlina do wyświetlenia danego elementu we Flutterze. Tworzymy nową klasę, dziedziczącą po “PlatformView”, a następnie w metodzie “getView” podpinamy nasz widok, który utworzyliśmy dla części natywnej.

Przedostatnim krokiem w dodawaniu natywnego widoku do Fluttera jest stworzenie specjalnej fabryki, która będzie odpowiadać za tworzenie nowych instancji naszych widoków. Żeby to zrobić tworzymy klasę, która dziedziczy po “PlatformViewFactory” i implementujemy metodę “create” w taki sposób, żeby tworzyła nowe instancje, kiedy jest nam to potrzebne. Oczywiście przykładowa implementacja przedstawiona poniżej jest dość prosta, ale jeżeli będziemy mieli taką potrzebę, to możemy tutaj stworzyć ogromną logikę, która pozwoli nam zoptymalizować ten proces pod nasze potrzeby.

 

Na sam koniec musimy jeszcze zarejestrować naszą fabrykę i nadać jej nazwę. Teraz uwaga! Nazwa musi być zgodna z polem “viewType”, które podawaliśmy po stronie Fluttera, ponieważ jeżeli wystąpi jakakolwiek różnica w literale, to zachowanie kodu będzie niezgodne z naszymi oczekiwaniami.

 

To wszystko w kwestii implementacji tego widoku we Flutterze. Teraz możemy korzystać z nowego widgetu, tak jak z dowolnego innego we Flutterze. Nie musimy robić żadnych tricków, mechanizm jest po prostu plug&play.

Jest jeszcze jeden aspekt inicjalizacji, który chciałbym tutaj krótko opisać - wybór GPU. Vulkan to API, które jest niezależne od platformy, więc może działać zarówno na komputerach, jak i na urządzeniach mobilnych. Komputery często posiadają więcej niż jeden układ graficzny (np. zintegrowana karta graficzna oraz karta podłączona przez PCI Express) i właśnie dlatego Vulkan wymaga wyboru jednego z nich. Zanim przejdziemy do wyboru urządzenia graficznego, musimy najpierw pobrać ich listę. Służy do tego bardzo prosty kod w C++.

 

Teraz musimy wybrać odpowiednie urządzenie. Gdybyśmy tworzyli aplikację dla komputerów osobistych, to powinniśmy stworzyć ogromną logikę. Tworzymy jednak aplikację dla urządzeń mobilnych i możemy to łatwo obejść. Jak? Skoro mamy urządzenie mobilne, to możemy być praktycznie pewni, że będzie wyposażone tylko w jedno urządzenie graficzne. W rezultacie zawsze możemy wybrać pierwsze GPU z listy, gdyż będzie to jedyne urządzenie graficzne. Dzięki temu możemy uprościć logikę do minimum.

Po poprawnym zainicjowaniu wszystkich zasobów możemy przejść do nagrywania komend dla naszego GPU, które następnie przekażemy do wykonania. Efekt wykonania tych operacji otrzymamy na ekranie. Podczas nagrywania przekazujemy informacje o wierzchołkach, indeksach oraz program do wykonania na GPU, a urządzenie wykonuje te zadania w nagranej przez nas kolejności.

Wiem, że to co opisałem w tej sekcji może wyglądać na chaotyczne, a pewnie w jakimś stopniu nawet takie jest. Problemem jednak jest ilość rzeczy, która dzieje się w Vulkanie. Niemniej, wydaje mi się, że poruszyłem wszystkie implementacyjne aspekty, które różnią użycie Vulkana na urządzeniach mobilnych od tego na komputerach osobistych. Możecie teraz, korzystając z wiedzy teoretycznej z poprzedniej sekcji przeanalizować ten kawałek kodu, a następnie dostosować go do narysowania kwadratu na ekranie. Jeżeli wam się to uda, to nic nie stoi na przeszkodzie, by następnie narysować coś bardziej zaawansowanego jak sześcian, czy inny trójwymiarowy model. Jeżeli nie jesteście jeszcze gotowi na analizę przykładowego kodu to gorąco zapraszam do filmu z początku tego artykułu, gdzie omawiam tematykę bardziej szczegółowo.

 

Vulkan w akcji!

Po zaimplementowaniu wszystkich elementów powinniśmy na ekranie telefonu/emulatora zobaczyć pomarańczowy kwadrat (w sumie to bardziej prostokąt, jeżeli nie wykonamy odpowiedniej projekcji). Poniżej możecie zobaczyć zdjęcia z dwóch wersji aplikacji, zarówno z opisywanej wersji “natywnej”, czyli tej napisanej w Kotlinie, oraz z wersji Flutterowej, czyli tej z dodatkową warstwą kodu napisanego w języku Dart.

Podsumowanie

Jak widzicie na podstawie tego artykułu, niskopoziomowe podejście do grafiki trójwymiarowej ma wysoki próg wejścia, ale możliwości jakie nam daje są praktycznie nieograniczone. Warto jest zawsze zastanowić się, czy potrzebujemy tego typu rozwiązania skrojonego na miarę. Jeżeli aplikacja dąży do bycia liderem na rynku i ma ogromne zaplecze doświadczonych deweloperów, to odpowiedź brzmi: tak. W przypadku aplikacji tworzonej przez kilka osób należy się zastanowić, czy nie lepiej można by wykorzystać ten np. na dodanie nowych funkcji do aplikacji, a tego typu elementy odroczyć lub rozwiązać je tymczasowo w inny sposób. Jednakże każdy przypadek należy rozpatrywać indywidualnie i warto jest poświęcić czas na wnikliwą analizę.

Mam jednak nadzieję, że nie przeraziłem was tym artykułem i nie odrzuciłem was od niskopoziomowego podejścia do grafiki komputerowej. Moim celem było zaciekawieniem was tą niszową tematyką. Jeżeli mi się to udało i chcielibyście dowiedzieć się więcej to zapraszam do przeczytania moich artykułów na LinkedIn, gdzie regularnie udostępniam publikacje na temat Vulkana oraz grafiki komputerowej.