Dance Tonite w WebVR

Byłem podekscytowany, gdy zespół Google Data Arts skontaktował się z firmą Moniker i mnie, aby wspólnie zbadać możliwości zapewniane przez WebVR. Przez lata obserwowałem ich pracę i zawsze podobały mi się ich projekty. Współpraca zaowocowała Dance Tonite, czyli stale zmieniającym się tanecznym doświadczeniem w VR, które powstało we współpracy z LCD Soundsystem i ich fanami. Oto jak to zrobiliśmy.

Wprowadzenie

Zaczęliśmy od opracowania serii prototypów z użyciem WebVR, otwartego standardu, który umożliwia wejście do VR przez odwiedzenie witryny w przeglądarce. Chcemy ułatwić każdemu dostęp do rzeczywistości wirtualnej, niezależnie od używanego urządzenia.

Wzięliśmy to pod uwagę. Wszystko, co wymyśliliśmy, powinno działać na wszystkich typach VR, od gogli VR współpracujących z telefonami komórkowymi, takich jak Daydream View, Cardboard i Gear VR firmy Samsung, po systemy room-scale, takie jak HTC VIVE i Oculus Rift, które odzwierciedlają Twoje fizyczne ruchy w wirtualnym środowisku. Co najważniejsze, uznaliśmy, że w duchu internetu warto stworzyć coś, co będzie działać również dla osób, które nie mają urządzenia VR.

1. Przechwytywanie ruchu w domu

Chcieliśmy zaangażować użytkowników w kreatywny sposób, więc zaczęliśmy szukać możliwości udziału i wyrażania siebie za pomocą VR. Zaskoczyło nas, jak drobnostka może się poruszać i rozglądać w rzeczywistości wirtualnej oraz jaka była dokładność. To dało nam pewien pomysł. Zamiast wymagać od użytkowników oglądania lub tworzenia czegoś, możesz rejestrować ich ruchy.

Ktoś nagrywa siebie w Dance Tonite. Ekran za nim pokazuje to, co widzi na zestawie słuchawkowym.

Stworzyliśmy prototyp, w którym odnotowywaliśmy pozycje okularów VR i kontrolerów podczas tańca. Zastąpiliśmy nagrane pozycje abstrakcyjnymi kształtami i byliśmy zachwyceni wynikami. Wyniki były bardzo ludzkie i mają niezwykłą osobowość. Szybko zdaliśmy sobie sprawę, że możemy używać WebVR do taniego tworzenia filmów z wykorzystaniem śledzenia ruchów w domu.

Dzięki WebVR deweloper ma dostęp do pozycji głowy i orientacji użytkownika za pomocą obiektu VRPose. Ta wartość jest aktualizowana w każdej klatce przez sprzęt VR, aby Twój kod mógł renderować nowe klatki z prawidłowego punktu widzenia. Dzięki interfejsowi GamePad API z WebVR mamy też dostęp do pozycji i orientacji kontrolera użytkownika za pomocą obiektu GamepadPose. Po prostu zapisujemy wszystkie te wartości pozycji i orientacji w każdej klatce, tworząc w ten sposób „nagranie” ruchów użytkownika.

2. Minimalizm i kostiumy

Dzięki obecnemu sprzętowi do VR w skali pokoju możemy śledzić 3 punkty ciała użytkownika: głowę i dwie ręce. W Dance Tonite chcieliśmy skupić się na ludzkości w ruchu w tych 3 punktach w kosmosie. Aby to osiągnąć, ograniczyliśmy estetykę do minimum, aby skupić się na ruchu. Spodobała nam się idea wykorzystania ludzkiego mózgu.

Ten film przedstawiający pracę szwedzkiego psychologa Gunnara Johanssona był jednym z przykładów, do których się odwoływaliśmy, gdy rozważaliśmy jak najbardziej uproszczenie procesu. Pokazuje on, jak unoszące się białe kropki są natychmiast rozpoznawane jako ciała, gdy są w ruchu.

Pod względem wizualnym zainspirowały nas kolorowe pomieszczenia i geometryczne kostiumy w tym nagraniu z 1970 r. przedstawiającym odtworzenie baletu Triadisches Oskara Schlemmera przez Margarete Hastings.

Powodem, dla którego Schlemmer wybrał abstrakcyjne stroje geometryczne, było ograniczenie ruchu tancerzy do lalek i marionetek, a my mieliśmy inny cel – Dance Tonite.

Ostatecznie wybraliśmy kształty na podstawie tego, ile informacji przekazywały one za pomocą obrotu. Kula wygląda tak samo bez względu na to, jak jest obrócona, ale stożek naprawdę wskazuje kierunek, w którym patrzy, i wygląda inaczej niż z przodu, a nie z tyłu.

3. Pedał zapętlający

Chcieliśmy pokazać duże grupy nagrywanych osób, które tańczą i poruszają się razem. Nie da się tego zrobić na żywo, ponieważ obecnie nie ma wystarczającej liczby urządzeń VR. Chcieliśmy jednak, aby grupy ludzi reagowały na siebie poprzez ruch. Skupiliśmy się na rekurencyjnym występie Normana McClarena w jego utworze filmowym „Canon” z 1964 roku.

McClaren wykonuje serię bardzo wyreżyserowanych ruchów, które po każdym pętli zaczynają się wzajemnie przeplatać. Podobnie jak w przypadku muzyki z pętlą, w której muzycy grają ze sobą, nakładając na siebie różne utwory, chcieliśmy sprawdzić, czy uda nam się stworzyć środowisko, w którym użytkownicy będą mogli swobodnie improwizować luźniejsze wersje występów.

4. Pokoje połączone

Pokoje połączone

Podobnie jak wiele innych utworów, utwory LCD Soundsystem są budowane za pomocą precyzyjnie zsynchronizowanych metrum. Utwór Tonite, który jest zawarty w naszym projekcie, zawiera 8-sekundowe dźwięki. Chcieliśmy, aby użytkownicy wykonywali każdą 8-sekundową pętlę na ścieżce. Chociaż rytm tych taktów się nie zmienia, ich treść muzyczna jest inna. W miarę postępów utworu pojawiają się momenty z różnymi instrumentami i wokalami, na które wykonawcy mogą reagować w różny sposób. Każda z tych wartości jest wyrażona jako pokój, w którym ludzie mogą wykonać przedstawienie dopasowane do jego potrzeb.

Optymalizacja pod kątem wydajności: nie pomijaj klatek

Stworzenie wieloplatformowego środowiska VR, które działa na podstawie jednej bazy kodu i zapewnia optymalną wydajność na każdym urządzeniu lub platformie, nie jest łatwym zadaniem.

W VR jedną z najbardziej nieprzyjemnych rzeczy jest to, że częstotliwość wyświetlania klatek nie nadąża za Twoimi ruchami. Jeśli obrócisz głowę, ale obrazy widziane przez Twoje oczy nie będą pasować do ruchu odczuwanego przez Twoje ucho wewnętrzne, natychmiast poczujesz mdłości. Z tego powodu musieliśmy uniknąć dużych opóźnień w częstotliwości odświeżania. Oto kilka zaimplementowanych przez nas optymalizacji.

1. Buforowana geometria z instancjami

Ponieważ w całym projekcie używamy tylko kilku obiektów 3D, mogliśmy uzyskać ogromny wzrost wydajności dzięki użyciu instancjonowanej geometrii buforowej. Zasadniczo pozwala to przesłać obiekt do karty graficznej tylko raz i wygenerować dowolną liczbę „kopii” tego obiektu w jednym wywołaniu funkcji draw(). W Dance Tonite mamy tylko 3 różne obiekty (kegel, cylinder i pokój z otworem), ale potencjalnie setki kopii tych obiektów. Geometria buforowa instancji jest częścią biblioteki ThreeJS, ale użyliśmy eksperymentalnej i wciąż rozwijanej gałęzi Dusan Bosnjak, która implementuje THREE.InstanceMesh, co znacznie ułatwia pracę z geometrią buforową instancji.

2. Unikanie zbieracza

Podobnie jak w przypadku wielu innych języków skryptowych, JavaScript automatycznie zwalnia pamięć, ustalając, które przydzielone obiekty nie są już używane. Ten proces nazywa się zbieraniem śmieci.

Deweloperzy nie mają kontroli nad tym, kiedy to nastąpi. W każdej chwili pod naszymi drzwiami może pojawić się śmieciarz i zacząć opróżniać śmieci. W tym czasie ramka może zniknąć.

Rozwiązaniem jest produkowanie jak najmniej śmieci poprzez recykling przedmiotów. Zamiast tworzenia nowego obiektu wektorowego dla każdego obliczenia, oznaczone zostały obiekty utworzone od podstaw, aby można było ich używać wielokrotnie. Zachowujemy je, przenosząc odniesienia do nich poza nasz zakres, dlatego nie zostały oznaczone do usunięcia.

Oto przykład kodu służącego do konwertowania macierzy lokalizacji głowy i rąk użytkownika na tablicę wartości pozycji/obrotu, które przechowujemy w każdym ujęciu. Dzięki ponownemu używaniu obiektów SERIALIZE_POSITION, SERIALIZE_ROTATIONSERIALIZE_SCALE unikamy przydzielania pamięci i zbierania elementów, które miałyby miejsce, gdybyśmy tworzyli nowe obiekty przy każdym wywołaniu funkcji.

const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
    matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
    return SERIALIZE_POSITION.toArray()
    .concat(SERIALIZE_ROTATION.toArray())
    .map(compressNumber);
};

3. Serializowanie ruchu i odtwarzanie progresywne

Aby rejestrować ruchy użytkowników w VR, musieliśmy serializować pozycję i obroty ich zestawów słuchawkowych i kontrolerów oraz przesyłać te dane na nasze serwery. Zaczęliśmy od rejestrowania pełnych macierzy przekształceń dla każdej klatki. Osiągnięcie to było dobre wyniki, ale przy 16 cyfrach pomnożonych przez 3 pozycje przy 90 klatkach na sekundę uzyskano bardzo duże pliki i dlatego długi czas oczekiwania na przesłanie i pobranie danych. Wyodrębniając z macierzy przekształceń tylko dane dotyczące położenia i obrotu, udało nam się zmniejszyć te wartości z 16 do 7.

Użytkownicy często klikają linki, nie wiedząc dokładnie, czego się spodziewać, dlatego musimy szybko wyświetlić treści wizualne, aby nie odeszli w ciągu kilku sekund.

Dlatego chcieliśmy jak najszybciej zacząć odtwarzać nasz projekt. Początkowo do wczytywania danych o ruchu używaliśmy formatu JSON. Problem polega na tym, że musimy załadować cały plik JSON, zanim będziemy mogli go przeanalizować. Niezbyt postępowe.

Aby projekt taki jak „Dance Tonite” wyświetlał się z najwyższą możliwą liczbą klatek na sekundę, przeglądarka ma tylko niewielką ilość czasu na każdą klatkę na obliczenia JavaScript. Jeśli zajmie Ci to zbyt dużo czasu, animacje zaczną się zacinać. Na początku występowały problemy z zacinaniem, ponieważ przeglądarka musiała dekodować te ogromne pliki JSON.

Natrafiliśmy na wygodny format danych strumieniowych o nazwie NDJSON lub JSON oddzielony znakami nowego wiersza. Aby to zrobić, utwórz plik z serią prawidłowych ciągów tekstowych JSON, z których każdy znajduje się w oddzielnym wierszu. Dzięki temu możesz przeanalizować plik podczas jego wczytywania, co pozwala nam wyświetlać wyniki jeszcze przed jego pełnym załadowaniem.

Oto fragment jednego z naszych nagrań:

{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...

Dzięki NDJSON możemy zachować reprezentacje danych poszczególnych klatek w występach jako ciągi znaków. Mogliśmy zaczekać, aż osiągnie wymagany czas, a potem zdekodować je w dane pozycjonujące, a tym samym rozłożyć przetwarzanie potrzebnego w czasie.

4. Interpolowanie ruchu

Chcieliśmy wyświetlać jednocześnie 30–60 występów, więc musieliśmy jeszcze bardziej obniżyć szybkość przesyłania danych. Zespół ds. sztuki danych rozwiązał ten sam problem w projekcie Virtual Art Sessions, w ramach którego za pomocą aplikacji til Brush odtwarzały nagrania artystów malujących w rzeczywistości wirtualnej. Rozwiązanie tego problemu polegało na tworzeniu pośrednich wersji danych użytkownika z niższą częstotliwością klatek i interpolowaniu między klatkami podczas ich odtwarzania. Byliśmy zaskoczeni, że trudno było zauważyć różnicę między interpolowanym nagraniem z 15 FPS a oryginalnym nagraniem z 90 FPS.

Aby się o tym przekonać, możesz zmusić Dance Tonite do odtwarzania danych z różnymi prędkościami za pomocą ciągu znaków zapytania ?dataRate=. Dzięki temu możesz porównać zarejestrowany ruch przy 90 klatkach na sekundę, 45 klatkach na sekundę lub 15 klatkach na sekundę.

W przypadku pozycji określamy interpolację liniową między poprzednią a następną klatką kluczową w zależności od tego, jak blisko jesteśmy w czasie między klatkami kluczowymi (współczynnik):

const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
    x1 + (x2 - x1) * ratio,
    y1 + (y2 - y1) * ratio,
    z1 + (z2 - z1) * ratio
    );

W przypadku orientacji stosujemy sferyczną interpolację liniową (slerp) między klatkami kluczowymi. Orientacja jest przechowywana jako kwaterniony.

const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
    getQuaternion(next, performanceIndex, limbIndex),
    ratio
    );

5. Synchronizacja ruchów z muzyką

Aby wiedzieć, który kadr nagranej animacji odtworzyć, musimy znać bieżący czas muzyki z dokładnością do milisekundy. Okazuje się, że chociaż element HTML Audio doskonale nadaje się do stopniowego wczytywania i odtwarzania dźwięku, jego właściwość czasu nie zmienia się w synchronizacji z pętlą klatek przeglądarki. Zawsze jest trochę za wcześnie lub za późno. Czasem o ułamek milisekundy za wcześnie, a czasem o ułamek za późno.

Powoduje to zacinanie się naszych pięknych nagrań tanecznych, czego chcemy uniknąć za wszelką cenę. Aby temu zaradzić, zaimplementowaliśmy własny licznik czasu w JavaScript. Dzięki temu możemy mieć pewność, że czas między kolejnymi klatkami jest dokładnie taki sam jak czas, który upłynął od ostatniej klatki. Gdy zegar jest o więcej niż 10 ms niesynchronizowany z muzyką, ponownie go synchronizujemy.

6. Wycinanie i mgła

Każda historia musi mieć dobre zakończenie, dlatego chcieliśmy zaskoczyć użytkowników, którzy dotarli do końca. Gdy opuścisz ostatni pokój, wejdziesz do miejsca, które przypomina spokojny krajobraz z kolumnami i walcami. Zastanawiasz się, czy to już koniec. Gdy wchodzisz na pole, nagle dźwięki muzyki powodują, że różne grupy rożków i cylindrycznych elementów łączą się w tancerzy. Znajdziesz się w środku wielkiej imprezy. Gdy muzyka nagle się kończy, wszystko spada na ziemię.

Chociaż było to świetne dla widzów, wiązało się z pewnymi problemami z wydajnością. Urządzenia VR o zasięgu pokojowym i ich zaawansowane platformy do gier działały idealnie podczas 40 dodatkowych występów, które były potrzebne do nowego zakończenia. Jednak liczba klatek na niektórych urządzeniach mobilnych spadła o połowę.

Aby temu zapobiec, wprowadziliśmy mgłę. Po pewnym dystansie wszystko powoli staje się czarne. Ponieważ nie musimy obliczać ani wyświetlać tego, co jest niewidoczne, ograniczamy wydajność w niewidocznych pomieszczeniach. Pozwala to oszczędzać zasoby procesora i układu graficznego. Ale jak zdecydować, jaka odległość będzie odpowiednia?

Niektóre urządzenia wytrzymują wszystko, co w nie rzucasz, a inne są bardziej ciasne. Zdecydowaliśmy się wdrożyć skalę przesuwną. Dzięki ciągłemu pomiarowi liczby klatek na sekundę możemy odpowiednio dostosować odległość mgły. Dopóki płynnie działa liczba klatek na sekundę, staramy się zwiększyć renderowanie, zwiększając zasięg mgły. Jeśli częstotliwość klatek nie jest wystarczająco płynna, przybliżamy mgłę, co pozwala nam pominąć renderowanie w ciemności.

// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
    frames++;
    const time = (performance || Date).now();
    if (prevTime == null) prevTime = time;
    if (time > prevTime + interval) {
    fps = Math.round((frames * 1000) / (time - prevTime));
    frames = 0;
    prevTime = time;
    const lastCullDistance = settings.cullDistance;

    // if the fps is lower than 52 reduce the cull distance
    if (fps <= 52) {
        settings.cullDistance = Math.max(
        settings.minCullDistance,
        settings.cullDistance - settings.roomDepth
        );
    }
    // if the FPS is higher than 56, increase the cull distance
    else if (fps > 56) {
        settings.cullDistance = Math.min(
        settings.maxCullDistance,
        settings.cullDistance + settings.roomDepth
        );
    }
    }

    // gradually increase the cull distance to the new setting
    cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;

    // mask the edge of the cull distance with fog
    viewer.fog.near = cullDistance - settings.roomDepth;
    viewer.fog.far = cullDistance;
}

Coś dla każdego: tworzenie VR dla internetu

Projektowanie i tworzenie wieloplatformowych, asymetrycznych wrażeń oznacza uwzględnienie potrzeb każdego użytkownika w zależności od jego urządzenia. Przy każdej decyzji projektowej musieliśmy sprawdzić, jak może ona wpłynąć na innych użytkowników. Jak dbasz o to, aby to, co widzisz w VR, było równie ekscytujące jak bez VR i na odwrót?

1. Żółta kula

Użytkownicy VR na skalę pokoju będą mogli tworzyć występy, ale jak projekt będzie wyglądał dla użytkowników urządzeń mobilnych VR (takich jak Cardboard, Daydream View czy Samsung Gear)? W tym celu dodaliśmy do naszej scenografii nowy element: żółtą kulę.

Żółta kula
Żółta kula

Gdy oglądasz projekt w rzeczywistości wirtualnej, robisz to z punktu widzenia żółtej kuli. Gdy przenosisz się z pokoju do pokoju, tancerze reagują na Twoją obecność. Robią gesty, tańczą wokół Ciebie, wykonują śmieszne ruchy za Twoimi plecami i szybko się odsuwają, aby Cię nie uderzyć. Żółta kula jest zawsze w centrum uwagi.

Podczas nagrywania występu żółta kula porusza się po środku pokoju w zgodzie z muzyką i powraca na swoje miejsce. Pozycja kuli pozwala wykonawcy określić, w którym miejscu pętli się znajduje i ile czasu mu jeszcze zostało. Pozwala to w naturalny sposób skupić się na osiąganiu określonych wyników.

2. Inny punkt widzenia

Nie chcieliśmy pomijać użytkowników bez VR, zwłaszcza że prawdopodobnie stanowiliby oni naszą największą grupę odbiorców. Zamiast tworzyć sztuczną inteligencję, chcieliśmy, aby urządzenia wyświetlane na ekranie były bardziej dostosowane do swoich potrzeb. Pomysł polegał na pokazaniu występów z lotu ptaka w perspektywie izometrycznej. Takie podejście ma długą tradycję w grach komputerowych. Po raz pierwszy użyto go w 1982 roku w strzelance kosmicznej Zaxxon. Podczas gdy użytkownicy VR znajdują się w samym środku akcji, perspektywa izometryczna daje widok z lotu ptaka. Postanowiliśmy pomniejszyć modele, by nadać im charakter domków dla lalek.

3. Cienie: udawaj, że potrafisz

Okazało się, że niektórzy użytkownicy mają problemy z widzeniem głębi w perspektywie izometrycznej. Jestem przekonany, że właśnie dlatego Zaxxon był jedną z pierwszych gier komputerowych, w której latające obiekty rzucały dynamiczne cienie.

Cienie

Okazuje się, że robienie cieni w 3D nie jest łatwe. Szczególnie w przypadku urządzeń o ograniczonej przestrzeni, takich jak telefony komórkowe. Początkowo musieliśmy podjąć trudną decyzję o wycofaniu ich z równania, ale po zapytaniu autora Three.js i doświadczonego hakera Mr doob o radę, wpadł na pomysł… ich sfałszowania.

Zamiast wyliczać, jak każdy z naszych obiektów pływających zasłania nasze światło i w związku z tym rzuca cienie o różnych kształtach, rysujemy pod każdym z nich ten sam okrągły, rozmyty obraz tekstury. Nasze elementy wizualne nie naśladują rzeczywistości, więc przekonaliśmy się, że wystarczy kilka poprawek, aby łatwo sobie z tym poradzić. Gdy obiekty zbliżają się do ziemi, tekstury stają się ciemniejsze i mniejsze. Gdy się przesuwają, tekstury stają się bardziej przezroczyste i większe.

Do ich utworzenia użyliśmy tej tekstury z delikatnym gradientem od bieli do czerni (bez przezroczystości alfa). Ustawiamy materiał jako przezroczysty i używamy mieszania subtraktywnego. Dzięki temu będą dobrze się komponować, gdy nakładają się na siebie:

function createShadow() {
    const texture = new THREE.TextureLoader().load(shadowTextureUrl);
    const material = new THREE.MeshLambertMaterial({
        map: texture,
        transparent: true,
        side: THREE.BackSide,
        depthWrite: false,
        blending: THREE.SubtractiveBlending,
    });
    const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
    const plane = new THREE.Mesh(geometry, material);
    return plane;
    }

4. Bycie tam

Klikając głowy tancerzy, użytkownicy bez gogli mogą oglądać rzeczy z perspektywy tancerza. Z tego kąta widać wiele drobnych szczegółów. Aby zachować rytm, tancerze szybko spoglądają na siebie. Gdy kula wkracza do pomieszczenia, widzimy, że nerwowo patrzą w swoją stronę. Jako widz nie możesz wpływać na te ruchy, ale zaskakująco dobrze oddają one poczucie zanurzenia. Ponownie woleliśmy to zrobić, niż zaoferować użytkownikom nudną wersję z kontrolą za pomocą myszy.

5. Udostępnianie nagrań

Wiemy, jak dumna możesz być z ukończenia skomplikowanej choreografii podczas nagrywania 20 warstw wykonawców reagujących na siebie nawzajem. Wiedzieliśmy, że użytkownicy prawdopodobnie będą chcieli pokazać go znajomym. Jednak statyczne zdjęcie tego wyczynu nie przekazuje wystarczającej ilości informacji. Chcieliśmy umożliwić użytkownikom udostępnianie filmów z ich występami. Dlaczego nie GIF? Nasze animacje mają płaskie cieniowanie, co idealnie pasuje do ograniczonych palet kolorów w tym formacie.

Udostępnianie nagrań

Stworzyliśmy GIF.js – bibliotekę JavaScriptu, która umożliwia kodowanie animowanych GIF-ów w przeglądarce. Przejmuje kodowanie klatek i przekazuje je instansom roboczym w przeglądarce, które mogą działać w tle jako osobne procesy, dzięki czemu można wykorzystać wiele procesorów pracujących równolegle.

Niestety ze względu na liczbę klatek potrzebnych do animacji proces kodowania był nadal zbyt wolny. Można z niego tworzyć małe pliki, używając ograniczonej palety kolorów. Okazało się, że najwięcej czasu zajmuje znalezienie najbliższego koloru dla każdego piksela. Udało nam się zoptymalizować ten proces 10-krotnie, stosując małe skróty: jeśli kolor piksela jest taki sam jak poprzedni, użyj tego samego koloru z palety.

Teraz mamy szybkie kodowanie, ale uzyskane pliki GIF są za duże. Format GIF pozwala określić, jak każda klatka ma być wyświetlana nad ostatnią, definiując metodę usuwania. Aby uzyskać mniejsze pliki, zamiast aktualizować każdy piksel w każdym ujęciu, aktualizujemy tylko piksele, które się zmieniły. Chociaż spowolniło to proces kodowania, znacznie zmniejszyło rozmiary plików.

6. Stały fundament: Google Cloud i Firebase

Backend strony z treściami generowanymi przez użytkowników może być często skomplikowany i niestabilny, ale dzięki Google Cloud i Firebase udało nam się opracować system, który jest prosty i wydajny. Gdy wykonawca przesyła do systemu nowy układ taneczny, Uwierzytelnianie Firebase przeprowadza anonimowe uwierzytelnianie. Użytkownik otrzymuje uprawnienia do przesyłania nagrania do tymczasowego pokoju za pomocą Cloud Storage for Firebase. Po zakończeniu przesyłania maszyna klienta wywołuje Cloud Functions dla Firebase za pomocą wyzwalacza HTTP i tokena Firebase. Spowoduje to uruchomienie procesu serwera, który zweryfikuje przesłanie, utworzy rekord w bazie danych i przeniesie nagranie do katalogu publicznego w Google Cloud Storage.

twardy grunt;

Wszystkie nasze publiczne treści są przechowywane w serii plików płaskich w zasobniku Cloud Storage. Dzięki temu nasze dane są szybko dostępne na całym świecie i nie musimy się martwić, że duże obciążenie ruchem w żaden sposób wpłynie na dostępność danych.

Użyliśmy punktów końcowych Firebase Realtime Database i Cloud Functions, aby stworzyć proste narzędzie do moderowania i publikowania, które pozwala nam oglądać każdą nową przesyłaną treść w VR i publikować nowe playlisty z dowolnego urządzenia.

7. Skrypty Service Worker

Skrypty service worker to dość nowa innowacja, która pomaga zarządzać przechowywaniem zasobów witryny w pamięci podręcznej. W naszym przypadku serwisy workerów wczytują nasze treści błyskawicznie dla powracających użytkowników, a nawet umożliwiają działanie witryny w trybie offline. To ważne funkcje, ponieważ wielu naszych użytkowników będzie korzystać z różnej jakości połączeń mobilnych.

Dodanie do projektu service workera było łatwe dzięki poręcznemu dodatkowi webpack, który wykonuje większość ciężkiej pracy. W konfiguracji poniżej generujemy skrypt service worker, który automatycznie zapisuje w pamięci podręcznej wszystkie nasze pliki statyczne. Pobiera najnowszy plik playlisty z sieci, jeśli jest dostępny, ponieważ playlista będzie się stale aktualizować. Wszystkie pliki JSON zapisu powinny być pobierane z pamięci podręcznej, jeśli są dostępne, ponieważ te pliki nigdy się nie zmienią.

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
    new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    minify: true,
    navigateFallback: 'index.html',
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    runtimeCaching: [{
        urlPattern: /playlist\.json$/,
        handler: 'networkFirst',
    }, {
        urlPattern: /\/recordings\//,
        handler: 'cacheFirst',
        options: {
        cache: {
            maxEntries: 120,
            name: 'recordings',
        },
        },
    }],
    })
);

Obecnie wtyczka nie obsługuje komponentów multimedialnych wczytywanych stopniowo, takich jak pliki muzyczne, więc znaleźliśmy rozwiązanie: ustawiliśmy w tych plikach nagłówek Cloud Storage Cache-Control na public, max-age=31536000, aby przeglądarka przechowywała je w pamięci podręcznej przez rok.

Podsumowanie

Nie możemy się doczekać, aż zobaczymy, jak wykonawcy wykorzystają tę funkcję i jak będą wykorzystywać ją do kreatywnej ekspresji za pomocą ruchu. Opublikowaliśmy cały kod open source dostępny na stronie https://github.com/puckey/dance-tonite. W tej wczesnej fazie rozwoju VR, a zwłaszcza WebVR, z niecierpliwością czekamy na to, w jakie nowe i nieoczekiwane kierunki rozwinie się to nowe medium. Tańczyć dalej.