Pakiet „Hobbit”

Ożywianie Śródziemia dzięki Mobile WebGL

Daniel Isaksson
Daniel Isaksson

W przeszłości wyzwaniem było wprowadzenie na telefonach komórkowych i tabletach interaktywnych, internetowych opartych na wielu multimediach. Główne ograniczenia to wydajność, dostępność interfejsu API, ograniczenia dotyczące dźwięku HTML5 na urządzeniach oraz brak płynnego odtwarzania filmów.

Na początku tego roku wspólnie ze znajomymi z Google i Warner Bros. rozpoczęliśmy projekt związany z nowym filmem Hobbita – Hobbit: Pustkowie Smauga. Stworzenie pełnego multimediów – eksperymentu w Chrome na komórki było naprawdę inspirującym i trudnym zadaniem.

Treści są zoptymalizowane pod kątem Chrome na Androida na nowych urządzeniach Nexus, na których mamy teraz dostęp do WebGL i Web Audio. Znaczna część funkcji jest jednak dostępna również na urządzeniach i w przeglądarkach bez WebGL, a dzięki akceleracji sprzętowej komponowania i animacji CSS.

Cała scena jest oparta na mapie Śródziemia oraz w miejscach i postaci z filmów o Hobbitach. Dzięki WebGL mogliśmy inscenizować i poznać bogaty świat trylogii „Hobbit”, a użytkownicy mogą kontrolować to, co dzieje się na ekranie.

Wyzwania WebGL na urządzeniach mobilnych

Po pierwsze, termin „urządzenia mobilne” jest bardzo ogólny. Specyfikacje urządzeń bardzo się różnią. Dlatego jako deweloper musisz zdecydować, czy chcesz obsługiwać więcej urządzeń o mniej złożonym środowisku, czy, tak jak w tym przypadku, ograniczyć obsługiwane urządzenia do tych, które mogą wyświetlać bardziej realistyczny świat 3D. W przypadku „Podróży przez Śródziemie” skupiliśmy się na urządzeniach Nexus i pięciu popularnych smartfonach z Androidem.

W eksperymencie użyliśmy kodu three.js, tak jak w kilku poprzednich projektach WebGL. Wdrożenie rozpoczęliśmy od opracowania początkowej wersji gry Trollshaw, która dobrze działała na Nexusie 10. Po wstępnych testach na urządzeniu mieliśmy listę optymalizacji, które wyglądały podobnie do tych, które zwykle stosujemy w przypadku słabo wyglądających laptopów:

  • Używanie modeli o małej liczbie wieloboków
  • Używaj tekstur o niskiej rozdzielczości
  • Jak najbardziej zmniejsz liczbę wywołań rysowania przez scalanie geometrii.
  • Uprość materiały i oświetlenie
  • Usuń efekty posta i wyłącz antyaliasing
  • Optymalizacja wydajności JavaScriptu
  • Renderuj obiekt canvas WebGL o połowie rozmiaru i skaluj w górę za pomocą CSS

Po zastosowaniu tych optymalizacji w pierwszej, przybliżonej wersji gry uzyskaliśmy stałą liczbę klatek na 30 FPS, z której jesteśmy zadowoleni. Chcieliśmy poprawić jakość grafiki bez negatywnego wpływu na liczbę klatek. Próbowaliśmy wielu sztuczek. Niektóre z nich miały naprawdę duży wpływ na skuteczność kampanii, a niektóre nie były tak duże, jak się spodziewaliśmy.

Używanie modeli o małej liczbie wieloboków

Zacznijmy od modeli. Użycie modeli o małej intensywności z pewnością usprawnia pobieranie, a także czas potrzebny na zainicjowanie sceny. Okazało się, że możemy znacznie zwiększyć złożoność bez większego wpływu na skuteczność. Modele trolli, których używamy w tej grze, mają około 5 tys. twarzy, a scena to około 40 tys. twarzy i to nie problem.

Jeden z trolli z lasu Trollowe Bory
Jeden z trolli z lasu Trollowe Bory

W przypadku innej (jeszcze nieopublikowanej) lokalizacji w interfejsie zauważyliśmy większy wpływ na wydajność wynikające z ograniczenia liczby wielokątów. W tym przypadku w przypadku urządzeń mobilnych wczytywaliśmy obiekty składające się z mniejszych wielokątów niż obiekty dla komputerów. Tworzenie różnych zestawów modeli 3D wymaga nieco więcej pracy i nie zawsze jest wymagane. To zależy od tego, od czego będą potrzebne modele.

Podczas pracy na dużych scenach z wieloma obiektami staraliśmy się strategicznie podzielić geometrię. Pozwoliło nam to szybko włączać i wyłączać mniej ważne sieci, aby znaleźć ustawienie działające na wszystkich urządzeniach mobilnych. Następnie można było scalić geometrię w kodzie JavaScript w czasie działania w celu optymalizacji dynamicznej lub scalić je w wersji przedprodukcyjnej, aby zmniejszyć liczbę żądań.

Używaj tekstur o niskiej rozdzielczości

Aby skrócić czas wczytywania na urządzeniach mobilnych, zdecydowaliśmy się wczytywać różne tekstury o połowie mniejszym rozmiarze tekstu na komputerach. Okazuje się, że wszystkie urządzenia obsługują tekstury o rozmiarach do 2048 x 2048 pikseli, a większość z nich obsługuje także rozmiar 4096 x 4096 pikseli. Wygląda na to, że wyszukiwanie tekstur poszczególnych tekstur nie sprawia problemów po przesłaniu ich do GPU. Całkowity rozmiar tekstur musi zmieścić się w pamięci GPU, aby uniknąć ciągłego pobierania i pobierania tekstur, ale w przypadku większości użytkowników stron internetowych nie jest to wielki problem. Warto jednak połączyć tekstury w jak najmniejszą liczbę arkuszy sprite, aby zmniejszyć liczbę wywołań – to coś, co ma duży wpływ na wydajność na urządzeniach mobilnych.

Tekstura jednego z trolli w lesie Trollshaw
Tekstura jednego z trolli z lasu Trollowe Bory
(oryginalny rozmiar: 512 x 512 pikseli)

Uprość materiały i oświetlenie

Odpowiedni wybór materiałów również może mieć istotny wpływ na wydajność urządzenia, dlatego trzeba rozsądnie zarządzać nim na urządzeniach mobilnych. Do optymalizacji wydajności użyliśmy funkcji MeshLambertMaterial (obliczanie na światło wierzchołkowe) w 3.js zamiast MeshPhongMaterial (na światło Texel). Staraliśmy się korzystać z najprostszych cieniowania przy jak najmniejszej liczbie obliczeń.

Aby sprawdzić, jak używane materiały wpływają na skuteczność sceny, możesz zastąpić materiały użyte w niej za pomocą polecenia MeshBasicMaterial . To będzie dobre porównanie.

scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});

Optymalizacja wydajności JavaScriptu

Tworząc gry na urządzenia mobilne, GPU nie zawsze jest największą przeszkodą. Procesor zajmuje dużo czasu, zwłaszcza animacje i animacje fizyczne. Jedną ze sztuczek, która czasem pomaga, w zależności od symulacji, jest wykonywanie tych kosztownych obliczeń tylko co 2 klatki. Możesz też skorzystać z dostępnych technik optymalizacji JavaScriptu podczas pulowania obiektów, odbierania odpadów i tworzenia obiektów.

Zaktualizowanie wstępnie przydzielonych obiektów w pętlach, a nie tworzenie nowych, to ważny krok, który pozwala uniknąć „zakłóceń” zbierania pamięci podczas gry.

Rozważmy na przykład taki kod:

var currentPos = new THREE.Vector3();

function gameLoop() {
  currentPos = new THREE.Vector3(0+offsetX,100,0);
}

Ulepszona wersja tej pętli eliminuje tworzenie nowych obiektów, które muszą być zbierane do kosza.

var originPos = new THREE.Vector3(0,100,0);
var currentPos = new THREE.Vector3();
function gameLoop() {
  currentPos.copy(originPos).x += offsetX;
  //or
  currentPos.set(originPos.x+offsetX,originPos.y,originPos.z);
}

Moduły obsługi zdarzeń powinny w miarę możliwości aktualizować właściwości, a pętla renderowania requestAnimationFrame powinna aktualizować scenę.

Kolejna wskazówka to zoptymalizowanie lub wstępne obliczenie operacji rzutowania promieniami. Jeśli na przykład musisz połączyć obiekt z siatką podczas statycznego ruchu ścieżki, możesz „zapisać” pozycje w trakcie jednej pętli, a potem odczytywać z nich dane, zamiast rzutować promienie na siatkę. Podobnie jak w wersji Rivendell, Rivendell polega na rzutowaniu promieniami w celu znalezienia interakcji z myszą przy użyciu prostszej, niewidocznej siatki o niskiej liczbie wieloboków. Wyszukiwanie kolizji w sieci o dużej zawartości siatki jest bardzo powolne i ogólnie należy unikać kolizji w pętli.

Renderuj obiekt canvas WebGL o połowie rozmiaru i skaluj w górę za pomocą CSS

Rozmiar obszaru roboczego WebGL jest prawdopodobnie najskuteczniejszym parametrem, który można dostosować, aby zoptymalizować wydajność. Im większy obszar roboczy użyty do narysowania sceny 3D, tym więcej pikseli trzeba narysować na każdej klatce. Oczywiście ma to wpływ na wydajność.Dzięki wyświetlaczowi o dużej gęstości 2560 x 1600 pikseli Nexus 10 musi naciskać 4 razy więcej pikseli niż tablet o małej gęstości. Aby zoptymalizować to pod kątem urządzeń mobilnych, stosujemy sztuczkę – ustawiamy połowę rozmiaru obszaru roboczego (50%), a następnie skalujemy go do żądanego rozmiaru (100%) za pomocą wspomaganych sprzętowo przekształceń 3D CSS. Wadą tego rozwiązania jest pikselizowany obraz, gdzie cienkie linie mogą stanowić problem, ale na ekranie o wysokiej rozdzielczości efekt nie jest aż tak zły. Warto korzystać z tej możliwości.

Ta sama scena bez skalowania obszaru roboczego na Nexusie 10 (16 FPS) i skalowaniu do 50% (33 FPS)
Ta sama scena bez skalowania obszaru roboczego na Nexusie 10 (16 FPS) i skalowaniu do 50% (33 FPS).

Obiekty jako elementy składowe

Aby zbudować wielki labirynt zamku Dol Guldur i niekończącej się doliny Rivendell, utworzyliśmy zestaw modeli 3D z klocków, które wykorzystujemy ponownie. Ponowne wykorzystywanie obiektów pozwala nam mieć pewność, że utworzymy instancję i prześlemy je na początku, a nie w trakcie procesu.

Elementy trójwymiarowe obiektów użytych w labiryncie w Dol Guldur.
Elementy składowe obiektów 3D używane w labiryncie w Dol Guldur.

W Rivendell znajduje się kilka sekcji, które stale zmieniamy na głębokość Z w miarę postępów użytkownika. Gdy użytkownik mija sekcje, przesuwają się one w dużej odległości.

W zamku Dol Guldur chcieliśmy, aby labirynt był odświeżany dla każdej gry. W tym celu opracowaliśmy skrypt ponownie generujący labirynt.

Połączenie całej struktury w jedną dużą siatkę od początku skutkuje bardzo dużą sceną i słabą skutecznością. Aby rozwiązać ten problem, zdecydowaliśmy się ukryć i pokazać elementy składowe w zależności od tego, czy są widoczne. Od samego początku mieliśmy pomysł na wykorzystanie skryptu 2D raycaster, ale na koniec wykorzystaliśmy wbudowane wyszukiwanie frustrum queen size 3.js. Ponownie wykorzystaliśmy skrypt raycaster, aby pokazać „niebezpieczeństwo”, przed którym stoi odtwarzacz.

Kolejna ważna kwestia to interakcja użytkownika. Na komputerach użytkownicy mogą korzystać z myszy i klawiatury, a na urządzeniach mobilnych – dotyku, przesuwania, ściągnięcia palców, orientacji urządzenia itp.

Korzystanie z interakcji dotykowych w internecie mobilnym

Dodanie obsługi dotykowej nie jest trudne. Dostępne są świetne artykuły na ten temat. Pewne drobnostki mogą jednak utrudnić sprawę.

Możesz używać zarówno dotyku, jak i myszy. Chromebook Pixel i inne laptopy obsługujące dotyk obsługują zarówno mysz, jak i dotyk. Częstym błędem jest sprawdzenie, czy urządzenie obsługuje dotyk, a następnie dodanie tylko detektorów zdarzeń dotyku i żadnych dla myszy.

Nie aktualizuj renderowania w detektorach zdarzeń. Zapisz zdarzenia dotknięcia w zmiennych i reaguj na nie w pętli renderowania requestAnimationFrame. Poprawia to wydajność i łączy sprzeczne zdarzenia. Pamiętaj, aby używać obiektów ponownie, a nie tworzyć nowych w detektorach zdarzeń.

Pamiętaj, że to wielodotyk: event.touches to tablica różnych dotknięć. W niektórych przypadkach lepiej jest przyjrzeć się parametrom event.targetTouches lub event.changedTouches, aby po prostu reagować na interesujące Cię dotknięcia. Aby oddzielić kliknięcia od przesuwania z opóźnieniem, sprawdzamy z opóźnieniem, czy dotyk został przesunięty (przesunięcie) czy nieruchomy (dotknięcie). Aby określić, jaka jest odległość między dwoma pierwszymi dotknięciami, mierzymy, jak ta wartość zmienia się z biegiem czasu.

W świecie 3D musisz zdecydować, jak kamera reaguje na ruchy myszy, a na na działania przesuwania palcem. Jednym z typowych sposobów dodawania ruchu kamery jest podążanie za ruchem myszy. Można to robić przy użyciu bezpośredniej kontroli, korzystając z pozycji myszy, lub wykorzystując ruch delta (zmianę pozycji). Nie każdy użytkownik urządzenia mobilnego chce działać tak samo jak na komputerze. Przeprowadziliśmy gruntowne testy, aby zdecydować, co sprawdzi się w każdej wersji.

W przypadku mniejszych ekranów i ekranów dotykowych zauważysz, że palce użytkownika i grafika używane w interfejsie często przeszkadzają w wyświetlaniu treści. Jesteśmy przyzwyczajeni do projektowania aplikacji natywnych, ale w przypadku stron internetowych z pewnością nie zastanawialiśmy się nad tym. To prawdziwe wyzwanie dla projektantów i projektantów UX.

Podsumowanie

Z naszego projektu wynika, że WebGL na urządzeniach mobilnych działa bardzo dobrze, zwłaszcza na nowszych, zaawansowanych urządzeniach. Jeśli chodzi o wydajność, wygląda na to, że na czas pobierania i inicjowania wpływa głównie liczba wielokątów i rozmiar tekstur. Materiały, cieniowanie i rozmiar obszaru roboczego WebGL to najważniejsze elementy, które należy wziąć pod uwagę przy optymalizacji wydajności na urządzeniach mobilnych. To jednak suma elementów, które wpływają na skuteczność, i wszystko, co możesz zrobić, aby zoptymalizować wyniki.

Kierowanie na urządzenia mobilne oznacza również, że trzeba się przyzwyczaić do interakcji z ekranem dotykowym i że nie chodzi tylko o rozmiar w pikselach, lecz także o fizyczny rozmiar ekranu. W niektórych przypadkach musieliśmy przybliżyć kamerę 3D, aby faktycznie zobaczyć, co się dzieje.

Eksperyment się rozpoczął i był fantastyczny. Mamy nadzieję, że Ci się to podoba.

Chcesz spróbować? Wybierz się w Podróż do Śródziemia.