Wirtualne sesje artystyczne

Szczegóły sesji artystycznej

Podsumowanie

Do malowania, projektowania i rzeźbienia za pomocą rzeczywistości wirtualnej zaproszono 6 artystów. To proces, w którym rejestrujemy ich sesje, konwertujemy dane i prezentujemy je w czasie rzeczywistym w przeglądarkach.

https://g.co/VirtualArtSessions

Co za czas, jak żyje się! Wprowadzenie rzeczywistości wirtualnej jako produktu konsumenta odkrywa nowe i nieodkryte możliwości. Tilt Brush to usługa Google dostępna na HTC Vive, która umożliwia rysowanie w trójwymiarowej przestrzeni. Gdy pierwszy raz wypróbowaliśmy Tilt Brush, uczucie gry ze śledzeniem ruchu i obecność „w pomieszczeniu z supermocami” pozostaje bez końca. Nie ma to jak rysowanie w pustej przestrzeni wokół siebie.

Wirtualne dzieło sztuki

Zespół ds. ochrony danych w Google miał przed sobą wyzwanie polegające na udostępnieniu tej funkcji osobom bez gogli VR w internecie, gdzie nie działa jeszcze Tilt Brush. W związku z tym członkowie zespołu skorzystali z pomocy rzeźbiarza, ilustratora, projektanta koncepcyjnego, artysty mody, instalacji i artystów ulicznych, aby tworzyli w tym nowym stylu dzieła sztuki w swoim stylu.

Rejestrowanie rysunków w rzeczywistości wirtualnej

Wbudowane w Unity oprogramowanie Tilt Brush jest aplikacją komputerową, która używa rzeczywistości wirtualnej w skali pomieszczenia, aby śledzić pozycję głowy (wyświetlacz zamontowany z góry, czyli HMD) i kontrolery w każdej z dłoni. Elementy graficzne tworzone w aplikacji Tilt Brush są domyślnie eksportowane do pliku .tilt. Zdaliśmy sobie sprawę, że potrzeba czegoś więcej niż tylko danych o grafice. Ściśle współpracowaliśmy z zespołem Tilt Brush nad zmodyfikowaniem tej aplikacji, aby eksportował działania cofania i usuwania oraz położenie głowy i dłoni wykonawcy z 90 razy na sekundę.

Podczas rysowania Tilt Brush korzysta z pozycji i kąta kontrolera i przekształca wiele punktów w „pociągnięcie”. Przykład znajdziesz tutaj. Opracowaliśmy wtyczki, które wyodrębniły te ciągi i wygenerowały je w formacie RAW.

    {
      "metadata": {
        "BrushIndex": [
          "d229d335-c334-495a-a801-660ac8a87360"
        ]
      },
      "actions": [
        {
          "type": "STROKE",
          "time": 12854,
          "data": {
            "id": 0,
            "brush": 0,
            "b_size": 0.081906750798225,
            "color": [
              0.69848710298538,
              0.39136275649071,
              0.211316883564
            ],
            "points": [
              [
                {
                  "t": 12854,
                  "p": 0.25791856646538,
                  "pos": [
                    [
                      1.9832634925842,
                      17.915264129639,
                      8.6014995574951
                    ],
                    [
                      -0.32014992833138,
                      0.82291424274445,
                      -0.41208130121231,
                      -0.22473378479481
                    ]
                  ]
                }, ...many more points
              ]
            ]
          }
        }, ... many more actions
      ]
    }

Powyższy fragment kodu przedstawia format szkicowanego formatu JSON.

Tutaj każda kreska jest zapisywana jako akcja o typie: „STROKE”. Chcieliśmy pokazać wykonawcę, który popełnia błędy i zmienia myślenie w trakcie szkicu, dlatego tak ważne było zapisanie działań „USUŃ”, które służą do usuwania lub cofania działań związanych z całym ruchem.

Zapisane są podstawowe informacje o każdym pociągnięciu, dzięki czemu zbierane są informacje o typie pędzla, jego rozmiarze i kolorze.

Na koniec zapisywane są wszystkie wierzchołki pociągu obejmujące położenie, kąt, czas oraz siłę nacisku na spust na kontrolerze (oznaczone jako p w każdym punkcie).

Pamiętaj, że rotacja jest 4-komponentowa. Jest to ważne na późniejszym etapie renderowania kresek, aby uniknąć blokady gimbal.

Odtwarzanie szkiców z użyciem WebGL

Aby pokazać szkice w przeglądarce, użyliśmy THREE.js i opracowaliśmy kod generowania geometrii, który naśladował działanie aplikacji Tilt Brush pod maską.

Aplikacja Tilt Brush tworzy w czasie rzeczywistym pasy trójkątne, kierując się ruchem dłoni użytkownika, ale do chwili, gdy pokazujemy go w internecie, cały szkic jest już „ukończony”. Dzięki temu możemy ominąć znaczną część obliczeń w czasie rzeczywistym i wypalić dane geometryczne po wczytaniu.

Szkice WebGL

Każda para wierzchołków pociągu generuje wektor kierunkowy (niebieskie linie łączące każdy punkt, jak pokazano powyżej, oraz moveVector we fragmencie kodu poniżej). Każdy punkt zawiera też orientację, kwaternion reprezentujący bieżący kąt kontrolera. W celu utworzenia paska trójkątnego powtarzamy nad każdym z tych punktów, uzyskując normalne, prostopadłe do kierunku i orientacji kontrolera.

Sposób obliczania paska trójkąta dla każdego pociągnięcia jest niemal taki sam jak w kodzie w Tilt Brush:

const V_UP = new THREE.Vector3( 0, 1, 0 );
const V_FORWARD = new THREE.Vector3( 0, 0, 1 );

function computeSurfaceFrame( previousRight, moveVector, orientation ){
    const pointerF = V_FORWARD.clone().applyQuaternion( orientation );

    const pointerU = V_UP.clone().applyQuaternion( orientation );

    const crossF = pointerF.clone().cross( moveVector );
    const crossU = pointerU.clone().cross( moveVector );

    const right1 = inDirectionOf( previousRight, crossF );
    const right2 = inDirectionOf( previousRight, crossU );

    right2.multiplyScalar( Math.abs( pointerF.dot( moveVector ) ) );

    const newRight = ( right1.clone().add( right2 ) ).normalize();
    const normal = moveVector.clone().cross( newRight );
    return { newRight, normal };
}

function inDirectionOf( desired, v ){
    return v.dot( desired ) >= 0 ? v.clone() : v.clone().multiplyScalar(-1);
}

Samodzielne połączenie kierunku i orientacji kreski daje niejednoznaczne wyniki matematyczne; może istnieć wiele wartości normalnych, które często powodują „skręt” geometrii.

Podczas iteracji nad punktami kreski zachowujemy wektor „preferowany prawy” i przekazujemy go do funkcji computeSurfaceFrame(). Ta funkcja zapewnia normalną, z której możemy wyciąć czworokąt z pasa poczwórnego, w zależności od kierunku ruchu (od ostatniego punktu do bieżącego punktu) oraz orientacji kontrolera (kwanton). Co ważniejsze, zwraca też nowy wektor „preferowany prawy” dla następnego zestawu obliczeń.

Kreski

Po wygenerowaniu czworokątów na podstawie punktów kontrolnych każdego kreski łączymy czworokąty, interpolując ich narożniki między czworokątami.

function fuseQuads( lastVerts, nextVerts) {
    const vTopPos = lastVerts[1].clone().add( nextVerts[0] ).multiplyScalar( 0.5
);
    const vBottomPos = lastVerts[5].clone().add( nextVerts[2] ).multiplyScalar(
0.5 );

    lastVerts[1].copy( vTopPos );
    lastVerts[4].copy( vTopPos );
    lastVerts[5].copy( vBottomPos );
    nextVerts[0].copy( vTopPos );
    nextVerts[2].copy( vBottomPos );
    nextVerts[3].copy( vBottomPos );
}
Połączone czworokąty
Stałe czworokąty.

Każdy quad zawiera też promienie UV, które są generowane w ramach następnego etapu. Niektóre pędzle mają różne wzory pociągnięć, dzięki czemu każde pociągnięcie pędzla jest inne. Można to osiągnąć za pomocą atlasowania tekstury _, gdzie każda tekstura pędzla zawiera wszystkie możliwe odmiany. Prawidłową teksturę wybiera się, modyfikując wartości UV kreski.

function updateUVsForSegment( quadVerts, quadUVs, quadLengths, useAtlas,
atlasIndex ) {
    let fYStart = 0.0;
    let fYEnd = 1.0;

    if( useAtlas ){
    const fYWidth = 1.0 / TEXTURES_IN_ATLAS;
    fYStart = fYWidth * atlasIndex;
    fYEnd = fYWidth * (atlasIndex + 1.0);
    }

    //get length of current segment
    const totalLength = quadLengths.reduce( function( total, length ){
    return total + length;
    }, 0 );

    //then, run back through the last segment and update our UVs
    let currentLength = 0.0;
    quadUVs.forEach( function( uvs, index ){
    const segmentLength = quadLengths[ index ];
    const fXStart = currentLength / totalLength;
    const fXEnd = ( currentLength + segmentLength ) / totalLength;
    currentLength += segmentLength;

    uvs[ 0 ].set( fXStart, fYStart );
    uvs[ 1 ].set( fXEnd, fYStart );
    uvs[ 2 ].set( fXStart, fYEnd );
    uvs[ 3 ].set( fXStart, fYEnd );
    uvs[ 4 ].set( fXEnd, fYStart );
    uvs[ 5 ].set( fXEnd, fYEnd );

    });

}
4 tekstury w atlasie tekstur na pędzel olejny
4 tekstury w atlasie tekstur do pędzla olejnego
W Tilt Brush
W Tilt Brush
W WebGL
W WebGL

Każdy szkic ma nieograniczoną liczbę pociągnięć, a kreski nie muszą być modyfikowane w czasie wykonywania, dlatego wstępnie obliczamy geometrię kreski z wyprzedzeniem i scalamy je w jedną siatkę. Każdy nowy typ pędzla musi być oddzielnym materiałem, ale to ogranicza liczbę wywołań.

Cały powyższy szkic jest wykonywany w ramach jednego wywołania rysowania w WebGL
Cały powyższy szkic jest wykonywany w ramach jednego wywołania rysowania w WebGL

Aby przetestować system w warunkach skrajnych, stworzyliśmy szkic, którego wypełnienie zajęło 20 minut możliwie dużą liczbą wierzchołków. Powstały szkic był nadal odtwarzany z szybkością 60 kl./s w WebGL.

Ponieważ każdy z pierwotnych wierzchołków sekwencji zawierał również czas, możemy łatwo odtworzyć dane. Ponowne obliczenie liczby pociągnięć na klatkę byłoby bardzo powolne, dlatego wstępnie przetworzyliśmy cały szkic przy wczytaniu i po prostu odkryliśmy każdy quad, gdy nadszedł odpowiedni moment.

Ukrycie czworokąta oznaczało tylko zwinięcie wierzchołków do 0,0,0 punktu. Gdy nadejdzie czas, w którym powinien zostać odsłonięty czworokąt, zmieniamy położenie wierzchołków z powrotem.

Obszarem, w którym można wprowadzić ulepszenia, jest całkowicie manipulowanie wierzchołkami w GPU za pomocą cieni. Obecna implementacja umieszcza je w pętli przez tablicę wierzchołków z bieżącej sygnatury czasowej, sprawdzając, które wierzchołki należy odsłonić, i aktualizując geometrię. Spowoduje to duże obciążenie procesora, co spowoduje, że wentylator wiruje, a ponadto nadmiernie zużywa baterię.

Wirtualne dzieło sztuki

Nagrywanie wykonawców

Uznaliśmy, że same szkice nie wystarczą. Chcieliśmy pokazać artystów w ich szkicach, malując każdy pociągnięcie pędzla.

Użyliśmy aparatów Microsoft Kinect, aby rejestrować głębokość ich ciała z kosmosu. Dzięki temu możemy pokazać ich trójwymiarowe figury w tym samym miejscu, w którym widać rysunki.

Ponieważ ciało wykonawcy zasłaniało się tak, że nie dało się zobaczyć, co jest za nim, użyliśmy podwójnych systemów Kinect, które znajdują się po przeciwnych stronach pokoju i wskazują na środku.

Oprócz informacji o głębi zdjęć zarejestrowaliśmy też informacje o kolorach, korzystając ze standardowych lustrzanek cyfrowych. Użyliśmy doskonałego oprogramowania DepthKit do kalibracji i scalenia nagrań z kamery do nagrywania głębi i z kolorowych kamer. Kinect umożliwia rejestrowanie kolorów, ale zdecydowaliśmy się używać lustrzanek cyfrowych, ponieważ mogliśmy sterować ustawieniami ekspozycji, używać wysokiej jakości obiektywów i nagrywać w wysokiej rozdzielczości.

Aby nagrać materiał, zbudowaliśmy specjalne pomieszczenie dla HTC Vive, wykonawcy i kamery. Wszystkie powierzchnie zostały pokryte materiałem absorbującym światło podczerwone, co daje nam czystszą chmurę punktów (powłoka na ścianach, gumowa mata na podłodze z żebrowanej gumy). Na wypadek, gdyby materiał ten pojawił się w nagraniach z chmury punktów, wybraliśmy czarny materiał, dzięki czemu nie będzie on tak rozpraszający jak biały.

Wykonawca nagrywający

Uzyskane w ten sposób nagrania wideo dały nam wystarczającą ilość informacji, aby stworzyć system cząstek. W openFrameworks napisaliśmy kilka dodatkowych narzędzi, które pozwalają jeszcze bardziej oczyścić materiał, a w szczególności usunąć podłogi, ściany i sufity.

Wszystkie 4 kanały podczas nagranej sesji wideo (2 kanały kolorów powyżej i 2 kanały głębiowe poniżej)
Wszystkie 4 kanały podczas nagranej sesji wideo (2 kanały kolorów powyżej i 2 kanały głębiowe poniżej)

Oprócz zaprezentowania wykonawców chcemy także renderować HMD i kontrolery w 3D. Było to istotne nie tylko ze względu na to, że w wyniku wyraźnego pokazania modułu HMD (soczewki HTC Vive odbijały podświetlenia od Kinecta), ale też dawały nam punkty kontaktu do debugowania danych cząstek i dopasowywania filmów do szkicu.

Wyświetlacz zamontowany na głowie, kontrolery i cząstki w układzie
Wyświetlacz zamontowany na głowie, kontrolery i cząstki w ustalonej linii

W tym celu wpisano w Tilt Brush niestandardową wtyczkę, która wyodrębniała pozycję procesora HMD i kontrolerów w każdej klatce. Ponieważ Tilt Brush działa z szybkością 90 kl./s, odbierało się mnóstwo danych, a dane wejściowe szkicu przekraczały 20 MB nieskompresowanych. Wykorzystaliśmy tę metodę również do rejestrowania zdarzeń, które nie są rejestrowane w typowym pliku zapisu Tilt Brush, np. gdy wykonawca wybierze opcję na panelu narzędzi i pozycję widżetu lustrzanego.

Jednym z największych wyzwań podczas przetwarzania 4 TB zebranych danych było porównanie wszystkich źródeł danych wizualnych i danych. Każdy film z lustrzanki cyfrowej musi być wyrównany z odpowiednim aparatem Kinect, tak by piksele były wyrównane względem przestrzeni i czasu. Nagrania z tych 2 uchwytów na kamerze musiały być ze sobą wyrównane, aby stworzyć jednego wykonawcę. Potem musieliśmy dopasować naszego wykonawcę 3D dane z rysunku. Uff... Opracowaliśmy narzędzia działające w przeglądarce, które ułatwią Ci wykonanie większości tych zadań. Możesz je wypróbować tutaj.

Wykonawcy nagrywający muzykę

Po wyrównaniu danych wykorzystaliśmy kilka skryptów napisanych w NodeJS, aby je przetworzyć i wygenerować plik wideo oraz serię plików JSON. Wszystkie pliki zostały przycięte i zsynchronizowane. Aby zmniejszyć rozmiar pliku, podjęliśmy pewne działania. Po pierwsze, zmniejszyliśmy dokładność każdej liczby zmiennoprzecinkowej do maksymalnie 3 z dokładnością po przecinku. Po drugie, zmniejszamy liczbę punktów o jedną trzecią do 30 kl./s i interpolowaliśmy pozycje po stronie klienta. Na koniec zserializowaliśmy dane, więc zamiast używać zwykłego formatu JSON z parami klucz-wartość, tworzona jest kolejność wartości dla pozycji i rotacji HMD oraz kontrolerów. Dzięki temu rozmiar pliku zmniejszył się do zaledwie 3 MB, który był możliwy do przesłania przez kabel.

Wykonawcy nagrywający

Film jest wyświetlany jako element wideo HTML5, który jest odczytywany przez teksturę WebView w celu przekształcenia filmu w cząstki, dlatego sam film musiał być odtwarzany w tle. Program do cieniowania przekształca kolory obrazów w głębi i na pozycje w przestrzeni 3D. James George podaje świetny przykład tego, jak można wykorzystać materiał prosto z DepthKit.

W systemie iOS obowiązują ograniczenia dotyczące odtwarzania filmów bezpośrednio wyświetlanych w treści strony. Zakładamy, że nie pozwalamy na to, żeby użytkownicy nie byli kierowani do automatycznie odtwarzanych reklam wideo w internecie. Użyliśmy metody podobnej do innych metod obejścia tego problemu dostępnych w internecie, która polega na skopiowaniu klatki filmu do obszaru roboczego i ręcznej aktualizacji czasu przewijania filmu co 1/30 sekundy.

videoElement.addEventListener( 'timeupdate', function(){
    videoCanvas.paintFrame( videoElement );
});

function loopCanvas(){

    if( videoElement.readyState === videoElement.HAVE\_ENOUGH\_DATA ){

    const time = Date.now();
    const elapsed = ( time - lastTime ) / 1000;

    if( videoState.playing && elapsed >= ( 1 / 30 ) ){
        videoElement.currentTime = videoElement.currentTime + elapsed;
        lastTime = time;
    }

    }

}

frameLoop.add( loopCanvas );

Nasze podejście spowodowało niefortunne uboczne zmniejszenie liczby klatek w iOS, ponieważ kopiowanie bufora piksela z filmu do przestrzeni roboczej pochłania dużo procesora. Aby obejść ten problem, udostępnialiśmy mniejsze wersje tych samych filmów, które pozwalają uzyskać co najmniej 30 klatek na sekundę na iPhonie 6.

Podsumowanie

Zgodnie z powszechną zasadą w dziedzinie tworzenia oprogramowania VR w 2016 r. nie musimy skupiać się na prostocie geometrii i cieniowania, aby na takich procesorach HMD można było korzystać z szybkością 90 kl./s. Okazało się, że idealnie nadaje się to do testowania wersji demonstracyjnych WebGL, ponieważ techniki używane w mapie Tilt Brush bardzo dobrze sprawdzają się w WebGL.

Chociaż przeglądarki wyświetlające złożone siatki 3D same w sobie nie są ciekawe, był to dowód na to, że zapylanie powietrza w rzeczywistości wirtualnej i internetu jest całkowicie możliwe.