虚拟 Art Session

Art Session 详情

摘要

六位艺术家受邀在 VR 中绘画、设计和雕刻。这个过程介绍了我们记录会话、转换数据以及通过网络浏览器实时呈现这些数据的过程。

https://g.co/VirtualArtSessions

活在当下真是太快了!随着虚拟现实作为消费类产品的推出,人们正在探索新的和尚未探索的可能性。Tilt Brush 是 HTC Vive 上提供的一款 Google 产品,可让您在三维空间中绘制内容。当我们首次尝试使用 Tilt Brush 时,使用运动跟踪控制器进行绘制的感觉仍然存在于“在具有超能力的房间中”的感觉,而这完全不像在周围的空白空间中绘制一样。

虚拟艺术作品

Google 的 Data Arts 团队面临着一项挑战,即如何在尚未运行 Tilt Brush 的网络环境中向没有使用 VR 头戴设备的用户展示这种体验。为此,该团队邀请了雕塑家、插画家、概念设计师、时尚艺术家、装置艺术家和街头艺术家,在这个新媒介上创作了他们自己的风格艺术作品。

在虚拟实境中录制绘图

Tilt Brush 软件本身内置于 Unity 中,它是一款桌面应用,使用室内规模的 VR 来跟踪头部位置(头戴式显示器,即 HMD)和您每只手中的控制器。在 Tilt Brush 中创建的海报图片默认以 .tilt 文件的形式导出。为了将这种体验带入网络,我们意识到 我们需要的不仅仅是海报图片数据。我们与 Tilt Brush 团队密切合作,修改了 Tilt Brush,因此它可以以每秒 90 次的速度导出撤消/删除操作以及音乐人的头部和手部位置。

在绘图时,Tilt Brush 会获取您的控制器位置和角度,并随着时间将多个点转换为“笔触”。您可以点击此处查看示例。我们编写了用于提取这些笔画并将其输出为原始 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 并编写了几何生成代码来模仿 Tilt Brush 在后台执行的操作。

虽然 Tilt 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 );

    });

}
油画笔纹理图集中的四个纹理
油画笔纹理图集中的四个纹理
In Tilt Brush
In Tilt Brush
在 WebGL 中
在 WebGL 中

由于每个草图的描边数量不受限制,并且在运行时无需修改描边,因此我们提前预计算描边几何图形,并将它们合并到一个网格中。虽然每个新的 Brush 类型都必须采用自己的材质,但这仍会将我们的绘制调用减少到每个 Brush 一次。

以上整个草图都是在 WebGL 中的一次绘制调用中执行。
以上整个草图都是在 WebGL 中的一次绘制调用中执行

为了对系统进行压力测试,我们创建了一个草图,用了 20 分钟的时间尽可能多地用顶点填充空间。生成的草图仍会在 WebGL 中以 60fps 的速率播放。

由于描边的每个原始顶点也包含时间,因此我们可以轻松回放数据。每帧重新计算笔画非常缓慢,因此我们在加载时预先计算了整个草图,并在需要执行此操作时简单地显示每个四边形。

隐藏四边形仅仅意味着将其顶点折叠到 0,0,0 点。当时间到了应该显示四边形的点时,我们会将顶点重新放置到位。

一个需要改进的方面是,使用着色器完全在 GPU 上操控顶点。当前实现通过以下方式放置这些顶点:从当前时间戳循环遍历顶点数组,检查需要显示哪些顶点,然后更新几何图形。这会给 CPU 带来大量负载,导致风扇转动,并浪费电池续航时间。

虚拟艺术作品

录制音乐人的作品

我们认为这些草图本身是不够的。我们希望向艺术家展示他们的素描,让每一个笔触都呈现在用户面前。

为了拍摄音乐人的作品,我们使用 Microsoft Kinect 相机记录了音乐人在太空中身体的深度数据。这样我们就能在与绘画相同的空间里展示他们的三维形状了

由于画家的身体是被遮挡,所以我们无法查看后面的内容,因此我们使用了双 Kinect 系统,在房间两侧指向中心。

除了深度信息之外,我们还使用标准的数码单反相机捕获了场景的颜色信息。我们使用出色的 DepthKit 软件校准并合并来自深度相机和彩色相机的视频片段。虽然 Kinect 能够记录色彩,但我们选择使用数码单反相机,因为我们可以控制曝光设置,使用精美的高端镜头,并以高清画质进行录制。

为了录制视频片段,我们建造了一个特殊房间,用来容纳 HTC Vive、音乐人和相机。所有表面都覆盖了吸收红外光的材料,呈现更清洁的点云层(墙壁上铺有薄垫,地板上铺有罗纹橡胶垫)。如果材料出现在点云视频片段中,我们选择了黑色材料,这样就不会像白色材料那样分散注意力。

唱片音乐人

所得到的视频录制内容为我们提供足够的信息来投射粒子系统。我们在 openFrameworks 中编写了一些其他工具,以进一步清理视频片段,特别是去除地板、墙壁和天花板。

录制的视频会话的所有四个通道(上方两个颜色通道,下方两个颜色通道)
录制的视频会话的所有四个频道(上方两个颜色通道,下方两个颜色通道)

除了展示音乐人之外,我们还希望以 3D 形式渲染 HMD 和控制器。这不仅对在最终输出中清晰显示 HMD 非常重要(HTC Vive 的反射镜头反射出 Kinect 的红外线读数),还为我们提供了用于调试粒子输出以及将视频与草图对齐的接触点。

头戴式显示器、控制器和粒子排列在一起
对齐的头戴式显示屏、控制器和粒子

为此,我们在 Tilt Brush 中编写了一个自定义插件,用于提取 HMD 和每帧控制器的位置。由于 Tilt Brush 以 90fps 的速率运行,因此有大量数据会排出,草图的输入数据未经压缩就超过了 20 MB。我们还使用此技术来捕获未记录在典型 Tilt Brush 保存文件中的事件,例如,艺术家在工具面板上选择一个选项时以及镜像 widget 的位置。

在处理我们捕获的 4TB 数据时,最大的挑战之一是对齐所有不同的可视化/数据源。数码单反相机的每个视频都需要与相应 Kinect 对齐,以便像素在空间和时间上对齐。然后,需要将来自这两台摄像机装置的视频片段相互对齐才能组成一位音乐人。然后,我们需要将 3D 艺术家的数据与从他们的画作中采集到的数据对齐。好了!我们编写了基于浏览器的工具来帮助完成大多数此类任务。您可以点击此处自行试用这些工具

唱片音乐人
数据校准完毕后,我们使用一些用 NodeJS 编写的脚本来处理所有这些数据,然后输出一个视频文件和一系列 JSON 文件,这些文件全部经过修剪和同步。为了减小文件大小,我们采取了三项措施。首先,我们降低了每个浮点数的精度,使其达到小数点后 3 位的精度。其次,我们将数据点数量减少三分之一,即 30fps,并在客户端对位置进行插值。最后,我们对数据进行了序列化,因此不是使用带有键值对的普通 JSON,而是针对 HMD 和控制器的位置和旋转创建值顺序。这会将文件大小缩减至只有 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 上以 90fps 以上的速率运行。事实证明,这非常适合 WebGL 演示,因为 Tilt Brush 中使用的技术与 WebGL 非常契合。

虽然显示复杂 3D 网格的网络浏览器本身并不令人兴奋,但这只是一个概念证明,交叉传授 VR 工作和网络是完全可行的。