Ringkasan
Enam seniman diundang untuk melukis, mendesain, dan memahat dalam VR. Ini adalah bagaimana kita merekam sesi mereka, mengonversi data, dan menyajikannya secara waktu nyata dengan {i>browser<i} web.
https://g.co/VirtualArtSessions
Ini adalah waktu yang luar biasa! Dengan diperkenalkannya realitas virtual sebagai konsumen produk, kemungkinan-kemungkinan baru dan yang belum dieksplorasi terus ditemukan. Kuas Virtual, sebuah Produk Google yang tersedia di HTC Vive, memungkinkan Anda menggambar dalam tiga ruang dimensi. Ketika kami mencoba Tilt Kuas untuk pertama kali, perasaan itu menggambar dengan pengontrol pelacakan gerak ditambah dengan adanya ruangan dengan kekuatan super" tetap bersama Anda; tidak ada pengalaman yang seperti menggambar di ruang kosong di sekitar Anda.
Tim Data Arts di Google dihadapkan pada tantangan untuk menampilkan bagi mereka yang tidak menggunakan headset VR, di web yang tidak menyediakan headset VR belum beroperasi. Untuk itu, tim menghadirkan seorang pematung, ilustrator, desainer konsep, seniman mode, seniman instalasi, dan seniman jalanan membuat karya seni dengan gaya mereka sendiri dalam media baru ini.
Merekam Gambar dalam Virtual Reality
Dibangun di Unity, perangkat lunak {i>
tilt Brush <i}adalah aplikasi desktop yang
menggunakan VR berskala ruangan untuk melacak posisi kepala Anda (layar yang dipasang di kepala, atau HMD)
dan {i>controller<i} di
masing-masing tangan Anda. Poster yang dibuat di Kuas Virtual adalah dengan
default diekspor sebagai file .tilt
. Untuk menghadirkan pengalaman ini ke web, kami
menyadari bahwa kami membutuhkan lebih dari sekadar data karya seni. Kami bekerja sama dengan
Tim Miringkan Kuas untuk memodifikasi Kuas Virtual agar mengekspor tindakan urungkan/hapus juga
sebagai kepala dan tangan sang seniman memposisikan 90 kali per detik.
Saat menggambar, Kuas Virtual akan mengambil posisi dan sudut pengontrol Anda, serta melakukan konversi beberapa titik dari waktu ke waktu menjadi "stroke". Anda dapat melihat contoh di sini. Kami menulis plugin yang mengekstraksi {i>stroke<i} ini dan menghasilkannya 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 format JSON sketsa.
Di sini, setiap goresan disimpan sebagai tindakan, dengan jenis: "STROKE". Selain tindakan goresan, kami ingin menunjukkan kesalahan artis dan mengubahnya di tengah sketsa, jadi sangat penting untuk menyimpan "DELETE" tindakan yang berfungsi sebagai menghapus atau membatalkan tindakan untuk seluruh {i>stroke<i}.
Informasi dasar untuk setiap goresan disimpan, sehingga jenis kuas, ukuran kuas, warna RGB, semuanya dikumpulkan.
Akhirnya, setiap verteks goresan disimpan dan itu mencakup posisi,
sudut, waktu, serta kekuatan tekanan pemicu pengontrol (dicatat sebagai p
dalam setiap titik).
Perhatikan bahwa rotasi adalah kuaternion 4 komponen. Hal ini penting nanti ketika kita merender goresan untuk menghindari penguncian gimbal.
Memutar Sketsa dengan WebGL
Untuk menampilkan sketsa di {i>browser<i} web, kita menggunakan THREE.js dan menulis kode pembuatan geometri yang meniru apa yang dilakukan Tilt Brush.
Sementara Tilt Brush menghasilkan strip segitiga secara real-time berdasarkan tangan pengguna {i>motion<i}, keseluruhan sketsa sudah “selesai” pada saat kita menampilkannya di web. Ini memungkinkan kita untuk melewati sebagian besar kalkulasi dan penghitungan real-time geometri saat dimuat.
Setiap pasang titik sudut dalam goresan menghasilkan sebuah vektor arah (garis biru
yang menghubungkan setiap titik seperti yang ditunjukkan di atas, moveVector
dalam cuplikan kode di bawah).
Setiap titik juga berisi orientasi, yaitu kuarternion yang mewakili
sudut {i>controller<i} saat ini. Untuk menghasilkan strip segitiga, kita melakukan iterasi setiap
titik-titik tersebut menghasilkan normal
yang tegak lurus terhadap arah
orientasi pengontrol.
Proses komputasi strip segitiga untuk setiap goresan hampir identik ke kode yang digunakan di Kuas Virtual:
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 dan orientasi goresan sendiri akan menghasilkan hasil yang ambigu secara matematis; mungkin ada beberapa hal yang normal dan sering kali menghasilkan “twist” dalam geometri.
Saat melakukan iterasi pada titik-titik goresan, kita mempertahankan "hak pilihan"
vektor dan teruskan ke fungsi computeSurfaceFrame()
. Fungsi ini
kita bisa mendapatkan kuadrat dalam strip kuadrat, berdasarkan
arah goresan (dari titik terakhir ke titik saat ini), dan
orientasi pengontrol (kuarternion). Lebih penting lagi, ini juga menghasilkan
"hak pilihan" baru vektor untuk set komputasi berikutnya.
Setelah menghasilkan kuadrat berdasarkan titik kontrol setiap goresan, kita menggabungkan kuadrat dengan menginterpolasi sudutnya, dari satu kuadrat ke kuadrat 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 segiempat juga berisi UV yang dihasilkan sebagai langkah berikutnya. Beberapa kuas berisi berbagai pola {i>stroke<i} untuk memberikan kesan bahwa setiap {i>stroke<i} terasa seperti goresan kuas yang berbeda. Hal ini dicapai dengan menggunakan _tekstur atlasing, _di mana setiap tekstur kuas berisi semua elemen variasi. Tekstur yang benar dipilih dengan memodifikasi nilai UV dari {i>stroke<i}.
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 {i>stroke<i} yang tidak terbatas, dan {i>stroke<i} tidak perlu dimodifikasi saat run-time, kita telah menghitung geometri goresan sebelumnya dan mereka menjadi satu {i>mesh<i} tunggal. Meskipun setiap jenis kuas baru harus memiliki bentuk tersendiri materi ini, yang tetap akan mengurangi panggilan gambar kita menjadi satu per kuas.
Untuk menguji daya tahan sistem, kami membuat sketsa yang membutuhkan waktu 20 menit untuk dengan banyak verteks sebanyak yang kita bisa. Sketsa yang dihasilkan masih dimainkan di 60 fps di WebGL.
Karena setiap verteks asli dari {i>stroke<i} juga berisi waktu, kita bisa memutar data dengan mudah. Menghitung ulang goresan per {i>frame<i} akan benar-benar lambat, jadi kami telah menghitung seluruh sketsa saat beban dan hanya mengungkapkan setiap kuadrat saat tiba waktunya untuk melakukannya.
Menyembunyikan segiempat berarti menciutkan verteksnya hingga titik 0,0,0. Jika waktu telah mencapai titik di mana kuadrat seharusnya diungkapkan, kami memposisikan ulang verteks kembali ke tempatnya.
Area yang perlu ditingkatkan adalah memanipulasi semua verteks di GPU dengan shader. Implementasi saat ini menempatkannya dengan melakukan loop pada verteks array dari stempel waktu saat ini, memeriksa verteks mana yang perlu diungkapkan lalu memperbarui geometri. Hal ini membebani CPU secara lebih cepat dan menyebabkan kipas untuk berputar dan memboroskan masa pakai baterai.
Merekam Artis
Kami merasa bahwa sketsa itu sendiri tidak akan cukup. Kami ingin menunjukkan seniman di dalam sketsa mereka, melukis setiap sapuan kuas.
Untuk mengambil gambar artis tersebut, kami menggunakan kamera Microsoft Kinect untuk merekam kedalamannya data artis tubuh kita di luar angkasa. Hal ini memberi kita kemampuan untuk menunjukkan gambar tiga dimensi di ruang yang sama dengan gambar-gambar yang muncul.
Karena tubuh artis akan menutupi dirinya sendiri sehingga mencegah kita melihat apa yang di belakangnya, kami menggunakan sistem Kinect ganda, keduanya di sisi ruangan yang berlawanan menunjuk ke tengah.
Selain informasi kedalaman, kami juga menangkap informasi warna adegan dengan kamera DSLR standar. Kami menggunakan Software DepthKit untuk mengkalibrasi dan menggabungkan rekaman dari kamera depth dan kamera warna. Kinect mampu warna rekaman, tetapi kami memilih untuk menggunakan DSLR karena kami dapat mengontrol setelan eksposur, menggunakan lensa canggih yang indah, dan merekam dalam resolusi tinggi.
Untuk merekam rekaman video, kami membuat ruangan khusus untuk menyimpan HTC Vive, sang seniman dan kamera. Semua permukaan ditutupi dengan bahan yang menyerap inframerah cahaya untuk memberi kita awan titik yang lebih bersih (duvetyne di dinding, karet bergelombang matte di lantai). Jika materi muncul di cloud titik kami memilih materi hitam agar tidak terlalu mengganggu warnanya putih.
Rekaman video yang dihasilkan memberi kami informasi yang cukup untuk memproyeksikan sebuah partikel sistem file. Kita menulis beberapa alat tambahan di openFrameworks untuk membersihkan rekaman lebih lanjut, dengan khususnya pelepasan lantai, dinding, dan plafon.
Selain menampilkan para artis, kami ingin merender HMD dan pengontrol dalam 3D. Ini tidak hanya penting untuk menunjukkan HMD pada hasil akhirnya dengan jelas (lensa reflektif HTC Vive terbuang pembacaan IR Kinect), fitur ini memberi kita kontak untuk melakukan debug pada partikel output dan menyelaraskan video dengan sketsa.
Hal ini dilakukan dengan menulis {i>plugin<i} khusus ke Tilt Brush yang mengekstrak posisi HMD dan {i> controller<i} di setiap {i>frame<i}. Karena Kuas Virtual berjalan pada 90 fps, sangat banyak data yang disalurkan dan data input sebuah sketsa melebihi 20 MB tidak dikompresi. Kami juga menggunakan teknik ini untuk menangkap peristiwa yang tidak direkam dalam file penyimpanan Kuas Virtual, seperti saat artis memilih opsi di panel alat dan posisi widget cermin.
Saat memproses data sebesar 4 TB yang kami tangkap, salah satu tantangan terbesar adalah menyelaraskan semua sumber data/visual yang berbeda. Setiap video dari kamera DSLR harus disejajarkan dengan Kinect yang sesuai, sehingga piksel ruang serta waktu. Kemudian rekaman dari kedua perangkat kamera ini perlu selaras satu sama lain untuk membentuk satu artis. Kemudian, kita perlu menyelaraskan dengan data yang diambil dari gambar mereka. Fiuh! Kami menulis berbasis browser alat untuk membantu menyelesaikan sebagian besar tugas ini, dan Anda dapat mencobanya sendiri di sini
Setelah data diselaraskan, kami menggunakan beberapa skrip yang ditulis dalam NodeJS untuk memprosesnya semua dan menghasilkan {i>output<i} video dan serangkaian file JSON, semuanya dipangkas dan disinkronkan. Untuk mengurangi ukuran file, kita melakukan tiga hal. Pertama, kami mengurangi akurasi setiap bilangan floating point sehingga mencapai maksimum 3 presisi dari desimal. Kedua, kita mengurangi jumlah titik sebanyak sepertiga menjadi 30 fps, dan menginterpolasi posisi sisi klien. Akhirnya, kami menserialisasi data jadi alih-alih menggunakan JSON biasa dengan key-value pair, urutan nilainya dibuat untuk posisi dan rotasi HMD dan {i>controller<i}. Ini memotong file ukuran hingga kurang dari 3mb yang dapat diterima untuk dikirimkan melalui kabel.
Karena video itu sendiri ditayangkan sebagai elemen video HTML5 yang dibaca oleh Tekstur WebGL akan menjadi partikel, video itu sendiri harus diputar secara tersembunyi di latar belakang. Shader mengonversi warna pada citra kedalaman menjadi posisi di Ruang 3D. James George telah membagikan sebuah contoh bagus tentang bagaimana Anda dapat melakukan rekaman langsung dari DepthKit.
iOS memiliki pembatasan pada pemutaran video inline, yang kami asumsikan untuk mencegah pengguna agar tidak diganggu oleh iklan video web yang diputar otomatis. Kita menggunakan teknik mirip dengan solusi lain di web, yaitu untuk menyalin gambar video ke kanvas dan memperbarui waktu pencarian video secara manual, setiap 1/30 satu 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 diinginkan, yaitu menurunkan iOS secara signifikan frekuensi gambar karena penyalinan {i>buffer<i} piksel dari video ke kanvas sangat menggunakan CPU secara intensif. Untuk menyiasatinya, kami menyajikan versi berukuran lebih kecil dari video yang sama yang memungkinkan setidaknya 30 fps di iPhone 6.
Kesimpulan
Konsensus umum untuk pengembangan perangkat lunak VR sejak tahun 2016 adalah untuk menjaga geometri dan shader sederhana sehingga Anda dapat berjalan pada kecepatan 90+fps di HMD. Ini ternyata menjadi target yang sangat bagus untuk demo WebGL karena teknik yang digunakan di peta Miringkan Kuas dengan sangat baik di WebGL.
Meskipun browser web yang menampilkan mesh 3D yang kompleks tidak menarik dalam dan itu sendiri, ini adalah bukti konsep yang melintas penyerbukan pekerjaan VR dan web sepenuhnya dimungkinkan.