虛擬藝術課程

Art Session 詳細資料

摘要

六位藝術家受邀在 VR 中繪畫、設計及雕塑。這是我們如何記錄工作階段、轉換資料,並透過網路瀏覽器即時呈現這些資料的程序。

https://g.co/VirtualArtSessions

「活起來」!隨著虛擬實境技術成為消費性產品問世,人們開始探索無限的可能。「傾斜筆刷」是 HTC Vive 上提供的 Google 產品,可讓您在 3D 空間中畫出圖形。我們第一次嘗試傾斜筆刷時,如果選擇使用動作追蹤控制器來繪圖,就會感受到「在房間有超能力的房間」下待你,因此真正有如能在周遭空白空間中畫出來的效果。

虛擬藝術品

Google 的資料藝術團隊提出了一項挑戰,希望讓沒有 VR 頭戴式裝置的使用者能夠在網路上使用傾斜筆觸功能。為了達成這個目標,我們的團隊邀請了雕塑家、插畫家、概念設計師、時尚藝術家、裝置藝術藝術家和街頭藝術家,打造符合自身風格的新媒介。

以虛擬實境技術錄製繪圖

內建於 Unity 中的傾斜筆刷軟體是電腦應用程式,透過空間縮放 VR 追蹤頭部位置 (頭戴式螢幕或 HMD) 和每隻手中的控制器。根據預設,使用傾斜筆刷建立的圖片會匯出為 .tilt 檔案。為了將這項服務引進網路 我們實現了除了圖片資料以外我們與魔幻畫筆團隊密切合作,以修改傾斜筆刷,以每秒 90 次匯出復原/刪除動作,以及藝術家的頭部和手部位置。

繪圖時,傾斜筆刷會擷取控制器的位置和角度,並隨著時間將多個點轉換為「筆劃」。您可以在這裡查看範例。我們編寫了可擷取這些筆劃,並以原始 JSON 輸出的外掛程式。

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

上述程式碼片段概述了素描 JSON 格式的格式。

這裡的每個筆劃會儲存為動作,類型為「STROKE」。除了筆劃動作之外,我們還希望顯示藝人犯錯,並改變他們草圖,因此務必儲存「DELETE」動作,做為整個筆劃的清除或復原動作。

系統會儲存每種筆劃的基本資訊,以便收集筆刷類型、筆刷大小、顏色 rgb。

最後,筆劃的每個頂點都會儲存,其中包括位置、角度、時間,以及控制器的觸發壓力強度 (每個點內會以 p 表示)。

請注意,旋轉是 4 元件四元數。當我們之後轉譯筆觸時,這點很重要,以避免以雜訊鎖定為目標。

使用 WebGL 播放後素描寫

為了在網路瀏覽器中顯示素描,我們使用 THREE.js 並編寫幾何產生程式碼,讓「Tlt Brush」直接模擬其運作方式。

雖然傾斜筆刷會根據使用者的手動作即時產生三角形條紋,但在我們在網路上顯示草圖時,整個草圖已經「完成」。這樣一來,我們就能在載入時略過大部分即時計算作業,並在載入時製作幾何圖形。

WebGL 素描

筆劃中每一組端點都會產生一個方向向量 (如上所示,連接每個點的藍色線,如下方程式碼片段所示的 moveVector)。每個點也都包含一個方向,這個四元數代表控制器目前的角度。為產生三角形條紋,我們會對每個點進行疊代,產生與方向和控制器方向一致的正常值。

計算每次筆劃三角形條紋的程序與 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);
}

結合筆劃方向和方向本身,會傳回模糊不清的結果;可能會產生多個法線,而這些常態通常會產生「扭轉」。

疊代筆劃點時,我們會保留「偏好右側」向量,並將其傳遞至 computeSurfaceFrame() 函式。這個函式提供了一種正常現象,我們可根據筆劃方向 (從最後一個點到目前點) 和控制器的方向 (四元數),在四條狀線中立出四柱方的頻道。更重要的是,系統也會傳回新的「偏好右側」向量來進行下一組計算。

筆觸

根據每個筆劃的控制點產生四邊後,我們會藉由插邊邊角 (四角至下一個四角) 來融合四角

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 );
}
融合的四頭肌
被融合的四邊形。

每個四柱也都有 UV 會在下一個步驟產生。部分筆刷包含多種筆劃圖案,讓人誤以為每次筆劃都像畫筆不同的筆觸。做法是使用「紋理」圖集,其中每個筆刷紋理都包含所有可能的變化版本。修改筆劃 UV 值以選擇正確的紋理。

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

    });

}
油畫的紋理圖集提供四個紋理
為油筆刷子的紋理圖集提供 4 種紋理
魔幻畫筆
傾斜筆刷
使用 WebGL
在 WebGL 中

由於每個草圖的筆劃數量不受限,而且筆劃不需要在執行階段中修改,因此我們預先計算筆劃幾何圖形,然後將這些筆劃合併為一個網格。即使每種新筆刷類型都必須具備自己的材質,仍會減少每個筆刷的繪製呼叫次數。

上述所有草圖都會在 WebGL 中透過一次繪製呼叫執行
以上草圖會在 WebGL 中透過一次繪製呼叫執行

為了對系統進行壓力測試,我們建立了一套草圖,花費 20 分鐘來填滿空間,並盡可能加入最多的頂點。產生的草圖仍會在 WebGL 中以 60 FPS 播放。

由於筆劃的原始頂點也包含時間,因此可以輕鬆播放資料。重新計算每個影格的筆觸會非常緩慢,因此我們在載入時預先計算整個草圖,並在需要時逐一顯示每個四元組。

隱藏四角形就是將頂點收合到 0,0,0 點。當時間達到四點應顯示的點時,我們就會將頂點重新定位。

改善的部分是用著色器完全操控 GPU 上的頂點。目前的實作方式是循環檢視目前時間戳記的頂點陣列,檢查需要顯示哪些端點,然後更新幾何圖形。這會對 CPU 造成大量負載,導致風扇旋轉以及浪費電池續航力。

虛擬藝術品

為藝人錄製

我們覺得自己草圖是不夠的。我們想讓藝人親自體驗他們素描的畫面,為每次筆刷畫出筆跡。

為了拍攝這些藝術家,我們使用 Microsoft Kinect 相機記錄藝術家在太空中人的深度資料。如此一來,我們就能在顯示繪圖的相同空間中 呈現三個維度圖形

因為藝術家的身體會遭到遮擋,導致我們無法查看背後的內容,因此我們在房間的對面使用雙 Kinect 系統,讓兩個 Kinect 的系統位於房間中心的對面。

除了深度資訊外,我們也使用標準數位單眼相機擷取場景的色彩資訊。我們使用出色的 DepthKit 軟體校正及合併深度相機和彩色鏡頭拍攝的片段。Kinect 可以記錄色彩,但我們選擇使用數位單眼相機,因為我們可以控制曝光設定、使用美麗的高階鏡頭,以及錄製高畫質內容。

為了錄製影片,我們特別建立一個特別的房間,來存放 HTC Vive、藝術家和相機。所有表面都上面有吸收紅外線的材料,可提供更乾淨的點雲 (牆壁上的灰泥,在地板上灌木叢)。如果素材出現在雲端片段中,我們選擇了黑色材料,因此不會像白色的東西一樣令人分心。

唱片藝人

產生的錄影結果提供了充足資訊,可用來投影粒子系統。我們在 openFrameworks 中編寫了一些其他工具,希望進一步清理影像片段,尤其是移除地板、牆壁和天花板。

錄影工作階段的全部四個頻道 (上方兩個色彩管道,如下所示)
錄影工作階段的四個聲道 (上方有兩個色彩頻道,如以下兩個色版)

除了展示藝人作品之外,我們也希望以 3D 模式算繪 HMD 和控制器。這不僅有助於清楚呈現最終輸出內容中的 HMD (HMD Vive 的反光鏡射掉了 Kinect 的 IR 讀數),還讓我們的聯絡窗口能對粒子輸出內容進行偵錯,以及將影片與草圖彙整在一起。

頭戴式螢幕、控制器和粒子排開
頭戴式螢幕、控制器和粒子已排開

做法是將自訂外掛程式寫入 Tilt Brush,然後擷取 HMD 的位置和每個影格的位置。自從傾斜筆刷以每秒 90 個影格的速度執行,因此串流的大量資料已串流,且草圖的輸入資料超過 20 MB 未壓縮。我們也使用這項技巧擷取未記錄於一般「傾斜筆刷」儲存檔案中的事件,例如藝術家在工具面板中選取選項時,以及鏡像小工具的位置。

在處理我們擷取的 4 TB 資料時,最大的挑戰之一,就是必須對齊不同的視覺/資料來源。DSLR 相機的每部影片都必須與對應的 Kinect 對齊,以便讓像素在空間中與時間對齊。然後,這兩個鏡頭架的片段必須彼此對齊,才能形成單一藝術家。然後,我們需要將 3D 藝人與從圖畫擷取的資料對齊。大功告成!我們編寫了瀏覽器式工具,來協助處理大部分的工作,您可以在這裡自行試用這些工具

錄音家
資料對齊後,我們使用 NodeJS 編寫的部分指令碼處理所有資料,並輸出一個影片檔案和一系列 JSON 檔案,全面剪輯和同步資料。我們有三個做法能夠縮減檔案大小,我們先降低每個浮點數的精確度,使其達到最多小數點後 3 位。其次,我們將資料點減少 30 FPS 到 30fps,並在用戶端插入位置。最後,我們將資料序列化,避免使用純 JSON 搭配鍵/值組合,而是針對 HMD 和控制器的位置和旋轉建立值的順序。這樣可以將檔案大小縮減為 3mb 這個 3mb (允許透過線路傳輸)。
唱片藝人

影片本身是以 HTML5 影片元素的形式提供,可由 WebGL 紋理讀取並成為粒子,因此影片本身必須在背景播放。著色器會將深度圖像的顏色轉換為 3D 空間中的位置。James George 分享瞭如何處理直接使用 DepthKit 影片片段的絕佳範例

iOS 對內嵌影片播放設有限制,假設使用自動播放功能的網頁廣告,就不會讓使用者受到干擾。我們使用與網路上的其他解決方法類似的技巧,也就是將影片畫面複製到畫布上,並每 1/30 一秒手動更新影片搜尋時間。

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

我們的做法可大幅降低 iOS 影格速率的副作用,因為將像素緩衝區從影片複製到畫布會大量使用 CPU 資源。為瞭解決這個問題,我們單純為同一部影片提供較小版本,而 iPhone 6 上允許至少 30fps 的影片。

結語

截至 2016 年為止,VR 軟體開發的共識是保持簡單的幾何圖形和著色器,以便在 HMD 中以 90 以上的每秒影格數執行。後來,Tlt Brush 地圖使用的技巧極佳,採用 WebGL 技術,因此成為 WebGL 示範的理想目標。

雖然網路瀏覽器顯示複雜的 3D 網格並不令人興奮,但這可是跨輪詢 AI 工作與網路的終極概念驗證。