가상 아트 세션

아트 세션 세부정보

요약

6명의 아티스트가 초대되어 VR에서 그림을 그리고, 디자인하고, 조각을 만들었습니다. 다음은 세션을 기록하고 데이터를 변환하여 웹브라우저로 실시간으로 표시하는 프로세스입니다.

https://g.co/VirtualArtSessions

살아 있음에 감사할 수 있는 날이에요. 가상 현실이 소비자 제품으로 도입되면서 아직 탐색되지 않은 새로운 가능성이 발견되고 있습니다. HTC Vive에서 사용할 수 있는 Google 제품인 Tilt Brush를 사용하면 3차원 공간에서 그릴 수 있습니다. Tilt Brush를 처음 사용해 보니 모션 추적 컨트롤러로 그리는 느낌과 '초능력이 있는 방'에 있는 듯한 느낌이 오래 남았습니다. 주변의 빈 공간에 그릴 수 있는 것과 같은 경험은 정말 없습니다.

가상 아트 작품

Google의 데이터 아트팀은 Tilt Brush가 아직 작동하지 않는 웹에서 VR 헤드셋이 없는 사용자에게 이 환경을 보여주는 과제를 안았습니다. 이를 위해 조각가, 일러스트레이터, 컨셉 디자이너, 패션 아티스트, 설치 예술가, 거리 예술가를 초대하여 이 새로운 매체에서 자신만의 스타일로 아트워크를 제작하도록 했습니다.

가상 현실에서 그림 녹화

Unity에 내장된 Tilt Brush 소프트웨어는 룸 스케일 VR을 사용하여 머리 위치 (헤드 마운트 디스플레이 또는 HMD)와 각 손의 컨트롤러를 추적하는 데스크톱 애플리케이션입니다. Tilt Brush에서 만든 아트워크는 기본적으로 .tilt 파일로 내보내집니다. 이 환경을 웹에 제공하려면 아트워크 데이터 외의 데이터가 필요하다는 것을 깨달았습니다. Tilt Brush팀과 긴밀히 협력하여 Tilt Brush를 수정하여 1초에 90번씩 아티스트의 머리와 손 위치와 함께 실행취소/삭제 작업을 내보내도록 했습니다.

Tilt Brush를 사용해 그리면 컨트롤러의 위치와 각도를 감지하여 시간 경과에 따른 여러 점을 '획'으로 변환합니다. 여기에서 예를 확인할 수 있습니다. 이러한 획을 추출하여 원시 JSON으로 출력하는 플러그인을 작성했습니다.

    {
      "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
      ]
    }

위 스니펫은 스케치 JSON 형식의 형식을 간략히 보여줍니다.

여기서 각 획은 'STROKE' 유형의 작업으로 저장됩니다. 획 작업 외에도 아티스트가 실수를 하고 스케치 중간에 마음을 바꾸는 모습을 보여주고 싶었기 때문에 전체 획을 지우거나 실행취소하는 작업인 '삭제' 작업을 저장하는 것이 중요했습니다.

각 획의 기본 정보가 저장되므로 브러시 유형, 브러시 크기, 색상 RGB가 모두 수집됩니다.

마지막으로 획의 각 정점이 저장되며 여기에는 위치, 각도, 시간, 컨트롤러의 트리거 압력 강도 (각 지점 내에 p로 표시됨)가 포함됩니다.

회전은 4개 구성요소 쿼터니언입니다. 이는 나중에 짐벌 잠금을 방지하기 위해 획을 렌더링할 때 중요합니다.

WebGL로 스케치 재생

웹브라우저에 스케치를 표시하기 위해 THREE.js를 사용하고 Tilt Brush가 내부에서 실행하는 작업을 모방한 도형 생성 코드를 작성했습니다.

Tilt Brush는 사용자의 손 움직임을 기반으로 실시간으로 삼각형 스트립을 생성하지만 웹에 표시될 때면 전체 스케치가 이미 '완료'됩니다. 이렇게 하면 대부분의 실시간 계산을 우회하고 로드 시 도형을 굽을 수 있습니다.

WebGL 스케치

획의 각 정점 쌍은 방향 벡터 (위 그림과 같이 각 점을 연결하는 파란색 선, 아래 코드 스니펫의 moveVector)를 생성합니다. 각 지점에는 컨트롤러의 현재 각도를 나타내는 쿼터니언인 방향도 포함됩니다. 삼각형 스트립을 만들기 위해 이러한 각 지점을 반복하여 방향 및 컨트롤러 방향에 수직인 법선을 생성합니다.

각 획의 삼각형 스트립을 계산하는 프로세스는 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);
}

획 방향과 방향을 단독으로 결합하면 수학적으로 모호한 결과가 반환됩니다. 여러 개의 법선이 파생될 수 있으며, 이는 흔히 도형에 '꼬임'을 야기합니다.

획의 점을 반복할 때 '선호되는 오른쪽' 벡터를 유지하고 이를 computeSurfaceFrame() 함수에 전달합니다. 이 함수는 획의 방향 (마지막 지점에서 현재 지점으로)과 컨트롤러의 방향 (쿼터니언)을 기반으로 쿼드 스트립에서 쿼드를 파생할 수 있는 법선을 제공합니다. 더 중요한 것은 다음 계산 세트에 사용할 새 '선호되는 오른쪽' 벡터도 반환한다는 것입니다.

획

각 획의 제어 점을 기반으로 사각형을 생성한 후 한 사각형에서 다음 사각형으로 모서리를 보간하여 사각형을 융합합니다.

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 );
}
융합된 쿼드
융합된 쿼드.

각 쿼드에는 다음 단계에서 생성되는 UV도 포함됩니다. 일부 브러시에는 다양한 획 패턴이 포함되어 있어 모든 획이 다른 유형의 페인트 브러시 획처럼 느껴지는 인상을 줍니다. 이는 각 브러시 텍스처에 가능한 모든 변형이 포함된 _텍스처 아틀라스_를 사용하여 실행됩니다. 획의 UV 값을 수정하여 올바른 텍스처를 선택합니다.

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개의 텍스처
유채 브러시용 텍스처 아틀라스의 텍스처 4개
Tilt Brush에서
Tilt Brush
WebGL
WebGL

각 스케치에는 무제한의 획이 있으며 런타임에 획을 수정할 필요가 없으므로 획 도형을 미리 계산하여 하나의 메시에 병합합니다. 새 브러시 유형마다 자체 재료가 있어야 하지만 그래도 그리기 호출 수가 브러시당 1회로 줄어듭니다.

위의 전체 스케치는 WebGL의 한 번의 그리기 호출에서 실행됩니다.
위의 전체 스케치는 WebGL의 한 번의 그리기 호출에서 실행됩니다.

시스템을 스트레스 테스트하기 위해 최대한 많은 정점으로 공간을 채우는 데 20분이 걸리는 스케치를 만들었습니다. 결과 스케치는 WebGL에서 여전히 60fps로 재생되었습니다.

획의 각 원래 정점에는 시간도 포함되어 있으므로 데이터를 쉽게 재생할 수 있습니다. 프레임별로 획을 다시 계산하는 것은 매우 느리므로 대신 로드 시 전체 스케치를 사전 계산하고 표시할 때 각 쿼드를 표시하기만 했습니다.

쿼드를 숨기는 것은 꼭짓점을 0,0,0 지점으로 접는 것을 의미했습니다. 시간이 정사각형이 표시되어야 하는 지점에 도달하면 정점을 제자리로 다시 배치합니다.

개선이 필요한 영역은 GPU에서 셰이더를 사용하여 완전히 정점을 조작하는 것입니다. 현재 구현은 현재 타임스탬프의 정점 배열을 반복하여 표시해야 하는 정점을 확인한 다음 도형을 업데이트하여 정점을 배치합니다. 이로 인해 CPU에 많은 부하가 발생하여 팬이 회전하고 배터리 수명이 낭비됩니다.

가상 아트 작품

아티스트 녹음

스케치만으로는 충분하지 않다고 생각했습니다. 스케치 내부에서 각 붓 터치를 그리는 아티스트를 보여주고 싶었습니다.

아티스트를 캡처하기 위해 Microsoft Kinect 카메라를 사용하여 공간에서 아티스트 신체의 깊이 데이터를 기록했습니다. 이렇게 하면 그림이 표시되는 동일한 공간에 입체 도형을 표시할 수 있습니다.

아티스트의 몸이 가려져 뒤에 있는 것을 볼 수 없으므로 방의 양쪽에 Kinect 시스템을 설치하여 중앙을 향하도록 했습니다.

깊이 정보 외에도 표준 DSLR 카메라로 장면의 색상 정보도 캡처했습니다. 우수한 DepthKit 소프트웨어를 사용하여 깊이 카메라와 컬러 카메라의 영상을 보정하고 병합했습니다. Kinect는 색상을 녹화할 수 있지만 노출 설정을 제어하고, 아름다운 고급 렌즈를 사용하고, 고화질로 녹화할 수 있으므로 DSLR을 사용하기로 했습니다.

영상을 녹화하기 위해 HTC Vive, 아티스트, 카메라를 수용할 수 있는 특별한 공간을 마련했습니다. 모든 표면은 더 선명한 포인트 클라우드를 얻기 위해 적외선을 흡수하는 소재로 덮었습니다 (벽면에는 두베틴, 바닥에는 리브가 있는 고무 매트). 물체가 포인트 클라우드 영상에 표시되는 경우 흰색 물체만큼 눈에 띄지 않도록 검은색 물체를 선택했습니다.

음반 아티스트

이렇게 하여 얻은 동영상 녹화물은 입자 시스템을 투사하기에 충분한 정보를 제공했습니다. 특히 바닥, 벽, 천장을 삭제하여 영상을 더 정리할 수 있는 도구를 openFrameworks에 추가로 작성했습니다.

녹화된 동영상 세션의 4개 채널 (위의 2개 색상 채널과 아래의 2개 깊이 채널)
녹화된 동영상 세션의 4개 채널 (위의 2개 색상 채널과 아래의 2개 깊이 채널)

아티스트를 보여주는 것 외에도 HMD와 컨트롤러도 3D로 렌더링하고 싶었습니다. 이는 최종 출력에서 HMD를 명확하게 표시하는 데 중요할 뿐만 아니라 (HTC Vive의 반사 렌즈가 Kinect의 IR 판독값을 왜곡함) 입자 출력을 디버그하고 동영상을 스케치와 정렬하는 데도 도움이 되었습니다.

머리 장착형 디스플레이, 컨트롤러, 입자 배열
머리 장착형 디스플레이, 컨트롤러, 입자 정렬

이를 위해 Tilt Brush에 각 프레임의 HMD 및 컨트롤러 위치를 추출하는 맞춤 플러그인을 작성했습니다. Tilt Brush는 90fps로 실행되므로 엄청난 양의 데이터가 스트리밍되고 스케치의 입력 데이터는 압축되지 않은 상태에서 20MB를 초과했습니다. 또한 이 기법을 사용하여 아티스트가 도구 패널에서 옵션을 선택할 때와 미러 위젯의 위치와 같이 일반적인 Tilt Brush 저장 파일에 기록되지 않는 이벤트를 캡처했습니다.

캡처한 4TB의 데이터를 처리할 때 가장 큰 문제 중 하나는 다양한 시각적/데이터 소스를 정렬하는 것이었습니다. DSLR 카메라의 각 동영상은 픽셀이 시간뿐만 아니라 공간에서도 정렬되도록 해당하는 Kinect와 정렬되어야 합니다. 그런 다음 두 카메라 장비의 영상을 서로 정렬하여 단일 아티스트를 형성해야 했습니다. 그런 다음 3D 아티스트를 그림에서 캡처한 데이터와 정렬해야 했습니다. 다양한 혜택이 마음에 드셨나요? 이러한 작업의 대부분을 지원하는 브라우저 기반 도구를 작성했으며 여기에서 직접 사용해 볼 수 있습니다.

음반 아티스트

데이터가 정렬되면 NodeJS로 작성된 몇 가지 스크립트를 사용하여 모든 데이터를 처리하고 동영상 파일과 일련의 JSON 파일을 모두 자르고 동기화하여 출력했습니다. 파일 크기를 줄이기 위해 다음 세 가지 작업을 수행했습니다. 먼저 각 부동 소수점 수의 정밀도를 최대 3자리로 줄였습니다. 두 번째로, 점 수를 3분의 1로 줄여 30fps로 만들고 클라이언트 측에서 위치를 보간했습니다. 마지막으로 데이터를 직렬화하여 키/값 쌍이 포함된 일반 JSON을 사용하는 대신 HMD 및 컨트롤러의 위치와 회전에 관한 값 순서가 생성됩니다. 이렇게 하여 파일 크기가 3MB에 조금 못 미치는 수준으로 줄었으며, 이는 전송에 적합한 크기였습니다.

음반 아티스트

동영상 자체는 WebGL 텍스처에 의해 읽혀 파티클이 되는 HTML5 동영상 요소로 제공되므로 동영상 자체는 배경에 숨겨져 재생되어야 했습니다. 셰이더는 깊이 이미지의 색상을 3D 공간의 위치로 변환합니다. 제임스 조지님이 DepthKit에서 바로 가져온 영상으로 할 수 있는 작업에 관한 훌륭한 예시를 공유했습니다.

iOS에는 인라인 동영상 재생에 대한 제한사항이 있습니다. 이는 사용자가 자동재생되는 웹 동영상 광고로 인해 불편을 겪지 않도록 하기 위한 조치로 추정됩니다. 동영상 프레임을 캔버스에 복사하고 1/30초마다 동영상 탐색 시간을 수동으로 업데이트하는 웹의 다른 해결 방법과 유사한 기법을 사용했습니다.

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

동영상에서 캔버스로의 픽셀 버퍼 복사는 CPU를 많이 사용하기 때문에 이 접근 방식에는 iOS 프레임 속도가 크게 낮아지는 부작용이 있습니다. 이 문제를 해결하기 위해 iPhone 6에서 최소 30fps를 허용하는 동일한 동영상의 소형 버전을 게재했습니다.

결론

2016년 현재 VR 소프트웨어 개발에 대한 일반적인 합의는 HMD에서 90fps 이상으로 실행할 수 있도록 도형과 셰이더를 간단하게 유지하는 것입니다. Tilt Brush에서 사용되는 기법이 WebGL에 매우 잘 매핑되므로 WebGL 데모에 매우 적합한 타겟이었습니다.

복잡한 3D 메시를 표시하는 웹브라우저는 그 자체로 그리 흥미롭지 않지만, VR 작업과 웹의 교차 수분은 완전히 가능하다는 개념 증명입니다.