Buổi biểu diễn nghệ thuật trực tuyến

Thông tin chi tiết về buổi biểu diễn nghệ thuật

Tóm tắt

Sáu nghệ sĩ được mời vẽ, thiết kế và điêu khắc trong môi trường thực tế ảo. Đây là quy trình cho cách chúng tôi ghi lại các phiên của họ, chuyển đổi dữ liệu và trình bày dữ liệu theo thời gian thực với các trình duyệt web.

https://g.co/VirtualArtSessions

Thật là thời khắc để sống! Với sự ra đời của thực tế ảo dưới dạng một sản phẩm tiêu dùng, mọi người đang khám phá những khả năng mới và chưa được khám phá. Tilt Brush là một sản phẩm của Google có trên HTC Vive, cho phép bạn vẽ trong không gian ba chiều. Lần đầu tiên chúng tôi dùng thử Tilt Brush, cảm giác vẽ bằng các bộ điều khiển theo dõi chuyển động cùng với cảm giác "ở trong một căn phòng có siêu năng lực" luôn len lỏi trong bạn; thực sự không có trải nghiệm nào giống như việc có thể vẽ vào không gian trống xung quanh mình.

Tác phẩm nghệ thuật ảo

Nhóm nghệ thuật dữ liệu tại Google đã đưa ra thử thách trong việc thể hiện trải nghiệm này cho những người không có tai nghe thực tế ảo trên web nơi Tilt Brush chưa hoạt động. Để đạt được mục tiêu này, nhóm đã mời một nhà điêu khắc, hoạ sĩ minh hoạ, nhà thiết kế ý tưởng, nghệ sĩ thời trang, nghệ sĩ sắp đặt và các nghệ sĩ đường phố để tạo ra tác phẩm nghệ thuật theo phong cách riêng trong môi trường mới này.

Ghi lại bản vẽ trong thực tế ảo

Được tích hợp trong Unity, phần mềm Tilt Brush là một ứng dụng dành cho máy tính, sử dụng công nghệ thực tế ảo (VR) trong quy mô phòng để theo dõi vị trí đầu của bạn (màn hình đeo đầu hay HMD) và bộ điều khiển trên mỗi tay bạn. Theo mặc định, hình minh hoạ được tạo trong Tilt Brush được xuất ở dạng tệp .tilt. Để mang trải nghiệm này lên web, chúng tôi nhận ra rằng mình không chỉ cần dữ liệu hình minh hoạ. Chúng tôi hợp tác chặt chẽ với nhóm Tilt Brush để sửa đổi Tilt Brush, nhờ đó họ xuất các thao tác huỷ/xoá cũng như vị trí đầu và tay của nghệ sĩ với tần suất 90 lần trong một giây.

Khi vẽ, Tilt Brush sẽ lấy vị trí và góc của bộ điều khiển, đồng thời chuyển đổi nhiều điểm theo thời gian thành một "nét vẽ". Bạn có thể xem ví dụ tại đây. Chúng tôi đã viết các trình bổ trợ trích xuất các nét vẽ này và xuất chúng dưới dạng JSON thô.

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

Đoạn mã trên nêu rõ định dạng của định dạng JSON phác thảo.

Tại đây, mỗi nét vẽ được lưu dưới dạng một thao tác, thuộc loại: "STROKE". Ngoài các thao tác vẽ, chúng tôi muốn cho thấy nghệ sĩ mắc lỗi và thay đổi ý tưởng giữa bản phác thảo. Vì vậy, điều quan trọng là phải lưu thao tác "DELETE" (xoá) để đóng vai trò là thao tác xoá hoặc huỷ cho toàn bộ nét vẽ.

Thông tin cơ bản cho mỗi nét vẽ được lưu lại, nhờ đó, loại bút vẽ, kích thước bút vẽ, mô-đun màu sắc đều được thu thập.

Cuối cùng, mỗi đỉnh của nét vẽ được lưu và bao gồm vị trí, góc, thời gian, cũng như cường độ áp lực kích hoạt của bộ điều khiển (được ghi chú là p trong mỗi điểm).

Lưu ý rằng chế độ xoay là một quaternion 4 thành phần. Đây là điều quan trọng sau này khi chúng ta kết xuất các nét vẽ để tránh tình trạng khoá gimbal.

Phát Back Sketches bằng WebGL

Để hiển thị các bản phác thảo trong trình duyệt web, chúng tôi đã sử dụng THREE.js và viết mã tạo hình học bắt chước những gì Tilt Brush làm được.

Mặc dù Tilt Brush tạo các dải tam giác theo thời gian thực dựa trên chuyển động tay của người dùng, nhưng toàn bộ bản phác thảo đã "hoàn tất" vào thời điểm chúng tôi đăng trên web. Điều này cho phép chúng ta bỏ qua phần lớn công việc tính toán theo thời gian thực và xử lý hình học khi tải.

Bản phác thảo WebGL

Mỗi cặp đỉnh trong một nét vẽ tạo ra một vectơ chỉ hướng (các đường màu xanh dương kết nối từng điểm như minh hoạ ở trên, moveVector trong đoạn mã dưới đây). Mỗi điểm cũng chứa một hướng, một quaternion đại diện cho góc hiện tại của bộ điều khiển. Để tạo một dải tam giác, chúng tôi lặp lại từng điểm này để tạo ra các pháp tuyến vuông góc với hướng và hướng của bộ điều khiển.

Quy trình tính toán dải tam giác cho mỗi nét vẽ gần giống với mã được sử dụng trong 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);
}

Việc tự kết hợp hướng và hướng nét vẽ sẽ trả về kết quả không rõ ràng về mặt toán học; có thể có nhiều pháp tuyến được bắt nguồn và thường sẽ gây ra hiện tượng "xuyên hình" trong hình học.

Khi lặp lại qua các điểm của một nét vẽ, chúng tôi duy trì vectơ "bên phải ưu tiên" và truyền vectơ này vào hàm computeSurfaceFrame(). Hàm này mang lại cho chúng ta một giá trị bình thường mà từ đó chúng ta có thể rút ra được một tứ giác trong bốn dải, dựa trên hướng của nét vẽ (từ điểm cuối đến điểm hiện tại) và hướng của tay điều khiển (một quaternion). Quan trọng hơn, phương thức này cũng trả về một vectơ "bên phải ưu tiên" mới cho tập hợp các phép tính tiếp theo.

Nét vẽ

Sau khi tạo tứ giác dựa trên các điểm điều khiển của từng nét vẽ, chúng tôi hợp nhất các điểm đó bằng cách nội suy các góc của chúng, từ điểm này sang điểm tiếp theo.

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 );
}
tứ giác kết hợp
Qua tứ giác kết hợp.

Mỗi tứ giác cũng chứa UV được tạo ở bước tiếp theo. Một số cọ có chứa nhiều kiểu nét vẽ để tạo ấn tượng rằng mỗi nét vẽ lại giống như một nét khác của cọ vẽ. Bạn có thể thực hiện việc này bằng cách sử dụng phương pháp tô màu _texture, _trong đó mỗi hoạ tiết bút vẽ chứa tất cả các biến thể có thể có. Bạn có thể chọn đúng hoạ tiết bằng cách sửa đổi giá trị UV của nét vẽ.

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

    });

}
Bốn hoạ tiết trong tập bản đồ hoạ tiết cho bút vẽ dầu
Bốn hoạ tiết trong tập bản đồ hoạ tiết cho bút vẽ dầu
Bút vẽ nghiêng
Trong Tilt Brush
Trong WebGL
Trong WebGL

Vì mỗi bản phác thảo có số lượng nét không giới hạn và các nét vẽ sẽ không cần sửa đổi trong thời gian chạy, nên chúng tôi sẽ tính toán trước hình dạng nét vẽ và hợp nhất chúng vào một lưới duy nhất. Mặc dù mỗi loại bút vẽ mới phải là chất liệu riêng, nhưng điều đó vẫn giảm lệnh gọi vẽ cho mỗi loại bút vẽ.

Toàn bộ bản phác thảo ở trên được thực hiện trong một lần gọi vẽ trong WebGL
Toàn bộ bản phác thảo ở trên được thực hiện bằng một lệnh gọi vẽ trong WebGL

Để kiểm thử hệ thống một cách hiệu quả nhất, chúng tôi đã tạo một bản phác thảo mất 20 phút để lấp đầy không gian bằng nhiều đỉnh nhất có thể. Bản phác thảo thu được vẫn phát ở tốc độ 60 khung hình/giây trong WebGL.

Vì mỗi đỉnh ban đầu của nét vẽ cũng chứa thời gian, nên chúng ta có thể dễ dàng phát lại dữ liệu. Việc tính toán lại các nét trên mỗi khung hình sẽ thực sự chậm, vì vậy, thay vào đó, chúng tôi đã tính toán trước toàn bộ bản phác thảo khi tải và chỉ tiết lộ từng nét vẽ khi đến thời điểm thực hiện.

Ẩn một tứ giác đơn giản có nghĩa là thu gọn các đỉnh của nó xuống điểm 0,0,0. Khi đạt đến điểm mà tứ giác đáng ra, chúng ta sẽ đặt lại vị trí các đỉnh về vị trí đó.

Một vấn đề cần cải thiện là thao tác trên toàn bộ các đỉnh trên GPU bằng chương trình đổ bóng. Cách triển khai hiện tại sẽ đặt các đỉnh này bằng cách lặp lại mảng đỉnh từ dấu thời gian hiện tại, kiểm tra xem đỉnh nào cần được hiển thị rồi cập nhật hình dạng. Điều này sẽ gây quá nhiều tải cho CPU, khiến quạt quay và gây lãng phí thời lượng pin.

Tác phẩm nghệ thuật ảo

Ghi âm nhạc của nghệ sĩ

Chúng tôi cảm thấy rằng bản thân các bản phác thảo là chưa đủ. Chúng tôi muốn cho các nghệ sĩ nhìn thấy bên trong các bản phác hoạ của họ, vẽ từng nét vẽ.

Để ghi lại hình ảnh các nghệ sĩ, chúng tôi đã dùng máy ảnh của Microsoft Kinect để ghi lại dữ liệu về chiều sâu cơ thể của các nghệ sĩ trong không gian. Điều này cho phép chúng tôi hiển thị các hình ba chiều của các hình đó trong cùng không gian mà bản vẽ xuất hiện.

Vì cơ thể của nghệ sĩ sẽ che khuất chính mình khiến chúng tôi không nhìn thấy gì ở phía sau, nên chúng tôi đã dùng hệ thống Kinect kép, cả hai ở hai bên đối diện của phòng đều hướng vào chính giữa.

Ngoài thông tin về độ sâu, chúng tôi cũng thu thập thông tin màu của cảnh bằng máy ảnh DSLR tiêu chuẩn. Chúng tôi đã sử dụng phần mềm DepthKit tuyệt vời để hiệu chỉnh và hợp nhất cảnh quay từ máy ảnh độ sâu và máy ảnh màu. Kinect có khả năng ghi lại màu, nhưng chúng tôi chọn dùng máy ảnh DSLR vì chúng tôi có thể kiểm soát chế độ cài đặt phơi sáng, sử dụng ống kính cao cấp đẹp mắt và quay video ở độ phân giải cao.

Để ghi lại các cảnh quay, chúng tôi đã xây dựng một căn phòng đặc biệt để lưu giữ HTC Vive, nghệ sĩ và máy quay. Tất cả các bề mặt được phủ bằng vật liệu hấp thụ ánh sáng hồng ngoại để tạo một đám mây điểm sạch hơn (tông mù trên tường, lớp cao su có gai trên sàn). Trong trường hợp vật liệu xuất hiện trong cảnh quay đám mây điểm, chúng tôi đã chọn chất liệu màu đen để không gây mất tập trung như vật liệu màu trắng.

Nghệ sĩ thu âm

Các bản ghi video thu được đã cung cấp cho chúng tôi đủ thông tin để chiếu một hệ thống phần tử. Chúng tôi đã viết một số công cụ bổ sung trong openFrameworks để dọn dẹp cảnh quay thêm, cụ thể là loại bỏ sàn, tường và trần nhà.

Tất cả bốn kênh của một phiên video được ghi lại (hai kênh màu ở trên và hai kênh màu ở bên dưới)
Cả 4 kênh của một phiên video được ghi lại (hai kênh màu ở trên và hai kênh màu ở bên dưới)

Ngoài việc hiển thị các nghệ sĩ, chúng tôi cũng muốn kết xuất HMD và bộ điều khiển ở chế độ 3D. Điều này không chỉ quan trọng để thể hiện rõ HMD trong kết quả cuối cùng (ống kính phản chiếu của HTC Vive làm lệch các chỉ số IR của Kinect), mà còn cung cấp cho chúng tôi đầu mối liên hệ để gỡ lỗi đầu ra hạt và xếp video bằng bản phác thảo.

Màn hình đội đầu, bộ điều khiển và các hạt sắp xếp
Màn hình gắn đầu, bộ điều khiển và các hạt sắp xếp

Bạn có thể thực hiện việc này bằng cách viết một trình bổ trợ tuỳ chỉnh vào Tilt Brush đã trích xuất các vị trí của HMD và bộ điều khiển từng khung hình. Vì Tilt Brush chạy ở tốc độ 90 khung hình/giây, nên rất nhiều dữ liệu được truyền trực tuyến và dữ liệu đầu vào của một bản phác thảo đã vượt quá 20 MB không nén. Chúng tôi cũng dùng kỹ thuật này để ghi lại các sự kiện không được ghi lại trong tệp lưu Tilt Brush thông thường, chẳng hạn như khi nghệ sĩ chọn một tuỳ chọn trên bảng điều khiển công cụ và vị trí của tiện ích phản chiếu.

Trong quá trình xử lý 4 TB dữ liệu chúng tôi thu thập được, một trong những thách thức lớn nhất là sắp xếp tất cả các nguồn dữ liệu/hình ảnh khác nhau. Mỗi video trên máy ảnh DSLR cần được căn chỉnh với Kinect tương ứng để các pixel được căn chỉnh về không gian cũng như thời gian. Sau đó, cảnh quay từ hai thiết bị camera này cần được căn chỉnh với nhau để tạo thành một nghệ sĩ duy nhất. Sau đó, chúng tôi cần căn chỉnh nghệ sĩ 3D với dữ liệu thu được từ bản vẽ của họ. Chà! Chúng tôi đã viết các công cụ dựa trên trình duyệt để giúp thực hiện hầu hết các tác vụ này và bạn có thể tự mình thử các công cụ này tại đây

Nghệ sĩ thu âm

Sau khi dữ liệu đã được điều chỉnh, chúng tôi sử dụng một số tập lệnh viết trong NodeJS để xử lý tất cả và xuất ra một tệp video cũng như các chuỗi tệp JSON, tất cả đều được cắt bớt và đồng bộ hoá. Để giảm kích thước tệp, chúng tôi đã thực hiện ba việc. Trước tiên, chúng tôi đã giảm độ chính xác của từng số có dấu phẩy động để các số này có độ chính xác tối đa là 3 thập phân. Thứ hai, chúng tôi cắt giảm số điểm đi 1/3 xuống còn 30 khung hình/giây và nội suy các vị trí ở phía máy khách. Cuối cùng, chúng tôi đã chuyển đổi tuần tự dữ liệu này để thay vì sử dụng JSON thuần tuý với các cặp khoá/giá trị, hệ thống sẽ tạo thứ tự các giá trị để sắp xếp vị trí và xoay HMD và bộ điều khiển. Cách này giúp giảm kích thước tệp xuống chỉ còn 3mb (có thể chấp nhận được để phân phối qua dây).

Nghệ sĩ thu âm

Vì chính video được phân phát dưới dạng phần tử video HTML5 được kết cấu WebGL đọc để trở thành các hạt, nên video cần phát ẩn trong nền. Chương trình đổ bóng sẽ chuyển đổi các màu trong hình ảnh chiều sâu thành các vị trí trong không gian 3D. James George đã chia sẻ một ví dụ hay về cách bạn có thể thực hiện với cảnh quay ngay từ DepthKit.

iOS có các hạn chế về việc phát video nội tuyến. Chúng tôi giả định rằng các hạn chế này là để ngăn người dùng bị ảnh hưởng bởi quảng cáo video trên web tự động phát. Chúng tôi đã sử dụng một kỹ thuật tương tự như các giải pháp khác trên web, đó là sao chép khung hình video vào canvas và cập nhật thời gian tìm kiếm video theo cách thủ công, cứ 1/30 giây một lần.

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

Phương pháp của chúng tôi có tác dụng phụ đáng tiếc là việc giảm đáng kể tốc độ khung hình trên iOS do việc sao chép vùng đệm pixel từ video sang canvas rất tốn CPU. Để giải quyết vấn đề này, chúng tôi chỉ cần phân phối các phiên bản video có kích thước nhỏ hơn cho phép ít nhất 30 khung hình/giây trên iPhone 6.

Kết luận

Từ năm 2016, sự đồng thuận chung đối với việc phát triển phần mềm thực tế ảo (VR) là giữ cho các hình học và chương trình đổ bóng đơn giản để bạn có thể chạy ở tốc độ 90 khung hình/giây trở lên trong HMD. Đây trở thành mục tiêu thực sự tuyệt vời cho các bản minh hoạ WebGL vì các kỹ thuật được sử dụng trong bản đồ Tilt Brush rất phù hợp với WebGL.

Mặc dù bản thân các trình duyệt web hiển thị lưới 3D phức tạp không phải là điều dễ thấy, nhưng đây là một bằng chứng cho thấy khái niệm rằng việc thụ phấn chéo của công nghệ thực tế ảo (VR) và web là hoàn toàn có thể xảy ra.