WebVR의 댄스 톤

Google 데이터 아트팀에서 모니커와 저에게 WebVR에서 도입된 가능성을 함께 탐색해 보자고 제안했을 때 매우 기뻤습니다. 오랜 세월 동안 이 팀의 작업을 지켜봤으며, 이들의 프로젝트는 항상 저에게 깊은 인상을 남겼습니다. 이 협업으로 LCD Soundsystem과 팬들이 함께하는 끊임없이 변화하는 VR 댄스 환경인 Dance Tonite가 탄생했습니다. 그 방법을 소개해 드리겠습니다.

기본 개념

먼저 브라우저를 사용하여 웹사이트를 방문하여 VR에 접속할 수 있는 개방형 표준인 WebVR을 사용하여 일련의 프로토타입을 개발했습니다. 목표는 모든 사용자가 어떤 기기를 사용하든 VR 환경을 더 쉽게 이용할 수 있도록 하는 것입니다.

Google은 이러한 의견을 적극적으로 수렴하고 있습니다. Google의 Daydream View, Cardboard, 삼성의 Gear VR과 같이 휴대전화에서 작동하는 VR 헤드셋부터 HTC VIVE, Oculus Rift와 같이 가상 환경에서 사용자의 실제 움직임을 반영하는 룸 스케일 시스템에 이르기까지 모든 유형의 VR에서 작동해야 합니다. 무엇보다도 VR 기기를 소유하지 않은 모든 사용자에게도 작동하는 제품을 만드는 것이 웹 정신에 부합한다고 생각했습니다.

1. DIY 모션 캡처

사용자를 창의적으로 참여시키고자 VR을 통한 참여와 자기 표현의 가능성을 살펴보기 시작했습니다. VR에서 얼마나 세밀하게 움직이고 둘러볼 수 있는지, 얼마나 정확한지 놀랐습니다. 이를 바탕으로 아이디어를 얻었습니다. 사용자가 무언가를 보거나 만들도록 하는 대신 사용자의 움직임을 기록해 보세요.

Dance Tonite에서 자신의 모습을 녹화하는 사람 뒤에 있는 화면에는 헤드셋에 표시되는 내용이 표시됩니다.

댄스 중에 VR 고글과 컨트롤러의 위치를 기록하는 프로토타입을 제작했습니다. 기록된 위치를 추상적인 도형으로 대체하고 그 결과에 놀랐습니다. 결과는 매우 인간적이었고 개성이 넘쳤습니다. WebVR을 사용하여 집에서 저렴하게 모션 캡처를 할 수 있다는 것을 금방 깨달았습니다.

WebVR을 사용하면 개발자가 VRPose 객체를 통해 사용자의 머리 위치 및 방향에 액세스할 수 있습니다. 이 값은 VR 하드웨어가 프레임마다 업데이트하므로 코드가 올바른 관점에서 새 프레임을 렌더링할 수 있습니다. WebVR을 사용한 GamePad API를 통해 GamepadPose 객체를 통해 사용자 컨트롤러의 위치/방향에 액세스할 수도 있습니다. 프레임마다 모든 위치 및 방향 값을 저장하기만 하면 사용자의 움직임이 '기록'됩니다.

2. 미니멀리즘 및 의상

오늘날의 룸 스케일 VR 장비를 사용하면 사용자의 머리와 양손 등 사용자 신체의 세 지점을 추적할 수 있습니다. 'Dance Tonite'에서는 공간에서 이 세 지점의 움직임에 담긴 인간적인 면모에 초점을 맞추고자 했습니다. 이를 위해 움직임에 집중하기 위해 미학을 최대한 간소화했습니다. Google은 인간의 두뇌를 활용하는 아이디어에 관심을 가졌습니다.

스웨덴 심리학자 Gunnar Johansson의 연구를 보여주는 이 동영상은 최대한 간소화하는 방법을 모색할 때 참고한 예시 중 하나입니다. 둥둥 떠 있는 흰색 점이 움직일 때 어떻게 몸으로 즉시 인지되는 것을 보여줍니다.

시각적으로는 1970년 마가레테 헤이스팅스가 오스카 슐레머의 삼중 발레를 재연한 녹화 영상에 나온 색색의 방과 기하학적 의상에서 영감을 얻었습니다.

슐레머가 추상적인 기하학적 의상을 선택한 이유는 댄서들의 움직임을 인형과 마리오네트의 움직임으로 제한하기 위해서였지만, 저희는 Dance Tonite에서 그와는 반대의 목표를 추구했습니다.

결국 회전으로 전달하는 정보의 양에 따라 도형을 선택했습니다. 구체는 회전하는 방식에 관계없이 동일하게 보이지만 원뿔은 실제로 보고 있는 방향을 가리키며 앞면과 뒷면이 다르게 보입니다.

3. 움직임을 위한 루프 페달

함께 춤추고 움직이는 많은 인원의 모습을 보여주고 싶었습니다. VR 기기가 충분히 보급되지 않았기 때문에 실시간으로 테스트하는 것은 불가능합니다. 하지만 여전히 움직임을 통해 서로에게 반응하는 사람들의 그룹을 만들고 싶었습니다. 노먼 맥클라렌이 1964년 제작한 동영상 'Canon'에서 반복 공연을 펼쳤습니다.

McClaren의 공연은 매 루프 후에 서로 상호작용하기 시작하는 고도로 안무화된 일련의 움직임을 특징으로 합니다. 뮤지션이 여러 라이브 음악을 겹쳐서 연주하는 음악의 루프 페달처럼 사용자가 더 느슨한 버전의 공연을 자유롭게 즉흥적으로 연주할 수 있는 환경을 구축할 수 있을지 궁금했습니다.

4. 서로 연결된 방

서로 연결된 방

많은 음악과 마찬가지로 LCD Soundsystem의 트랙은 정확한 타이밍의 마디를 사용하여 구성됩니다. 프로젝트에 소개된 이들의 트랙인 Tonite는 길이가 정확히 8초입니다. 트랙의 8초 루프마다 사용자가 공연을 하도록 했습니다. 이러한 소절의 리듬은 변하지 않지만 음악 콘텐츠에는 변함이 있습니다. 노래가 진행되는 동안 연주자가 다양한 방식으로 반응할 수 있는 다양한 악기와 보컬이 등장합니다. 이러한 각 측정값은 방으로 표현되므로 사람들이 자신에게 맞는 공연을 할 수 있습니다

성능 최적화: 프레임 드롭하지 않음

단일 코드베이스에서 실행되며 각 기기 또는 플랫폼에 최적화된 성능을 제공하는 멀티플랫폼 VR 환경을 만드는 것은 쉬운 일이 아닙니다.

VR을 사용할 때 가장 메스꺼운 증상 중 하나는 프레임 속도가 움직임을 따라잡지 못한다는 것입니다. 머리를 돌리지만 눈에 보이는 시각적 이미지가 내이에서 느끼는 움직임과 일치하지 않으면 즉시 속이 울렁거립니다. 따라서 큰 프레임 속도 지연을 방지해야 했습니다. 다음은 구현된 몇 가지 최적화입니다.

1. 인스턴스화된 버퍼 도형

전체 프로젝트에서는 소수의 3D 객체만 사용하기 때문에 인스턴스 버퍼 도형을 사용하여 성능을 대폭 향상할 수 있었습니다. 기본적으로 객체를 GPU에 한 번 업로드하고 단일 그리기 호출에서 원하는 만큼 객체의 '인스턴스'를 그릴 수 있습니다. Dance Tonite에는 3가지 객체 (원뿔, 원통, 구멍이 있는 방)만 있지만 이러한 객체의 사본은 수백 개 있을 수 있습니다. 인스턴스 버퍼 도형은 ThreeJS의 일부이지만, 인스턴스 버퍼 도형을 훨씬 쉽게 사용할 수 있는 THREE.InstanceMesh를 구현하는 Dusan Bosnjak의 실험용 진행 중인 포크를 사용했습니다.

2. 가비지 컬렉터 방지

다른 많은 스크립트 언어와 마찬가지로 JavaScript는 할당되었지만 더 이상 사용되지 않는 객체를 찾아 메모리를 자동으로 해제합니다. 이 프로세스를 가비지 컬렉션이라고 합니다.

개발자는 이 시점을 제어할 수 없습니다. 가비지 컬렉터가 언제든지 문 앞에 표시되어 가비지를 비우기 시작할 수 있으며, 이로 인해 달콤한 시간을 보내는 동안 프레임이 떨어질 수 있습니다.

이 문제를 해결하려면 객체를 재활용하여 최대한 적은 양의 가비지를 생성하면 됩니다. 계산할 때마다 새 벡터 객체를 만드는 대신 재사용할 수 있도록 스크래치 객체를 표시했습니다. 이러한 참조는 Google의 범위 밖으로 이동함으로써 계속 보관되므로 삭제 대상으로 표시되지 않았습니다.

예를 들어 다음은 사용자의 머리와 손의 위치 행렬을 각 프레임을 저장하는 위치/회전 값 배열로 변환하는 코드입니다. SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE를 재사용하면 함수가 호출될 때마다 새 객체를 만들 때 발생하는 메모리 할당 및 가비지 컬렉션이 방지됩니다.

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. 모션 및 프로그레시브 재생 직렬화

VR에서 사용자의 움직임을 캡처하려면 헤드셋과 컨트롤러의 위치와 회전을 직렬화하고 이 데이터를 서버에 업로드해야 했습니다. 먼저 모든 프레임의 전체 변환 행렬을 캡처했습니다. 이 방법은 성능이 우수했지만, 16개의 숫자를 각각 3개의 위치에 곱하고 초당 90프레임으로 계산하면 파일이 매우 커져 데이터를 업로드하고 다운로드하는 동안 오래 기다려야 했습니다. 변환 행렬에서 위치 및 회전 데이터만 추출하여 이러한 값을 16에서 7로 줄일 수 있었습니다.

웹 방문자는 정확히 어떤 내용을 기대할지 모르고 링크를 클릭하는 경우가 많으므로 시각적 콘텐츠를 빠르게 표시해야 합니다. 그렇지 않으면 몇 초 이내에 사이트를 떠납니다.

따라서 프로젝트를 최대한 빨리 재생할 수 있도록 했습니다. 처음에는 JSON을 이동 데이터를 로드하는 형식으로 사용했습니다. 문제는 전체 JSON 파일을 파싱하기 전에 로드해야 한다는 점입니다. 별로 진보적이지 않다.

Dance Tonite와 같은 프로젝트를 최대 프레임 속도로 계속 표시하려면 브라우저에서 JavaScript 계산을 실행할 수 있는 시간이 프레임마다 조금씩 있습니다. 시간이 너무 오래 걸리면 애니메이션이 버벅거리기 시작합니다. 처음에는 이러한 대용량 JSON 파일이 브라우저에서 디코딩될 때 끊김 현상이 발생했습니다.

NDJSON 또는 줄바꿈으로 구분된 JSON이라는 편리한 스트리밍 데이터 형식을 발견했습니다. 이 방법을 사용하면 각 줄에 유효한 JSON 문자열을 여러 개 포함하여 파일을 만들 수 있습니다. 이렇게 하면 파일이 로드되는 동안 파싱할 수 있으므로 완전히 로드되기 전에 성능을 표시할 수 있습니다.

다음은 녹음 파일의 한 섹션이 표시되는 방식입니다.

{"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, ... ]
...

NDJSON을 사용하면 성능의 개별 프레임 데이터 표현을 문자열로 유지할 수 있습니다. 필요한 시간에 도달할 때까지 기다린 후 위치 데이터로 디코딩하여 시간이 지남에 따라 필요한 처리를 분산할 수 있습니다.

4. 모션 보간

동시에 30~60개의 공연을 상영하고 싶었기 때문에 이미 보유한 데이터 속도를 훨씬 더 낮춰야 했습니다. 데이터 아트팀은 Tilt Brush를 사용하여 VR에서 그림을 그리는 아티스트의 녹화 파일을 재생하는 가상 아트 세션 프로젝트에서 동일한 문제를 해결했습니다. 따라서 프레임 속도가 더 낮은 중간 버전의 사용자 데이터를 만들고 이를 재생하는 동안 프레임 간에 보간하여 이 문제를 해결했습니다. 놀랍게도 15FPS로 재생되는 보간 녹화본과 원래 90FPS 녹화본 사이에는 거의 차이가 없었습니다.

직접 확인하려면 ?dataRate= 쿼리 문자열을 사용하여 Dance Tonite가 다양한 속도로 데이터를 재생하도록 강제할 수 있습니다. 이를 사용하여 초당 90프레임, 초당 45프레임 또는 초당 15프레임으로 녹화된 모션을 비교할 수 있습니다.

위치의 경우 키프레임 간의 시간적 근접도 (비율)에 따라 이전 키프레임과 다음 키프레임 간에 선형 보간을 실행합니다.

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
    );

방향의 경우 키프레임 간에 구형 선형 보간 유형 (slerp)을 적용합니다. 방향은 Quaternions로 저장됩니다.

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

5. 음악에 맞춰 동작 동기화

녹화된 애니메이션 중 재생할 프레임을 알기 위해서는 음악의 현재 시간을 밀리초 단위로 알아야 합니다. HTML 오디오 요소는 소리를 점진적으로 로드하고 재생하는 데 적합하지만 제공하는 시간 속성은 브라우저의 프레임 루프와 동기화되어 변경되지 않습니다. 항상 약간 틀립니다. 밀리초의 일부분만큼 일찍 시작되는 경우도 있고, 조금 늦게 시작되는 경우도 있습니다.

이로 인해 아름다운 댄스 녹화 콘텐츠에 끊김 현상이 발생할 수 있으며, YouTube는 이를 절대적으로 피하고자 합니다. 이 문제를 해결하기 위해 JavaScript에 자체 타이머를 구현했습니다. 이렇게 하면 프레임 간에 변경되는 시간이 정확히 마지막 프레임 이후 경과한 시간과 동일하다고 확신할 수 있습니다. 타이머가 음악과 동기화되지 않을 때마다 다시 동기화됩니다.

6. 삭제 및 안개

모든 이야기에는 좋은 결말이 필요하며, YouTube는 YouTube 환경의 끝까지 도달한 사용자에게 놀라운 경험을 제공하고자 했습니다. 마지막 방을 나서면 원뿔과 원통으로 이루어진 조용한 풍경이 펼쳐집니다. '이제 끝인가요?'라고 궁금해집니다. 필드 안으로 더 들어가면 갑자기 음악의 음조에 따라 다양한 원뿔과 원통 그룹이 댄서가 됩니다. 갑자기 거대한 파티가 벌어집니다. 그러고 나서 음악이 갑자기 멈추면서 모든 것이 바닥으로 떨어집니다.

시청자에게는 만족스러웠지만 해결해야 할 몇 가지 성능 문제가 발생했습니다. 룸 스케일 VR 기기와 고급 게임 장비는 새로운 엔딩에 필요한 40여 개의 추가 공연을 완벽하게 실행했습니다. 하지만 특정 휴대기기의 프레임 속도는 절반으로 줄었습니다.

이를 방지하기 위해 안개를 도입했습니다. 일정 거리가 지나면 모든 것이 서서히 검은색으로 변합니다. 표시되지 않는 항목은 계산하거나 그리지 않아도 되므로 표시되지 않는 방에서 실적을 삭제하여 CPU와 GPU 모두의 작업을 절약할 수 있습니다. 하지만 적절한 거리를 어떻게 결정할 수 있을까요?

일부 기기는 개발자가 던지는 모든 동작을 처리할 수 있고, 다른 기기는 좀 더 좁아집니다. 우리는 슬라이딩 체중계를 구현하기로 했습니다. 초당 프레임 수를 지속적으로 측정하여 안개 거리를 적절하게 조정할 수 있습니다. 프레임 속도가 원활하게 실행되는 한 안개를 제거하여 더 많은 렌더링 작업을 처리합니다. 프레임 속도가 충분히 원활하지 않으면 안개를 더 가까이 가져와 어두운 곳에서 렌더링 성능을 건너뜁니다.

// 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;
}

누구나 이용할 수 있는 VR: 웹용 VR 빌드

멀티플랫폼 비대칭 환경을 설계하고 개발한다는 것은 기기에 따라 각 사용자의 요구사항을 고려해야 한다는 의미입니다. 그리고 디자인을 결정할 때마다 다른 사용자에게 미칠 수 있는 영향을 파악해야 했습니다. VR에서 보는 것이 VR 없이 보는 것만큼이나 흥미롭게 느껴지도록 하려면 어떻게 해야 하나요?

1. 노란색 구체

따라서 룸 스케일 VR 사용자는 공연을 진행하지만 모바일 VR 기기(예: Cardboard, Daydream View, Samsung Gear) 사용자는 어떻게 프로젝트를 경험하게 될까요? 이를 위해 환경에 노란색 구체라는 새로운 요소를 도입했습니다.

노란색 구체
노란색 구체

VR로 프로젝트를 시청하면 노란색 구체의 관점에서 시청하게 됩니다. 방에서 방으로 이동하면 댄서가 내 존재에 반응합니다. 몸짓을 하고, 주변에서 춤추고, 등 뒤에서 재미있는 동작을 하며, 부딪히지 않도록 재빨리 몸을 움직입니다. 노란색 구체는 항상 주목의 대상입니다.

이는 공연을 녹화하는 동안 노란색 구체가 음악과 동기화되어 방 중앙을 통과하며 다시 루프되기 때문입니다. 구체의 위치는 공연자가 시간의 어느 지점에 있고 루프에 남은 시간이 얼마인지 알 수 있도록 해줍니다. 이를 바탕으로 자연스럽게 공연을 꾸밀 수 있습니다.

2. 다른 관점

VR이 없는 사용자를 소외시키고 싶지 않았습니다. 특히 VR이 없는 사용자가 가장 많은 시청자층일 가능성이 높기 때문입니다. 가짜 VR 환경을 만드는 대신 화면 기반 기기에 고유한 환경을 제공하고자 했습니다. 아이소메트릭 관점에서 위에서 실적을 보여주는 아이디어가 떠올랐습니다. 이러한 관점은 컴퓨터 게임에서 오랜 역사를 가지고 있습니다. 1982년의 우주 슈팅 게임인 Zaxxon에서 처음 사용되었습니다. VR 사용자는 그 중심에 있는 반면, 아이소메트릭 관점에서는 마치 신의 시선으로 상황을 볼 수 있습니다. 인형의 집 같은 느낌을 주기 위해 모델을 약간 확대했습니다.

3. 그림자: '실제'가 될 때까지 '가짜'로 연습하기

일부 사용자가 등각 관점에서 깊이를 보는 데 어려움을 겪고 있다는 사실을 발견했습니다. 이 때문에 Zaxxon은 역사상 최초로 비행 물체 아래에 동적 그림자를 투사한 컴퓨터 게임 중 하나이기도 합니다.

그림자

3D로 그림자를 만드는 것은 어렵습니다. 특히 휴대전화와 같이 제약된 기기에 적합합니다. 처음에는 이를 제외하기로 하는 어려운 결정을 내려야 했지만 Three.js의 저자이자 경험 많은 데모 해커인 Mr doob에게 조언을 구한 결과, 가짜를 만드는 새로운 아이디어를 떠올렸습니다.

각 부유하는 물체가 어떻게 조명을 가리고 따라서 다양한 모양의 그림자를 드리우는지 계산하는 대신 각 물체 아래에 동일한 원형 흐리게 처리된 텍스처 이미지를 그립니다. Google의 시각화는 애초에 현실을 모방하려는 것이 아니므로 몇 가지 조정만으로 쉽게 해결할 수 있었습니다. 물체가 지면에 가까워지면 텍스처가 어두워지고 작아집니다 위로 이동하면 텍스처가 더 투명해지고 커집니다.

이를 만들기 위해 알파 투명도가 없는 부드러운 흰색에서 검은색으로의 그라데이션이 적용된 이 텍스처를 사용했습니다. 머티리얼을 투명으로 설정하고 감산 블렌딩을 사용합니다. 이렇게 하면 겹쳐질 때 잘 어울립니다.

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. 지원

VR을 사용하지 않은 방문자도 공연자의 머리를 클릭하면 댄서의 관점에서 사물을 볼 수 있습니다. 이 각도에서 보면 많은 세부사항을 명확히 알 수 있습니다. 댄서들이 리듬을 맞추기 위해 서로를 재빨리 힐끗 쳐다봅니다. 구체가 방에 들어오자 불안한 듯 그 방향을 바라봅니다. 시청자는 이러한 움직임에 영향을 줄 수 없지만 놀라울 정도로 몰입감을 전달합니다. 다시 말하지만, 우리는 사용자에게 단순한 마우스 제어 인조 VR 버전을 제공하는 것보다 이 방법을 선호합니다.

5. 녹음 파일 공유

20층의 공연자들이 서로 리액션하는 모습을 정교하게 연출한 영상을 선보이면 얼마나 자랑스러울지 잘 알고 있습니다. 사용자들이 친구들에게 보여 주고 싶어 할 거라고 알고 있었습니다. 하지만 이 장면을 찍은 스틸 이미지로는 충분히 전달되지 않습니다. 대신 사용자들이 자신의 공연 동영상을 공유할 수 있도록 했습니다. 사실 GIF는 안 되나요? 애니메이션은 평면 음영으로 처리되어 형식의 제한된 색상 팔레트에 적합합니다.

녹음 파일 공유

브라우저 내에서 애니메이션 GIF를 인코딩할 수 있는 JavaScript 라이브러리인 GIF.js를 사용했습니다. 프레임 인코딩을 백그라운드에서 별도의 프로세스로 실행할 수 있는 웹 워커로 오프로드하므로 여러 프로세서를 나란히 실행하여 활용할 수 있습니다.

안타깝게도 애니메이션에 필요한 프레임 수가 많아 인코딩 프로세스가 여전히 너무 느렸습니다. GIF는 제한된 색상 팔레트를 사용하여 작은 파일을 만들 수 있습니다. 각 픽셀에 가장 가까운 색상을 찾는 데 가장 많은 시간이 소요되는 것으로 나타났습니다. 작은 단축키를 해킹하여 이 프로세스를 10배나 최적화할 수 있었습니다. 픽셀의 색상이 마지막 색상과 동일하면 이전과 동일한 팔레트 색상을 사용합니다.

이제 인코딩 속도가 빨라졌지만 결과 GIF 파일의 크기가 너무 컸습니다. GIF 형식을 사용하면 삭제 메서드를 정의하여 마지막 프레임 위에 각 프레임이 표시되는 방식을 나타낼 수 있습니다. 파일 크기를 줄이기 위해 프레임마다 각 픽셀을 업데이트하는 대신 변경된 픽셀만 업데이트합니다. 인코딩 프로세스가 다시 느려졌지만 파일 크기는 크게 줄었습니다.

6. 탄탄한 기반: Google Cloud 및 Firebase

'사용자 제작 콘텐츠' 사이트의 백엔드는 종종 복잡하고 취약할 수 있지만 Google Cloud와 Firebase 덕분에 간단하고 강력한 시스템을 만들 수 있었습니다. 아티스트가 시스템에 새로운 댄스를 업로드하면 Firebase 인증으로 익명으로 인증됩니다. Firebase용 Cloud Storage를 사용하여 녹음 파일을 임시 스페이스에 업로드할 권한이 부여됩니다. 업로드가 완료되면 클라이언트 머신은 Firebase 토큰을 사용하여 Firebase용 Cloud Functions HTTP 트리거를 호출합니다. 그러면 제출을 확인하고, 데이터베이스 레코드를 만들고, 녹화 파일을 Google Cloud Storage의 공개 디렉터리로 이동하는 서버 프로세스가 트리거됩니다.

단단한 지반

모든 공개 콘텐츠는 Cloud Storage 버킷의 일련의 플랫 파일에 저장됩니다. 즉, 전 세계에서 Google 데이터에 빠르게 액세스할 수 있으며 트래픽 부하가 데이터 가용성에 영향을 미치는 것을 전혀 걱정할 필요가 없습니다.

우리는 Firebase 실시간 데이터베이스와 Cloud 함수 엔드포인트를 사용하여 VR로 새로 제출한 각 항목을 모두 시청하고 어느 기기에서나 새 재생목록을 게시할 수 있는 간단한 관리/선별 도구를 빌드했습니다.

7. 서비스 워커

서비스 워커는 웹사이트 애셋의 캐싱을 관리하는 데 도움이 되는 최근의 혁신 기술입니다. Google의 경우 서비스 워커가 재방문자를 위해 콘텐츠를 번개처럼 빠르게 로드하고 사이트가 오프라인으로 작동하도록 허용합니다. 이는 많은 방문자가 다양한 품질의 모바일 연결을 사용하기 때문에 중요한 기능입니다.

대부분의 힘든 작업을 대신 처리하는 편리한 webpack 플러그인 덕분에 프로젝트에 서비스 워커를 쉽게 추가할 수 있었습니다. 아래 구성에서는 모든 정적 파일을 자동으로 캐시하는 서비스 워커를 생성합니다. 재생목록은 항상 업데이트되므로 가능한 경우 네트워크에서 최신 재생목록 파일을 가져옵니다. 모든 녹음 JSON 파일은 변경되지 않으므로 가능한 경우 캐시에서 가져와야 합니다.

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',
        },
        },
    }],
    })
);

현재 플러그인은 음악 파일과 같이 점진적으로 로드되는 미디어 애셋을 처리하지 않으므로 브라우저가 파일을 최대 1년 동안 캐시하도록 이러한 파일의 Cloud Storage Cache-Control 헤더를 public, max-age=31536000로 설정하여 이 문제를 해결했습니다.

결론

아티스트가 이 환경을 어떻게 활용하고 모션을 사용한 창의적 표현의 도구로 사용할지 기대됩니다. 모든 코드를 오픈소스로 출시했으며 https://github.com/puckey/dance-tonite에서 확인할 수 있습니다. VR, 특히 WebVR의 초기 단계에서 이 새로운 매체가 어떤 창의적이고 예상치 못한 방향으로 나아갈지 기대됩니다. 댄스 앤 온