Riepilogo
Sei artisti sono stati invitati a dipingere, progettare e scolpire in VR. Questo è il di registrazione delle sessioni, conversione dei dati e presentazione in tempo reale con i browser web.
https://g.co/VirtualArtSessions
Che momento di vivere! Con l'introduzione della realtà virtuale come consumatore prodotto, si stanno scoprendo opportunità nuove e inesplorate. Pennello di inclinazione, un Prodotto Google disponibile su HTC Vive che consente di disegnare in tre spazio dimensionale. Quando abbiamo provato Tilt Brush per la prima volta, quella sensazione un disegno con controller basati sul movimento e la presenza di essere "in un stanza con i superpoteri" rimane con te; non c'è davvero un'esperienza è come essere in grado di disegnare lo spazio vuoto intorno a te.
La sfida di presentare al team Data Arts di Google è stata a chi non ha un visore VR, sul web in cui Tilt Brush ma operare. A questo scopo, il team ha partecipato a uno scultore, un illustratore, concept designer, un artista di moda, un artista di installazione e artisti di strada di creare opere d'arte nel loro stile all'interno di questo nuovo mezzo.
Registrare disegni in realtà virtuale
Integrato in Unity, il software Tilt Brush è un'applicazione desktop che
utilizza la VR su scala stanza per monitorare la posizione della testa (HMD)
e i controller in ognuna delle tue mani. L'artwork creata in Tilt Brush è di
esportato per impostazione predefinita come file .tilt
. Per portare questa esperienza sul web,
abbiamo capito che avevamo bisogno di qualcosa in più oltre ai dati dell'artwork. Abbiamo lavorato a stretto contatto con
Il team di Tilt Brush ha deciso di modificare Tilt Brush in modo da esportare anche le azioni di annullamento/eliminazione
quando la testa e la mano
dell'artista si trovano a 90 volte al secondo.
Quando disegni, Tilt Brush prende la posizione e l'angolazione del controller e converte più punti nel tempo in un "tratto". Puoi vedere un esempio qui Abbiamo scritto plug-in che estraevano questi tratti e li restituisce come JSON non elaborati.
{
"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
]
}
Lo snippet riportato sopra illustra il formato del formato JSON bozza.
In questo caso, ogni tratto viene salvato come azione, con un tipo: "STROKE". Oltre a le bracciate, volevamo mostrare un artista che commette degli errori e modifica le mente mid-sketch, quindi era fondamentale salvare "DELETE" azioni che fungono da cancella o annulla le azioni di un intero tratto.
Le informazioni di base per ogni tratto vengono salvate, in modo che il tipo, la dimensione e il colore del pennello RGB sono raccolti.
Infine, ogni vertice del tratto viene salvato e include la posizione,
angolo, tempo e forza di pressione del controller (indicata come p
all'interno di ciascun punto).
Tieni presente che la rotazione è una quaternione a quattro componenti. Ciò diventa importante in seguito, eseguiamo il rendering delle bracciate per evitare il blocco del gimbal.
Riproduzione di schizzi con WebGL
Per mostrare gli schizzi in un browser web, abbiamo utilizzato THREE.js e ha scritto un codice di generazione della geometria che imitava le funzionalità di Tilt Brush.
Mentre Tilt Brush produce strisce triangolari in tempo reale in base alla mano dell'utente, movimento, il disegno è già "finito" quando lo mostriamo sul web. In questo modo possiamo bypassare gran parte del calcolo in tempo reale e la geometria al caricamento.
Ciascuna coppia di vertici in un tratto produce un vettore di direzione (le linee blu
collegando ogni punto come mostrato sopra, moveVector
nello snippet di codice di seguito).
Ogni punto contiene anche un orientamento, un quaternione che rappresenta il
l'angolo attuale del controller. Per produrre una striscia triangolare, occorre ripetere l'iterazione
da questi punti che producono valori normali perpendicolari alla direzione e
l'orientamento del controller.
Il processo per calcolare la striscia triangolare per ogni tratto è quasi identico al codice utilizzato in 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);
}
La combinazione della direzione del tratto e dell'orientamento torna indietro risultati matematicamente ambigui; potrebbero derivare più normali e spesso produrrebbe un "torsione" nella geometria.
Nell'iterazione dei punti di un tratto, manteniamo il "diritto preferito"
vettoriale e lo passiamo alla funzione computeSurfaceFrame()
. Questa funzione
ci fornisce una normale da cui possiamo ricavare un quad nella striscia quad, in base a
la direzione del tratto (dall'ultimo punto al punto corrente) e
orientamento del controller (un quaternione). Ma soprattutto, restituisce
un nuovo "diritto preferito" per il set di calcoli successivo.
Dopo aver generato i quadricipiti in base ai punti di controllo di ogni tratto, finizziamo interpolando gli angoli, da un riquadro all'altro.
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 );
}
Ogni quad contiene anche UV, che vengono generati come passaggio successivo. Alcuni pennelli contengono una serie di pattern per dare l'impressione che ogni tratto sembrava un tratto diverso di pennello. Questo si ottiene utilizzando _atlante tra le texture, _dove ogni texture del pennello contiene tutte le varianti. La texture corretta viene selezionata modificando i valori UV del ictus.
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 );
});
}
Poiché ogni schizzo ha un numero illimitato di tratti, modificata in tempo di esecuzione, precalcoliamo la geometria del tratto in anticipo e uniamo in un'unica rete mesh. Anche se ogni nuovo tipo di pennello deve essere proprio , la riduzione dei richiami di disegno a uno per pennello.
Per eseguire lo stress test del sistema, abbiamo creato uno schizzo che richiedeva 20 minuti con il maggior numero di vertici possibile. Lo sketch risultante veniva comunque riprodotto 60 f/s in WebGL.
Poiché tutti i vertici originali di un tratto contenevano anche tempo, possiamo riprodurre facilmente i dati. Ricalcolare le corse per fotogramma sarebbe davvero lento, quindi abbiamo precalcolato l'intero disegno al caricamento e rivelato quando era il momento di farlo.
Nascondere un quad significava semplicemente comprimere i vertici al punto 0,0,0. Quando è arrivato il momento in cui dovrebbe essere rivelato il quad, abbiamo e riposizionare i vertici.
Un'area di miglioramento è manipolare interamente i vertici sulla GPU con Shaper. L'implementazione corrente li posiziona tramite loop attraverso il vertice del timestamp corrente, controllando quali vertici devono essere rivelati e poi aggiornando la geometria. Questo sottopone a carico la CPU, il che far girare la ventola e sprecare la batteria.
Registrazione degli artisti
Abbiamo pensato che gli sketch stessi non sarebbero stati sufficienti. Volevamo mostrarti artisti nei loro schizzi, dipingendo ogni pennellata.
Per acquisire le immagini degli artisti, abbiamo usato fotocamere Microsoft Kinect per registrare la profondità i dati dell'artista corpo nello spazio. Questo ci permette di mostrare tre figure dimensionali nello stesso spazio in cui appaiono i disegni.
Dal momento che il corpo dell'artista si occluderebbe impedendoci di vedere ciò che è dietro di esso abbiamo utilizzato un doppio sistema Kinect, entrambi sui lati opposti della stanza che indica al centro.
Oltre alle informazioni sulla profondità, abbiamo acquisito anche le informazioni sul colore la scena con fotocamere DSLR standard. Abbiamo utilizzato l'eccellente Software DepthKit per calibrare e unire il filmato dalla fotocamera di profondità e da quelle a colori. Kinect è in grado di registrazione del colore, ma abbiamo scelto di usare DSLR perché potevamo controllare impostazioni di esposizione, usare stupendi obiettivi di fascia alta e registrare in alta definizione.
Per registrare il filmato, abbiamo costruito una stanza speciale per ospitare l'HTC Vive, l'artista e la fotocamera. Tutte le superfici erano coperte con materiale che assorbiva gli infrarossi per ottenere una nuvola di punti più pulita (duvetyne sulle pareti, gomma a coste tappetino sul pavimento). Nel caso in cui il materiale sia stato visualizzato nella nuvola di punti abbiamo scelto il materiale nero per non distrarlo era bianco.
Le registrazioni video risultanti ci hanno fornito informazioni sufficienti per proiettare una particella di un sistema operativo completo. Abbiamo scritto alcuni strumenti aggiuntivi openFrameworks per ripulire ulteriormente il filmato, in nella rimozione di pavimenti, pareti e soffitti.
Oltre a mostrare gli artisti, volevamo eseguire il rendering dell'HMD e in 3D. Questo non era importante solo per mostrare l'HMD l'output finale in modo chiaro (le lenti riflettenti dell'HTC Vive si rilevazioni IR di Kinect), ci ha fornito i punti di contatto per eseguire il debug della particella produrre e allineare i video allo sketch.
A questo scopo, ha scritto un plug-in personalizzato in Tilt Brush che estraeva posizioni dell'HMD e dei controller per ogni frame. Dato che Tilt Brush funziona a 90 f/s, tonnellate di dati trasmessi in streaming e i dati di input di uno schizzo superavano i 20 MB non compresso. Abbiamo utilizzato questa tecnica anche per acquisire eventi che non vengono registrati nel tipico file di salvataggio di Tilt Brush, ad esempio quando l'artista seleziona un'opzione. sul riquadro degli strumenti e sulla posizione del widget Mirroring.
Nell'elaborazione dei 4 TB di dati acquisiti, una delle sfide maggiori è stata allineando tutte le diverse origini visive/dati. Ogni video di una fotocamera DSLR devono essere allineati con il Kinect corrispondente, in modo che i pixel spazio e tempo. Poi il filmato di questi due supporti per videocamere doveva allineati tra loro per formare un unico artista. Poi dovevamo allineare i modelli 3D, artista con i dati acquisiti dal suo disegno. Finalmente. Abbiamo scritto modelli per svolgere la maggior parte di queste attività, ma puoi provarle anche tu qui
Una volta allineati i dati, abbiamo utilizzato alcuni script scritti in NodeJS per elaborarli e restituisce un file video e una serie di file JSON, tutti tagliati e sincronizzati. Per ridurre le dimensioni del file, abbiamo fatto tre cose. In primo luogo, abbiamo ridotto la precisione di ogni numero con rappresentazione in virgola mobile al massimo pari a 3. di precisione dei decimali. In secondo luogo, riduciamo il numero di punti di un terzo 30 f/s e l'interpolazione delle posizioni lato client. Infine, abbiamo serializzato pertanto, invece di utilizzare un file JSON semplice con coppie chiave/valore, viene calcolato un ordine creato per la posizione e la rotazione dell'HMD e dei controller. Questa operazione ha tagliato il file fino a un massimo di 3 MB, che era accettabile per l'erogazione tramite cavo.
Poiché il video stesso viene pubblicato come elemento video HTML5 letto da un La texture WebGL diventava particelle, il video stesso doveva essere nascosto nel sfondo. Uno Shar converte i colori delle immagini di profondità in posizioni all'interno spazio 3D. James George ha condiviso un ottimo esempio di come fare con i filmati che provengono direttamente da DepthKit.
iOS prevede restrizioni relative alla riproduzione di video in linea, che presupponiamo abbiano lo scopo di impedire gli utenti di essere infastiditi dagli annunci video sul web che vengono riprodotti automaticamente. Abbiamo utilizzato una tecnica simili ad altre soluzioni alternative sul web, ovvero copiare frame video in una tela e aggiornare manualmente il tempo di ricerca del video, ogni 1/30 di un secondo.
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 );
Il nostro approccio ha avuto lo sfortunato effetto collaterale di una riduzione significativa di iOS della frequenza fotogrammi poiché la copia del buffer di pixel dal video al canvas è molto che richiede molta CPU. Per aggirare questa limitazione, abbiamo semplicemente pubblicato versioni più piccole dei gli stessi video che consentono almeno 30 fps su un iPhone 6.
Conclusione
Il consenso generale per lo sviluppo di software per la realtà virtuale al 2016 è di mantenere le geometrie e gli shaker, in modo da poter funzionare a 90+ f/s in un HMD. Questo si è rivelato un ottimo target per le demo di WebGL poiché le tecniche utilizzate nella mappa di Tilt Brush a WebGL.
Sebbene i browser web visualizzino mesh 3D complesse non sia interessante stessa, era una proof of concept che l'analisi incrociata tra il lavoro in VR e il il web è del tutto possibile.