Résumé
Six artistes y ont été invités à peindre, concevoir et sculpter en réalité virtuelle. Il s'agit de la concernant l'enregistrement des sessions, la conversion des données et la présentation en temps réel avec des navigateurs Web.
https://g.co/VirtualArtSessions
Quelle époque pour être vivant ! Avec l'introduction de la réalité virtuelle pour les consommateurs, produit, des possibilités nouvelles et inexplorées sont découvertes. Tilt Brush, un produit Google disponible sur le HTC Vive, vous permet de dessiner dans un espace dimensionnel. Lorsque nous avons essayé Tilt Brush, à l'aide de contrôleurs avec suivi du mouvement, associés à la présence une pièce aux superpouvoirs » s’attarde avec vous ; il n'y a pas vraiment d'expérience comme de pouvoir dessiner dans l'espace vide autour de vous.
L'équipe Google Data Arts a dû relever le défi de mettre en valeur pour les personnes qui n'ont pas de casque de RV, sur le Web, lorsqu'elles ne peuvent pas mais encore fonctionner. C'est pourquoi l'équipe a fait appel à un sculpteur, un illustrateur, un concepteur conceptuel, un artiste de mode, un artiste spécialisé dans les installations et des artistes de rue pour créer des œuvres d'art dans leur propre style sur ce nouveau support.
Enregistrement de dessins en réalité virtuelle
Développé dans Unity, le logiciel Tilt Brush est une application de bureau qui
utilise la réalité virtuelle à l'échelle de la pièce pour suivre la position de votre tête (moniteur frontal ou HMD)
et les contrôleurs entre vos mains. Les illustrations créées dans Tilt Brush sont
exporté par défaut en tant que fichier .tilt
. Pour proposer cette expérience sur le Web, nous
nous avons réalisé qu'il nous fallait plus
que des données sur les illustrations. Nous avons travaillé en étroite collaboration avec
Modification de l'équipe Tilt Brush pour qu'elle exporte également les actions d'annulation et de suppression
comme la tête et la main de l'artiste 90 fois par seconde.
Lorsque vous dessinez, Tilt Brush prend la position et l'angle de la télécommande plusieurs points au fil du temps en un « trait ». Voici un exemple cliquez ici. Nous avons écrit des plug-ins qui extraient ces traits et les génèrent au format JSON brut.
{
"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
]
}
L'extrait de code ci-dessus décrit le format JSON des esquisses.
Ici, chaque trait est enregistré en tant qu'action, avec un type : "STROKE". En plus de nous voulons montrer un artiste qui fait des erreurs et change à l'esprit en cours de croquis, il était donc essentiel d'enregistrer « DELETE » actions qui servent de effacer ou annuler des actions pour un trait entier.
Les informations de base pour chaque trait sont enregistrées, de sorte que le type de pinceau, la taille du pinceau, la couleur RVB sont toutes collectées.
Enfin, chaque sommet du trait est enregistré
et cela inclut la position,
l'angle, l'heure et l'intensité de la pression de déclenchement de la télécommande (notée p
dans chaque point).
Notez que la rotation est un quaternion à quatre composants. Ceci est important ultérieurement lorsque nous affichons les traits pour éviter le verrouillage du cardan.
Lire des croquis avec WebGL
Afin de montrer les croquis dans un navigateur Web, nous avons utilisé THREE.js et écrit un code de génération de géométrie pour imiter ce que fait Tilt Brush en arrière-plan.
Alors que Tilt Brush génère des bandes triangulaires en temps réel à partir de la main de l'utilisateur mouvement, l'intégralité de l'esquisse est déjà "terminée" au moment où nous les montrons sur le Web. Cela nous permet de contourner une grande partie du calcul en temps réel et de créer la géométrie lors du chargement.
Chaque paire de sommets d'un trait produit un vecteur de direction (les lignes bleues
en connectant chaque point comme indiqué ci-dessus, moveVector
dans l'extrait de code ci-dessous).
Chaque point contient aussi une orientation, un quaternion qui représente
l'angle actuel de la télécommande. Pour produire une bande triangulaire, nous effectuons une itération sur chacune des
ces points produisent des normales
perpendiculaires à la direction et
l'orientation de la manette.
Le processus de calcul de la bande triangulaire pour chaque trait est presque identique au code utilisé dans 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);
}
Combiner la direction du trait et l'orientation revient à des résultats mathématiquement ambigus ; il pourrait y avoir plusieurs normales dérivées et générait souvent un "torsion" dans la géométrie.
Lors d'une itération sur les points d'un trait, nous maintenons une « droite de préférence »
vecteur et le transmettre à la fonction computeSurfaceFrame()
. Cette fonction
nous donne une normale à partir de laquelle nous pouvons déduire un quad de la quadruple, en nous basant sur
la direction du trait (du dernier point au point actuel) et
l'orientation du contrôleur (quaternion). Plus important encore, il renvoie également
un nouveau "droit préféré" pour l'ensemble de calculs suivant.
Après avoir généré des quads en fonction des points de contrôle de chaque trait, nous fusionnons quad en interpolant leurs angles, d'un quad à l'autre.
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 );
}
Chaque quad contient également des UV qui sont générés lors de l'étape suivante. Quelques pinceaux contiennent une variété de motifs de trait pour donner l’impression que chaque trait j'ai ressenti un trait différent du pinceau. Pour ce faire, _atlasing, _où chaque texture de pinceau contient toutes les variantes. La texture appropriée est sélectionnée en modifiant les valeurs UV du ou un trait.
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 );
});
}
Étant donné que chaque esquisse a un nombre illimité de traits, les traits n'auront pas besoin de nous précalculons la géométrie du trait à l'avance et fusionnons en un seul maillage. Même si chaque nouveau type de pinceau doit être propre matériau, ce qui réduit quand même nos appels de dessin à un par pinceau.
<ph type="x-smartling-placeholder">.Pour tester le système, nous avons créé un croquis qui a pris 20 minutes à remplir le avec autant de sommets que possible. Le croquis obtenu était toujours 60 FPS en WebGL.
Étant donné que chacun des sommets d'origine d'un trait contenait également du temps, nous pouvons lire facilement les données. Recalculer les traits par image serait vraiment Nous avons donc précalculé l'intégralité du schéma lors du chargement et avons simplement révélé chaque quad quand le moment est venu de le faire.
Masquer un quad signifie simplement réduire ses sommets au point 0,0,0. Lorsque a atteint le point où le quad est censé être révélé, repositionner les sommets en place.
Un domaine à améliorer consiste à manipuler les sommets entièrement sur le GPU avec les nuanceurs. L'implémentation actuelle les place en boucle dans le sommet à partir de l'horodatage actuel, en vérifiant quels sommets doivent être révélés puis la mise à jour de la géométrie. Cela exerce une charge importante sur le CPU, ce qui entraîne faire tourner le ventilateur, ce qui gaspille l'autonomie de la batterie.
Enregistrement des artistes
Nous avions l'impression que les croquis ne suffisaient pas. Nous voulions montrer dans leurs croquis, en peignant chaque coup de pinceau.
Pour capturer les artistes, nous avons utilisé des appareils photo Microsoft Kinect afin d'enregistrer la profondeur. les données des artistes dans l'espace. Cela nous permet de montrer des figures en trois dimensions au même endroit que les dessins.
Puisque le corps de l'artiste s'occlurait, nous ne pourrions pas voir nous avons utilisé un double système Kinect, de part et d'autre de la pièce pointant vers le centre.
En plus des informations de profondeur, nous avons également capturé les informations sur les couleurs des avec un reflex numérique standard. Nous avons utilisé l'excellent Logiciel DepthKit pour le calibrage et la fusion les séquences vidéo des caméras de profondeur et de couleur. Le Kinect est capable de couleurs d'enregistrement, mais nous avons choisi d'utiliser un reflex numérique, car nous pouvions contrôler d'exposition, utilisez de magnifiques objectifs haut de gamme et enregistrez en haute définition.
Pour enregistrer la vidéo, nous avons construit une salle spécialement conçue pour accueillir l'artiste HTC Vive. et la caméra. Toutes les surfaces étaient recouvertes d'un matériau qui a absorbé l'infrarouge afin d'obtenir un nuage de points plus propre (duvetyne sur les murs, caoutchouc à côtes) au sol). Si le matériau apparaît dans le nuage de points nous avons choisi une matière noire pour ne pas être aussi gênante qui était blanc.
Les enregistrements vidéo obtenus nous ont fourni suffisamment d'informations pour projeter une particule du système d'exploitation. Nous avons vu quelques outils supplémentaires dans openFrameworks pour nettoyer davantage l'enregistrement, notamment le retrait des sols, des murs et des plafonds.
<ph type="x-smartling-placeholder">.En plus de montrer les artistes, nous voulions effectuer le rendu du HMD et du et des manettes de jeu en 3D. C'était non seulement important pour montrer le HMD dans la sortie finale était nette (les lentilles réfléchissantes du HTC Vive étaient les relevés infrarouges de Kinect), cela nous a donné des points de contact pour le débogage de la particule la sortie et en alignant les vidéos avec le croquis.
<ph type="x-smartling-placeholder">.Pour ce faire, un plug-in personnalisé extrait le fichier les positions du HMD et contrôler chaque trame. Puisqu'il s'exécute à 90 ips, des tonnes de données étaient diffusées et les données d'entrée d'un croquis étaient supérieures à 20 Mo non compressées. Nous avons également utilisé cette technique pour capturer des événements qui ne sont pas enregistrés. dans un fichier de sauvegarde Tilt Brush classique, par exemple lorsque l'artiste sélectionne une option ; dans le panneau des outils et la position du widget en miroir.
Lors du traitement des 4 To de données collectées, l'un des plus grands défis consistait à d’aligner toutes les différentes sources visuelles/de données. Chaque vidéo d'un reflex numérique doivent être alignés avec la Kinect correspondant, de sorte que les pixels soient alignés l’espace ainsi que le temps. Il fallait ensuite que les images de ces deux supports les uns avec les autres pour former un artiste unique. Ensuite, nous devions aligner notre 3D artiste avec les données capturées à partir de son dessin. Ouf ! Nous avons écrit la conception d'applications basées sur les navigateurs pour effectuer la plupart de ces tâches. Vous pouvez les essayer vous-même cliquez ici
Une fois les données alignées, nous avons utilisé des scripts écrits en NodeJS pour les traiter. et générer un fichier vidéo ainsi qu'une série de fichiers JSON, tous édités et synchronisé. Pour réduire la taille du fichier, nous avons fait trois choses. Tout d'abord, nous avons réduit l'exactitude de chaque nombre à virgule flottante de sorte qu'il soit égal ou supérieur à 3 la valeur décimale d'une décimale. Ensuite, nous réduisons le nombre de points d'un tiers pour 30 FPS et interpolation des positions côté client. Enfin, nous avons sérialisé Ainsi, au lieu d'utiliser un fichier JSON ordinaire avec des paires clé/valeur, un ordre des valeurs créé pour la position et la rotation du HMD et des contrôleurs. Cela a coupé le fichier à moins de 3 Mo, ce qui était acceptable pour une connexion par câble.
Étant donné que la vidéo est diffusée comme un élément vidéo HTML5 lu par un pour devenir des particules, la vidéo elle-même devant être lue dans la en arrière-plan. Un nuanceur convertit les couleurs de l'image de profondeur en positions Espace 3D. James George nous présente un excellent exemple des vidéos provenant directement de DepthKit.
iOS impose des restrictions concernant la lecture des vidéos intégrées, ce qui permet d'éviter d'être agacés par des annonces vidéo Web qui se lancent automatiquement. Nous avons utilisé une technique similaire à d'autres solutions de contournement Web, qui consiste à copier l'image vidéo en canevas et mettre à jour manuellement la durée de la recherche de la vidéo tous les 30 secondes une seconde.
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 );
Malheureusement, notre approche a eu l'effet négatif d'une baisse significative de la vidéo, car la copie du tampon de pixels entre la vidéo et le canevas et utilisent une utilisation intensive du processeur. Pour contourner ce problème, nous avons simplement proposé des versions de plus petite taille les mêmes vidéos qui permettent une fréquence d'au moins 30 FPS sur un iPhone 6.
Conclusion
Le consensus général sur le développement de logiciels de RV à partir de 2016 est de garder des géométries et des nuanceurs pour une exécution à plus de 90 FPS dans un HMD. Ce s'est avérée une très bonne cible pour les démonstrations WebGL, car les techniques utilisées de l'outil d'inclination au WebGL.
Si les navigateurs Web affichant des maillages 3D complexes ne sont pas passionnants, il s'agissait d'une preuve de concept selon laquelle la pollinisation croisée du travail en RV Web est tout à fait possible.