Sanal Sanat Oturumları

Sanat Oturumu ayrıntıları

Özet

Altı sanatçı sanal gerçeklikte resim yapmak, tasarım yapmak ve heykel yapmak üzere davet edildi. Bu, oturumlarını kaydetme, verileri dönüştürme ve web tarayıcıları ile gerçek zamanlı olarak sunma sürecidir.

https://g.co/VirtualArtSessions

Ne zaman yaşıyor olmak! Sanal gerçekliğin tüketici ürünü olarak kullanılmaya başlanmasıyla birlikte yeni ve keşfedilmemiş olanaklar keşfediliyor. HTC Vive'da bulunan bir Google ürünü olan Tilt Brush, üç boyutlu uzayda çizim yapmanıza olanak tanır. Tilt Brush'ı ilk kez denediğimizde, hareket izleyen kumandalarla çizim hissi ve"süper güçlere sahip bir odada" olmanın varlığı hâlâ duruyor. Etrafınızdaki boş alanlara çizim yapmak gibi bir deneyim yok.

Sanal sanat eseri

Google Veri Sanatları Ekibi, bu deneyimi VR başlığı olmayan, Tilt Brush'ın henüz çalışmadığı web'de ise onlara sergilemekle görevlendirildi. Ekip bu amaçla, bu yeni ortam içinde kendi tarzlarında sanat eserleri oluşturmaları için bir heykeltıraş, çizgi ustası, konsept tasarımcısı, moda sanatçısı, enstalasyon sanatçısı ve sokak sanatçısını görevlendirdi.

Sanal Gerçeklikte Çizimleri Kaydetme

Unity'de yerleşik olarak bulunan Tilt Brush yazılımı, başınızın konumunu (başa takılan ekran veya HMD) ve her bir elinizdeki kumandaları izlemek için oda ölçeğinde VR kullanan bir masaüstü uygulamasıdır. Tilt Brush'ta oluşturulan posterler varsayılan olarak .tilt dosyası olarak dışa aktarılır. Bu deneyimi web'e taşımak için basit sanat verilerinden daha fazlasına ihtiyacımız olduğunu fark ettik. Tilt Brush'ı değiştirmek için Tilt Brush ekibiyle yakın bir çalışma yürüttük. Böylece, geri alma/silme eylemlerinin yanı sıra sanatçının başı ile el pozisyonları saniyede 90 kez dışa aktarıldı.

Çizim yaparken Tilt Brush kumandanın konumunu ve açısını alıp zaman içinde birden fazla noktayı "fırça"ya dönüştürür. Burada bir örnek görebilirsiniz. Bu çizgileri ayıklayan ve ham JSON dosyası olarak çıkaran eklentiler yazdık.

    {
      "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
      ]
    }

Yukarıdaki snippet, taslak JSON biçiminin biçimini gösterir.

Burada, her çizgi, "SORU" türüyle bir işlem olarak kaydedilir. Çizgi işlemlerine ek olarak, bir sanatçının hata yaptığını ve zihnini değiştirdiğini göstermek istedik. Bu yüzden, bir fırçanın tamamı boyunca silme veya geri alma işlemi olarak işlev gören "SİL" işlemlerini kaydetmek çok önemliydi.

Her fırçayla ilgili temel bilgiler kaydedildiği için fırça türü, fırça boyutu ve renklirgb verilerinin tümü toplanır.

Son olarak, çizginin her bir köşe noktası kaydedilir. Bu köşe konumu, açıyı, zamanı ve denetleyicinin tetikleyici basıncı gücünü içerir (her bir nokta içinde p olarak belirtilir).

Rotasyonun 4 bileşenli bir dörtlüler olduğuna dikkat edin. Bu, daha sonra gimbal kilitlerini önlemek için fırçaları oluşturduğumuzda önemlidir.

Sketch'leri WebGL ile Oynatma

Çizimleri bir web tarayıcısında göstermek için THREE.js'yi kullandık ve Tilt Brush'ın arka planda yaptığını taklit eden geometri oluşturma kodu yazdık.

Tilt Brush kullanıcının el hareketine göre gerçek zamanlı olarak üçgen şeritler oluştursa da, çizimin tamamı web'de gösterilene kadar "bitti" olmuş demektir. Bu, gerçek zamanlı hesaplamanın büyük bir kısmını atlamamıza ve yük üzerinde geometriyi oluşturmamıza olanak tanır.

WebGL Çizimler

Bir çizgideki her bir köşe çifti, bir yön vektörü (yukarıda gösterildiği gibi her bir noktayı birbirine bağlayan mavi çizgiler, aşağıdaki kod snippet'inde moveVector) üretir. Her nokta, denetleyicinin geçerli açısını temsil eden bir dörtgen şeklinde bir yön de içerir. Bir üçgen şeridi oluşturmak için, bu noktaların her biri üzerinde yineleme yaparak yöne ve denetleyicinin yönüne dik normaller oluştururuz.

Her bir çizgi için üçgen şeridi hesaplama işlemi, Tilt Brush'ta kullanılan kodla neredeyse aynıdır:

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);
}

Fırça yönü ve yönünün kendi başlarına birleştirilmesi, matematiksel olarak belirsiz sonuçlar doğurabilir. Türetilen birden fazla normal olabilir ve genellikle geometride bir "büküm"e yol açar.

Bir çizginin noktaları üzerinde yineleme yaparken "tercih edilen sağ" vektörünü korur ve bunu computeSurfaceFrame() fonksiyonuna iletiriz. Bu işlev, çizginin yönüne (son noktadan şu anki noktaya) ve denetleyicinin yönüne (dörtgen) dayalı olarak, dörtlü şeritte bir dörtlü elde edebileceğimiz normal bir değer verir. Daha da önemlisi, bir sonraki hesaplama grubu için yeni bir "tercih edilen sağ" vektörü döndürür.

Çizgiler

Her vuruşun kontrol noktalarına göre dörtlüler oluşturduktan sonra, bir dörtlüden diğerine köşelerinin interpolasyonu yaparak dörtlüleri birleştiriyoruz.

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 );
}
Çok kaynaşmış dörtlüler
Kaynaşmış dörtlüler.

Her dörtlü, sonraki adım olarak oluşturulan UV'ler de içerir. Bazı fırçalar, her fırçanın farklı bir fırça darbesiymiş gibi hissettirmesi için çeşitli fırça desenleri içerir. Bu işlem, her fırça dokusunun olası tüm varyasyonları içerdiği _doku atlama kullanılarak gerçekleştirilir. Çizginin UV değerleri değiştirilerek doğru doku seçilir.

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 );

    });

}
Yağ fırçası için doku atlastaki dört doku
Yağlı fırça için doku atlastaki dört doku
Tilt Brush'ta
Tilt Brush'ta
WebGL'de
WebGL'de

Her çizimin fırça sayısı sınırsız olduğundan ve fırçaların çalışma süresinde değiştirilmesi gerekmeyeceğinden, fırça geometrisini önceden hesaplar ve bunları tek bir ağda birleştiririz. Her yeni fırça türünün kendi malzemesi olması gerekse de, çizim çağrılarımızı fırça başına bir taneye düşürür.

Yukarıdaki çizimin tamamı WebGL'deki tek bir çizim çağrısında gerçekleştirilir
Yukarıdaki çizimin tamamı WebGL'deki tek bir çizim çağrısında gerçekleştirilir

Sisteme stres testi uygulamak için 20 dakika süren, alanı mümkün olduğunca çok köşeyle dolduran bir eskiz oluşturduk. Ortaya çıkan çizim WebGL'de 60 fps'de oynatılmaya devam ediyor.

Bir çizginin orijinal köşelerinin her biri zaman içerdiğinden, verileri kolaylıkla oynatabiliriz. Kare başına çizgi sayısını yeniden hesaplamak çok yavaş bir işti. Bu yüzden, çizimin tamamını yükleme sırasında önceden hesapladık ve zaman geldiğinde her dörtlüleri basitçe ortaya çıkardık.

Bir dörtlüyü gizlemek, köşelerinin 0,0,0 noktasına kadar daraltılması anlamına geliyordu. Zaman, dörtgenin ortaya çıkması gereken noktaya ulaştığında köşeleri tekrar yerlerine konumlandırırız.

İyileştirilmesi gereken bir alan, köşelerin tamamen GPU'da düzenlenmesi ve gölgelendirmelerin kullanılmasıdır. Mevcut uygulama, bunları geçerli zaman damgasına göre köşe dizisinde döngüye alarak, hangi köşelerin ortaya çıkması gerektiğini kontrol ederek ve ardından geometriyi güncelleyerek yerleştirir. Bu da CPU'ya çok fazla yük bindirerek fanın dönmesine ve pil ömrünün boşa harcanmasına neden olur.

Sanal sanat eseri

Sanatçıları Kaydetme

Çizimlerin tek başına yeterli olmayacağını düşündük. Her fırça darbesini boyayarak sanatçılara skeçlerinin içinde göstermek istedik.

Sanatçıların fotoğraflarını çekmek için Microsoft Kinect kameraları kullanarak sanatçıların uzaydaki derinlik verilerini kaydettik. Bu bize, çizimlerin göründüğü alanda üç boyutlu şekilleri gösterme olanağı veriyor.

Sanatçının vücudu kendini kaplayarak arkasında ne olduğunu görmemizi engelleyeceğinden, odanın her iki tarafında da merkezi gösteren çift Kinect sistemini kullandık.

Derinlik bilgilerine ek olarak, standart DSLR kameralarla sahnenin renk bilgilerini de yakaladık. Derinlik kamerası ile renkli kamera görüntülerini kalibre etmek ve birleştirmek için mükemmel DepthKit yazılımını kullandık. Kinect renk kaydedebilir ama pozlama ayarlarını kontrol edebildiğimiz, kaliteli ve güzel lensler kullanabildiğimiz ve yüksek çözünürlükte kayıt yapabildiğimiz için DSLR'leri kullanmayı tercih ettik.

Çekimi kaydetmek amacıyla HTC Vive, sanatçı ve kamera için özel bir oda oluşturduk. Bize daha temiz bir nokta bulutu (duvarlarda kum tepesi, zeminde ise oluklu kauçuk kaplama) sağlamak için tüm yüzeyler kızılötesi ışık emen malzemeyle kaplandı. Malzeme, nokta bulutu çekiminde görünürse beyaz bir şey kadar dikkat dağıtıcı olmaması için siyah materyali seçtik.

Kayıt sanatçısı

Ortaya çıkan video kayıtları, bir parçacık sistemini yansıtmamız için bize yeterli bilgi sağladı. Görüntüleri daha da netleştirmek, özellikle de zemini, duvarları ve tavanı kaldırmak için openFrameworks'te bazı ek araçlar yazdık.

Kaydedilen bir video oturumunun dört kanalı (üstte iki renk kanalı, alt kısım iki derinlik)
Kaydedilen bir video oturumunun dört kanalı (üstte iki renk kanalı, aşağıda iki derinlik)

Hem HMD'yi hem de denetleyicileri 3D olarak göstermek istedik. Bu, HMD'nin son çıktıda net bir şekilde gösterilmesi açısından önemliydi (HTC Vive'ın yansıtıcı lensleri Kinect'in IR ölçümlerini çarpıyordu), parçacık çıktısında hata ayıklamak ve videoları eskizle hizalamak için bize temas noktaları sağladı.

Başa takılan ekran, kumandalar ve cisimler hizalı
Başa takılan ekran, kumandalar ve cisimlerin hizalanması

Bunun için Tilt Brush'a HMD ve her bir karenin denetleyicilerinin konumlarını çıkaran özel bir eklenti yazıldı. Tilt Brush 90 fps'de çalıştığı için tonlarca veri çıktı ve bir çizimin giriş verisi sıkıştırılmamış halde 20 MB'tan fazlaydı. Sanatçının araçlar panelinde bir seçenek belirlemesi ve ayna widget'ının konumu gibi tipik Tilt Brush kaydetme dosyasında kaydedilmeyen etkinlikleri de yakalamak için bu tekniği kullandık.

Topladığımız 4 TB'lık veriyi işlerken karşılaşılan en büyük zorluklardan biri tüm farklı görsel/veri kaynaklarını uyumlu hale getirmekti. Bir DSLR kameradan alınan her bir videonun karşılık gelen Kinect ile hizalanması gerekir. Böylece pikseller hem zaman hem de alan içinde hizalanır. Daha sonra, bu iki kamera düzeneğinden alınan görüntülerin tek bir sanatçı oluşturmak için birbiriyle hizalanması gerekiyordu. Sonra 3D sanatçımızı, çizimlerinden elde edilen verilerle hizalamamız gerekiyordu. Bora Bu görevlerin çoğunda yardımcı olması için tarayıcı tabanlı araçlar yazdık. Bunları buradan kendiniz de deneyebilirsiniz.

Plak sanatçıları

Veriler hizalandıktan sonra, tümünü işlemek ve tümü kırpılmış ve senkronize edilmiş bir video dosyası ve JSON dosyası serisi oluşturmak için NodeJS'de yazılan bazı komut dosyalarını kullandık. Dosya boyutunu küçültmek için üç şey yaptık. İlk olarak, her bir kayan nokta sayısının doğruluğunu, maksimum 3 ondalık basamak değerinde olacak şekilde düşürdük. İkinci olarak, puan sayısını üçte bir oranında 30 fps'ye düşürdük ve istemci tarafında konumlar için interpolasyon yaptık. Son olarak, verileri serileştirdik. Böylece anahtar/değer çiftleriyle düz JSON kullanmak yerine HMD ve denetleyicilerin konumu ve rotasyonu için bir değer sırası oluşturuluyor. Bu işlem, dosya boyutunu 3 MB'a kadar kısaltarak kablo üzerinden teslim edilmesi kabul edilebilir bir boyuta düşürdü.

Kayıt sanatçıları

Videonun kendisi, parçacık haline gelmesi için bir WebGL dokusu tarafından okunan bir HTML5 video öğesi olarak sunulduğundan, videonun arka planda gizli bir şekilde oynatılması gerekiyordu. Gölgelendirici, derin görüntülerdeki renkleri 3D uzayda konumlara dönüştürür. James George, doğrudan DepthKit'ten alınmış çekimlerle nasıl yapabileceğinizi gösteren mükemmel bir örnek paylaştı.

iOS'te satır içi video oynatmayla ilgili kısıtlamalar vardır. Bu kısıtlamaların, kullanıcıların otomatik oynatılan web video reklamları tarafından rahatsız edilmesini önlemek olduğunu varsayıyoruz. Web'deki diğer geçici çözümlere benzer bir teknik kullandık. Bu teknikte, video karesini bir zemine kopyalayıp video arama süresini saniyenin 30'unda bir manuel olarak güncelleriz.

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 );

Piksel arabelleğinin videodan kanvasa kopyalanması çok CPU yoğun olduğundan, yaklaşımımızın iOS kare hızını önemli ölçüde düşürmesi gibi talihsiz bir yan etkisi oldu. Bunu aşmak için aynı videoların iPhone 6'da en az 30 fps'ye izin veren daha küçük boyutlu sürümlerini sunduk.

Sonuç

2016 itibarıyla VR yazılımı geliştirme konusunda genel kanı, HMD'de 90+ fps'de çalışabilmeniz için geometrilerin ve gölgelendiricilerin basit tutulması yönündedir. Tilt Brush haritasında WebGL'yi çok güzel bir şekilde kullanan teknikler sayesinde, bunun WebGL demoları için gerçekten harika bir hedef olduğu ortaya çıktı.

Karmaşık 3D örgüler görüntüleyen web tarayıcıları kendi başına heyecan verici olmasa da bu, VR çalışmaları ile web'in çapraz pompalanmasının tamamen mümkün olduğunun bir kanıtıydı.