
Ringkasan
Enam seniman diundang untuk melukis, mendesain, dan membuat patung dalam VR. Ini adalah proses untuk cara kami merekam sesi mereka, mengonversi data, dan menyajikannya secara real time dengan browser web.
https://g.co/VirtualArtSessions
Sungguh hari yang luar biasa! Dengan diperkenalkannya virtual reality sebagai produk konsumen, kemungkinan baru dan yang belum dijelajahi sedang ditemukan. Tilt Brush, produk Google yang tersedia di HTC Vive, memungkinkan Anda menggambar dalam ruang tiga dimensi. Saat pertama kali mencoba Tilt Brush, perasaan menggambar dengan pengontrol pelacakan gerakan yang dipadukan dengan perasaan "ada di ruangan dengan kekuatan super" akan terus ada di benak Anda; tidak ada pengalaman yang cukup seperti dapat menggambar di ruang kosong di sekitar Anda.

Tim Data Arts di Google dihadapkan pada tantangan untuk menampilkan pengalaman ini kepada pengguna yang tidak memiliki headset VR, di web tempat Tilt Brush belum beroperasi. Untuk itu, tim mendatangkan pemahat, ilustrator, desainer konsep, seniman mode, seniman instalasi, dan seniman jalanan untuk membuat karya seni dengan gaya mereka sendiri dalam media baru ini.
Merekam Gambar dalam Virtual Reality
Dibuat di Unity, software Tilt Brush itu sendiri adalah aplikasi desktop yang
menggunakan VR skala ruangan untuk melacak posisi kepala Anda (head mounted display, atau HMD)
dan pengontrol di setiap tangan Anda. Karya seni yang dibuat di Tilt Brush secara
default diekspor sebagai file .tilt
. Untuk menghadirkan pengalaman ini ke web, kami
menyadari bahwa kami memerlukan lebih dari sekadar data karya seni. Kami bekerja sama dengan
tim Tilt Brush untuk mengubah Tilt Brush sehingga mengekspor tindakan urungkan/hapus serta
posisi kepala dan tangan artis dengan kecepatan 90 kali per detik.
Saat menggambar, Tilt Brush mengambil posisi dan sudut pengontrol Anda, lalu mengubah beberapa titik dari waktu ke waktu menjadi "goresan". Anda dapat melihat contohnya di sini. Kami menulis plugin yang mengekstrak goresan ini dan menampilkannya sebagai JSON mentah.
{
"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
]
}
Cuplikan di atas menguraikan format JSON sketsa.
Di sini, setiap goresan disimpan sebagai tindakan, dengan jenis: "STROKE". Selain tindakan goresan, kami ingin menunjukkan bahwa seorang seniman melakukan kesalahan dan berubah minta di tengah-tengah sketsa, jadi sangat penting untuk menyimpan tindakan "HAPUS" yang berfungsi sebagai tindakan penghapusan atau pembatalan untuk seluruh goresan.
Informasi dasar untuk setiap goresan disimpan, sehingga jenis kuas, ukuran kuas, warna rgb semuanya dikumpulkan.
Terakhir, setiap vertex goresan disimpan dan mencakup posisi, sudut, waktu, serta kekuatan tekanan pemicu pengontrol (ditandai sebagai p
dalam setiap titik).
Perhatikan bahwa rotasi adalah kuadrona dengan 4 komponen. Hal ini penting nanti saat kita merender goresan untuk menghindari kunci gimbal.
Memutar Sketsa dengan WebGL
Untuk menampilkan sketsa di browser web, kami menggunakan THREE.js dan menulis kode pembuatan geometri yang meniru apa yang dilakukan Tilt Brush di balik layar.
Meskipun Tilt Brush menghasilkan strip segitiga secara real time berdasarkan gerakan tangan pengguna, seluruh sketsa sudah "selesai" pada saat kita menampilkannya di web. Hal ini memungkinkan kita mengabaikan sebagian besar penghitungan real-time dan mem-bake geometri saat pemuatan.

Setiap pasangan vertex dalam goresan menghasilkan vektor arah (garis biru
yang menghubungkan setiap titik seperti yang ditunjukkan di atas, moveVector
dalam cuplikan kode di bawah).
Setiap titik juga berisi orientasi, kuadrat yang mewakili sudut pengontrol saat ini. Untuk menghasilkan strip segitiga, kita melakukan iterasi pada setiap
titik ini yang menghasilkan normal yang tegak lurus dengan arah dan
orientasi pengontrol.
Proses untuk menghitung strip segitiga untuk setiap goresan hampir identik dengan kode yang digunakan di 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);
}
Menggabungkan arah goresan dan orientasi itu sendiri akan menampilkan hasil yang ambigu secara matematis; mungkin ada beberapa normal yang berasal dan sering kali menghasilkan "twist" dalam geometri.
Saat melakukan iterasi pada titik goresan, kita mempertahankan vektor "kanan pilihan"
dan meneruskannya ke fungsi computeSurfaceFrame()
. Fungsi ini
memberi kita normal yang dapat digunakan untuk mendapatkan quad di strip quad, berdasarkan
arah goresan (dari titik terakhir ke titik saat ini), dan
orientasi pengontrol (kuaternion). Yang lebih penting, fungsi ini juga menampilkan
vektor "kanan pilihan" baru untuk kumpulan komputasi berikutnya.

Setelah membuat kuad berdasarkan titik kontrol setiap goresan, kita menggabungkan kuad dengan melakukan interpolasi sudutnya, dari satu kuad ke kuad berikutnya.
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 );
}

Setiap quad juga berisi UV yang dihasilkan sebagai langkah berikutnya. Beberapa kuas berisi berbagai pola goresan untuk memberikan kesan bahwa setiap goresan terasa seperti goresan kuas cat yang berbeda. Hal ini dilakukan menggunakan _atlas tekstur, _dengan setiap tekstur kuas berisi semua kemungkinan variasi. Tekstur yang benar dipilih dengan mengubah nilai UV stroke.
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 );
});
}



Karena setiap sketsa memiliki jumlah goresan yang tidak terbatas, dan goresan tidak perlu diubah dalam waktu proses, kita akan melakukan pra-komputasi geometri goresan terlebih dahulu dan menggabungkannya menjadi satu mesh. Meskipun setiap jenis kuas baru harus berupa materi tersendiri, hal itu masih mengurangi panggilan gambar menjadi satu per kuas.

Untuk melakukan pengujian stres pada sistem, kami membuat sketsa yang memerlukan waktu 20 menit untuk mengisi ruang dengan sebanyak mungkin vertex. Sketsa yang dihasilkan masih diputar pada 60 fps di WebGL.
Karena setiap vertex asli goresan juga berisi waktu, kita dapat memutar data dengan mudah. Menghitung ulang goresan per frame akan sangat lambat, jadi sebagai gantinya, kita akan melakukan pra-komputasi seluruh sketsa saat dimuat dan hanya menampilkan setiap quad saat tiba waktunya untuk melakukannya.
Menyembunyikan kuad berarti menciutkan verteksnya ke titik 0,0,0. Saat waktu telah mencapai titik saat quad seharusnya ditampilkan, kita akan memosisikan kembali vertex ke tempatnya.
Area yang dapat ditingkatkan adalah memanipulasi vertex sepenuhnya di GPU dengan shader. Implementasi saat ini menempatkannya dengan melakukan loop melalui array verteks dari stempel waktu saat ini, memeriksa verteks mana yang perlu ditampilkan, lalu memperbarui geometri. Hal ini akan membebani CPU sehingga menyebabkan kipas berputar serta membuang daya tahan baterai.

Merekam Artis
Kami merasa bahwa sketsa itu sendiri tidak akan cukup. Kami ingin menampilkan seniman di dalam sketsa mereka, melukis setiap goresan kuas.
Untuk merekam artis, kami menggunakan kamera Microsoft Kinect untuk merekam data kedalaman tubuh artis di ruang. Hal ini memberi kita kemampuan untuk menampilkan gambar tiga dimensinya di ruang yang sama dengan gambar yang muncul.
Karena tubuh artis akan menghalangi dirinya sendiri sehingga kita tidak dapat melihat apa yang ada di belakangnya, kami menggunakan sistem Kinect ganda, keduanya di sisi yang berlawanan dari ruangan yang mengarah ke tengah.
Selain informasi kedalaman, kami juga mengambil informasi warna adegan dengan kamera DSLR standar. Kami menggunakan software DepthKit yang sangat baik untuk mengalibrasi dan menggabungkan rekaman dari kamera kedalaman dan kamera warna. Kinect mampu merekam warna, tetapi kami memilih untuk menggunakan DSLR karena kami dapat mengontrol setelan eksposur, menggunakan lensa high-end yang indah, dan merekam dalam resolusi tinggi.
Untuk merekam rekaman, kami membuat ruangan khusus untuk menempatkan HTC Vive, artis, dan kamera. Semua permukaan ditutupi dengan bahan yang menyerap cahaya infra merah untuk memberi kita cloud titik yang lebih bersih (duvetyne di dinding, alas karet bergaris di lantai). Jika materi muncul dalam rekaman point cloud, kami memilih materi hitam agar tidak terlalu mengganggu seperti materi berwarna putih.

Rekaman video yang dihasilkan memberi kita informasi yang cukup untuk memproyeksikan sistem partikel. Kami menulis beberapa alat tambahan di openFrameworks untuk lebih membersihkan rekaman, terutama menghapus lantai, dinding, dan langit-langit.

Selain menampilkan artis, kami juga ingin merender HMD dan pengontrol dalam 3D. Hal ini tidak hanya penting untuk menampilkan HMD dalam output akhir dengan jelas (lensa reflektif HTC Vive mengacaukan pembacaan IR Kinect), tetapi juga memberi kita titik kontak untuk men-debug output partikel dan menyelaraskan video dengan sketsa.

Hal ini dilakukan dengan menulis plugin kustom ke Tilt Brush yang mengekstrak posisi HMD dan pengontrol setiap frame. Karena Tilt Brush berjalan pada 90 fps, banyak data yang di-streaming dan data input sketsa berukuran lebih dari 20 mb tanpa dikompresi. Kami juga menggunakan teknik ini untuk merekam peristiwa yang tidak dicatat dalam file penyimpanan Tilt Brush standar, seperti saat seniman memilih opsi di panel alat dan posisi widget cermin.
Dalam memproses 4 TB data yang kami rekam, salah satu tantangan terbesarnya adalah menyesuaikan semua sumber visual/data yang berbeda. Setiap video dari kamera DSLR harus disejajarkan dengan Kinect yang sesuai, sehingga piksel sejajar dalam ruang dan waktu. Kemudian, rekaman dari kedua rig kamera ini harus disejajarkan satu sama lain untuk membentuk satu artis. Kemudian, kita perlu menyelaraskan seniman 3D dengan data yang diambil dari gambar mereka. Fiuh! Kami menulis alat berbasis browser untuk membantu sebagian besar tugas ini, dan Anda dapat mencobanya sendiri di sini

Setelah data disejajarkan, kami menggunakan beberapa skrip yang ditulis di NodeJS untuk memproses semuanya dan menghasilkan file video dan serangkaian file JSON, semuanya dipangkas dan disinkronkan. Untuk mengurangi ukuran file, kami melakukan tiga hal. Pertama, kami mengurangi akurasi setiap bilangan floating point sehingga presisinya maksimal 3 desimal. Kedua, kita memotong jumlah titik sebesar sepertiga menjadi 30 fps, dan melakukan interpolasi posisi sisi klien. Terakhir, kita melakukan serialisasi data sehingga, alih-alih menggunakan JSON biasa dengan key-value pair, urutan nilai dibuat untuk posisi dan rotasi HMD dan pengontrol. Hal ini mengurangi ukuran file menjadi hampir 3 MB yang dapat diterima untuk dikirim melalui jaringan.

Karena video itu sendiri ditayangkan sebagai elemen video HTML5 yang dibaca oleh tekstur WebGL untuk menjadi partikel, video itu sendiri harus diputar secara tersembunyi di latar belakang. Shader mengonversi warna dalam gambar kedalaman menjadi posisi dalam ruang 3D. James George telah membagikan contoh bagus tentang cara menggunakan rekaman langsung dari DepthKit.
iOS memiliki batasan pada pemutaran video inline, yang kami asumsikan untuk mencegah pengguna diganggu oleh iklan video web yang diputar otomatis. Kami menggunakan teknik yang mirip dengan solusi lain di web, yaitu menyalin bingkai video ke kanvas dan memperbarui waktu pencarian video secara manual, setiap 1/30 detik.
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 );
Pendekatan kami memiliki efek samping yang tidak menguntungkan, yaitu menurunkan kecepatan frame iOS secara signifikan karena penyalinan buffering piksel dari video ke kanvas sangat memerlukan CPU. Untuk mengatasi hal ini, kami cukup menayangkan versi berukuran lebih kecil dari video yang sama yang memungkinkan setidaknya 30 fps di iPhone 6.
Kesimpulan
Konsensus umum untuk pengembangan software VR mulai tahun 2016 adalah menjaga geometri dan shader tetap sederhana sehingga Anda dapat menjalankannya pada kecepatan 90+fps di HMD. Hal ini ternyata menjadi target yang sangat bagus untuk demo WebGL karena teknik yang digunakan di Tilt Brush dipetakan dengan sangat baik ke WebGL.
Meskipun browser web yang menampilkan mesh 3D kompleks tidak menarik secara mandiri, ini adalah bukti konsep bahwa persilangan pekerjaan VR dan web sepenuhnya dapat dilakukan.