Tóm tắt
Sáu nghệ sĩ được mời vẽ tranh, thiết kế và điêu khắc trong thực tế ảo. Đây là về 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 theo thời gian thực bằng trình duyệt web.
https://g.co/VirtualArtSessions
Thật tuyệt vời! Với sự ra đời của thực tế ảo với vai trò là người tiêu dùng sản phẩm, những khả năng mới và tiềm năng chưa được khám phá đang được khám phá. Tilt Brush, một Sản phẩm của Google có sẵn trên HTC Vive, cho phép bạn vẽ trong ba không gian chiều. Khi chúng tôi thử Tilt Brush lần đầu tiên, cảm giác đó vẽ bằng bộ điều khiển được theo dõi chuyển động cùng với sự hiện diện của căn phòng có siêu năng lực" ở lại với bạn; thực sự không có trải nghiệm nào chẳng hạn như vẽ vào khoảng trống xung quanh mình.
Nhóm Data Arts tại Google đã gặp phải thách thức khi trình bày những tính năng này trải nghiệm của bạn với những người không có tai nghe thực tế ảo, trên web mà Tilt Brush không hỗ trợ chưa hoạt động. Để đạt được mục tiêu đó, nhóm đã thuê một nhà điêu khắc, người vẽ tranh minh hoạ, nhà thiết kế ý tưởng, nghệ sĩ thời trang, nghệ sĩ sắp đặt và nghệ sĩ đường phố để tạo tác phẩm nghệ thuật theo phong cách riêng trên phương tiện mới này.
Ghi bản vẽ trong thực tế ảo
Được tích hợp sẵn trong Unity, bản thân phần mềm Tilt Brush là một ứng dụng dành cho máy tính để bàn
sử dụng công nghệ thực tế ảo (VR) ở quy mô phòng để theo dõi vị trí đầu của bạn (màn hình gắn đầu hay HMD)
và bộ điều khiển trong mỗi tay bạn. Tác phẩm nghệ thuật được tạo trong Tilt Brush là của
được xuất theo mặc định dưới dạng tệp .tilt
. Để cung cấp trải nghiệm này trên web, chúng tôi
nhận ra rằng chúng tôi không chỉ cần dữ liệu về 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 để xuất cả tác vụ hoàn tác/xóa
đầu và tay của nghệ sĩ ở vị trí 90 lần/giây.
Khi vẽ, Tilt Brush lấy vị trí và góc của bộ điều khiển rồi chuyển đổi nhiều điểm theo thời gian thành một "nét". 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 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 ra định dạng của định dạng JSON phác thảo.
Ở đây, mỗi nét vẽ được lưu dưới dạng một hành động, thuộc loại: "STROKE". Ngoài nên chúng tôi muốn cho thấy một nghệ sĩ mắc lỗi và thay đổi ghi lại giữa chừng, nên điều quan trọng là phải lưu "DELETE" các hành động đóng vai trò là hoặc xoá hoặc huỷ các thao tác cho toàn bộ nét vẽ.
Thông tin cơ bản cho mỗi nét vẽ được lưu, do đó, loại bút vẽ, kích thước bút vẽ, màu rgb đều được thu thập.
Cuối cùng, mỗi đỉnh của nét vẽ được lưu và bao gồm cả vị trí,
góc, thời gian, cũng như cường độ áp lực của bộ điều khiển (được ký hiệu là p
trong mỗi điểm).
Lưu ý rằng chế độ xoay là một quaternion 4 thành phần. Điều này rất quan trọng sau này khi chúng ta sẽ kết xuất các nét vẽ để tránh tình trạng khoá gimbal.
Phát hình phác hoạ 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 tính năng nâng cao của Tilt Brush.
Trong khi Tilt Brush tạo các dải tam giác theo thời gian thực dựa trên tay của người dùng chuyển động thì toàn bộ bản phác thảo đã "hoàn tất" theo thời điểm chúng tôi hiển thị trên web. Điều này cho phép chúng ta bỏ qua phần lớn việc tính toán và xử lý theo thời gian thực hình học khi tải.
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 mỗi điểm như được hiển thị ở trên, moveVector
trong đoạn mã bên dưới).
Mỗi điểm cũng chứa một hướng, một tứ giác đại diện cho
góc hiện tại của bộ điều khiển. Để tạo dải tam giác, chúng ta lặp lại từng bước
các điểm này tạo ra các pháp tuyến vuông góc với hướng
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 hệt nhau vào 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);
}
Kết hợp hướng và hướng nét bằng cách tự 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 suy ra và thường sẽ mang lại tiếng "vặn" trong hình học.
Khi lặp lại qua các điểm của nét vẽ, chúng ta duy trì một "ưu tiên bên phải"
vectơ và truyền giá trị này vào hàm computeSurfaceFrame()
. Hàm này
cho chúng ta một pháp tuyến mà từ đó chúng ta có được giá trị tứ giác trong dải bốn phương, dựa vào
hướng của nét vẽ (từ điểm cuối đến điểm hiện tại) và
hướng của bộ điều khiển (một tứ phân vị). Quan trọng hơn, nó cũng trả về
một "ưu tiên bên phải" mới vectơ cho tập phép tính tiếp theo.
Sau khi tạo các góc dựa trên các điểm điều khiển của mỗi nét vẽ, chúng ta hợp nhất tứ phân vị bằng cách nội suy các góc, từ tứ phân này đến tứ phân khác.
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 );
}
Mỗi góc phần tư cũng chứa UV được tạo ở bước tiếp theo. Một số cọ chứa nhiều mẫu nét vẽ để tạo cảm giác rằng mỗi nét vẽ cho cảm giác giống như một nét vẽ khác. Điều này được thực hiện bằng cách sử dụng _bản đồ hoạ tiết, _trong đó mỗi hoạ tiết cọ chứa tất cả biến thể. Chọn đúng hoạ tiết bằng cách sửa đổi các 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 );
});
}
Vì mỗi bản vẽ có số nét không giới hạn và bạn không cần sửa đổi trong thời gian chạy, chúng tôi tính toán trước hình học nét vẽ và hợp nhất vào một lưới duy nhất. Mặc dù mỗi loại bút vẽ mới phải là riêng material, điều đó vẫn giảm hàm gọi vẽ xuống một hàm gọi cho mỗi bút vẽ.
Để kiểm tra căng thẳng cho hệ thống, chúng tôi đã tạo một bản phác thảo mất 20 phút để điền với càng nhiều đỉnh càng tốt. Bản phác thảo kết quả vẫn phát ở mức 60 khung hình/giây trong WebGL.
Do 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 nét vẽ trên mỗi khung hình sẽ thực sự chậm, nên 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ỉ hiển thị mỗi 4 vòng khi đến lúc cần làm việc đó.
Ẩn một quad đơn giản có nghĩa là thu gọn các đỉnh của nó xuống điểm 0,0,0. Khi thời điểm đạt đến điểm mà tại đó tứ giác được cho là sẽ xuất hiện, chúng ta đặt lại các đỉnh về vị trí.
Một khía cạnh cần cải thiện là thao tác với các đỉnh hoàn toàn trên GPU bằng chương trình đổ bóng. Phương thức triển khai hiện tại đặt chúng bằng cách lặp qua đỉnh mảng từ dấu thời gian hiện tại, kiểm tra xem đỉnh nào cần được tiết lộ rồi cập nhật hình học. Việc này sẽ tạo ra quá nhiều tải cho CPU, khiến để quạt quay cũng như làm lãng phí thời lượng pin.
Ghi lại bài hát của nghệ sĩ
Chúng tôi cảm thấy bản phác thảo thì vẫn chưa đủ. Chúng tôi muốn cho thấy của các hoạ sĩ bên trong các bức phác hoạ của họ, vẽ từng nét vẽ.
Để chụp lại các nghệ sĩ, chúng tôi đã sử dụng máy ảnh Microsoft Kinect để ghi lại độ sâu dữ liệu về các nghệ sĩ trong không gian. Điều này cho phép chúng tôi thể hiện các hình ba chiều trong cùng một không gian mà bản vẽ xuất hiện.
Vì cơ thể của nghệ sĩ sẽ bị che khuất nên chúng ta không nhìn thấy được ở phía sau phòng, chúng tôi dùng hệ thống Kinect kép ở cả hai phía đối diện phòng trỏ vào chính giữa.
Ngoài thông tin về chiều sâu, chúng tôi còn thu thập được thông tin màu sắc 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 để hiệu chỉnh và hợp nhất cảnh quay từ camera đo chiều sâu và camera màu. Kinect có khả năng màu ghi lại, nhưng chúng tôi đã chọn sử dụng DSLR vì chúng tôi có thể điều khiển cài đặt độ phơi sáng, sử dụng ống kính cao cấp đẹp và quay video ở độ phân giải cao.
Để ghi lại cảnh quay này, chúng tôi đã xây dựng một căn phòng đặc biệt để lưu giữ chiếc điện thoại HTC Vive, và camera. Tất cả bề mặt đều được phủ bằng vật liệu hấp thụ hồng ngoại ánh sáng mang đến cho chúng ta một đám mây điểm rõ ràng hơn (chiếc khăn phủ trên tường, cao su có rãnh thảm trên sàn nhà). Trong trường hợp vật liệu xuất hiện trong đám mây điểm nên chúng tôi đã chọn chất liệu màu đen để tránh gây mất tập trung đó là màu trắng.
Các bản ghi video thu được đã cung cấp đủ thông tin để dự đoán một hạt hệ thống. Chúng tôi đã viết một số công cụ bổ sung trong openFrameworks để dọn dẹp cảnh quay hơn nữa, trong đặc biệt là việc loại bỏ sàn, tường và trần nhà.
Ngoài việc hiển thị nghệ sĩ, chúng tôi muốn kết xuất HMD và bộ điều khiển trò chơi ở chế độ 3D. Điều này không chỉ quan trọng khi hiển thị HMD trong đầu ra cuối cùng rõ ràng (ống kính phản chiếu của HTC Vive kết quả đo hồng ngoại của Kinect), qua đó, chúng tôi nhận được các đầu mối liên hệ để gỡ lỗi hạt và sắp xếp video bằng bản phác thảo.
Việc này được thực hiện bằng cách viết một trình bổ trợ tuỳ chỉnh vào Tilt Brush để trích xuất 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, tấn dữ liệu được phát trực tuyến và dữ liệu đầu vào của bản phác thảo đã tăng lên đến 20mb 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 đồng bộ hoá hai chiều.
Trong quá trình xử lý 4TB dữ liệu mà chúng tôi thu thập được, một trong những thách thức lớn nhất là điều chỉnh tất cả các nguồn hình ảnh/dữ liệu khác nhau. Mỗi video từ 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 theo không gian cũng như thời gian. Vậy nên cảnh quay từ hai thiết bị camera này cần phải kết hợp với nhau để tạo nên một nghệ sĩ duy nhất. Sau đó, chúng tôi cần điều chỉnh mô hình 3D bằng dữ liệu thu thập được từ bản vẽ của họ. Chà! Chúng tôi đã viết dựa trên trình duyệt các công cụ trợ giúp cho hầu hết các nhiệm vụ này và bạn có thể tự mình thử thực hiện tại đây
Sau khi dữ liệu đã được căn chỉnh, chúng tôi sử dụng một số tập lệnh được viết bằng NodeJS để xử lý dữ liệu tất cả và xuất một tệp video và một loạt 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 đã làm 3 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 để chúng có giá trị lớn nhất là 3 độ chính xác của số thập phân. Thứ hai, chúng tôi giảm được 1/3 số điểm xuống 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ự nên thay vì sử dụng JSON thuần tuý với các cặp khoá/giá trị, thứ tự giá trị sẽ được tạo cho vị trí và xoay HMD và bộ điều khiển. Thao tác này sẽ cắt tệp giảm kích thước xuống chỉ còn 3mb, có thể chấp nhận được để phân phối qua dây.
Vì bản thân video được phân phát dưới dạng phần tử video HTML5 được đọc bởi Hoạ tiết WebGL trở thành các hạt, bản thân video cần phát được ẩn trong nền. Chương trình đổ bóng chuyển đổi màu sắc trong hình ảnh chiều sâu thành vị trí trong Không gian 3D. James George đã chia sẻ một ví dụ tuyệt vời về cách bạn có thể thực hiện với cảnh quay từ DepthKit.
iOS có các hạn chế về việc phát video cùng dòng. Chúng tôi giả định rằng điều này là để ngăn người dùng khỏi bị làm phiền 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, tức là sao chép khung hình video vào một canvas và cập nhật thời gian tua video theo cách thủ công, cứ 1/30 một giây.
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à làm giảm đáng kể iOS tốc độ khung hình do việc sao chép vùng đệm pixel từ video sang canvas rất Dùng nhiều CPU. Để giải quyết vấn đề này, chúng tôi chỉ cung cấp các phiên bản có kích thước nhỏ hơn của chính là video cho phép tốc độ 30 khung hình/giây trên iPhone 6.
Kết luận
Chúng tôi nhất trí chung cho việc phát triển phần mềm VR kể từ năm 2016 là duy trì hình học và chương trình đổ bóng trở nên đơn giản để bạn có thể chạy ở tốc độ 90 khung hình/giây trong HMD. Chiến dịch này trở thành mục tiêu thực sự tuyệt vời cho các bản trình diễn WebGL vì các kỹ thuật được sử dụng trong bản đồ Tilt Brush rất độc đáo với WebGL.
Mặc dù trình duyệt web hiển thị lưới 3D phức tạp không phải là điều thú vị đây là bằng chứng về khái niệm thụ phấn chéo giữa công việc thực tế ảo và web là hoàn toàn có thể.