在《使用ARCore创建新型用户交互体验》系列文章的前二篇中,我们分享了ARCore for Unity功能的使用方法。例如:使用运动跟踪来制作一个《抽积木》的AR游戏,或是使用光照估计功能来触发对象行为。在本文中,我们打算分享一些有意思的ARCore for Unity实验,告诉你摄像机画面生成的数据可以用来做些什么。
110007hfq1t31vony3zh1t.png

手持设备的摄像机可不只能拍照和录像
通过为用户体验提供情景数据,ARCore for Unity增强了摄像机的功能。为了给你展示它的功能,我们的工程师做了一些AR实验,下面将介绍他们的实验方法并提供相应代码片段,这样你就可以亲自去探索这些实验。这些实验内容不算多,你可以直接开始进行测试。

世界捕捉
AR情景应用是可以实时与真实世界进行交互的应用,它们或许是最为主流的使用案例之一。借助世界捕捉(World Captures)功能,你可以使用摄像机画面来捕捉并记录现实世界中的时间和空间信息,分享到应用的情景中。世界捕捉功能会在空间中生成一个平面,它会将摄像机画面中的一个截图作为纹理使用。

110013f718z6lngmlslmhs.gif

为了将摄像机画面转换为一个平面纹理,使用了CaptureScreenshotAsTexture API。当截图被采集后,就可以轻松将它作为纹理加入到一个平面的材质中,这个平面是在用户点击屏幕时生成的。需要注意:这里你需要耐心等待到那一帧画面的结束,从而为应用提供足够时间来将整个截图渲染为纹理。

下面的代码将帮助你尝试使用ARCore for Unity进行世界捕捉。

IEnumerator CaptureScreenshot() { 
  yield return new WaitForEndOfFrame(); 
  PlaneTex = ScreenCapture.CaptureScreenshotAsTexture(); 
  yield return new WaitForEndOfFrame(); 
  GameObject newPlane = Instantiate(plane, spawnPoint.position, Camera.main.transform.rotation); 
  newPlane.GetComponent<MeshRenderer>().material.mainTexture = PlaneTex; PlaneTex.Apply(); 
}

AR摄像机光照
要创作出真实世界和虚拟对象完美融合的效果十分困难,就好像它们真的存在一样。实现这个效果的一个关键因素是,使用3D数字对象周围现实的光照和反射情况来影响这些对象的行为。

110019wjf3gj9t9gnokyzf.gif
使用摄像机画面来为虚拟对象提供光照和反射效果
AR摄像机光照(AR Camera Lighting)能让你通过摄像机画面创建的天空盒来实现这个效果。你可以使用Unity场景中的天空盒来为虚拟对象增加光照,并使用反射探针从天空盒创建反射效果。
由于从摄像机视图中所捕获的画面不足以覆盖整个球体,因此这样得到的光照和反射效果并不完全准确。尽管如此,这个效果仍引人注目,尤其是在用户移动摄像机的时候和模型自身进行移动的时候,效果都很好。
为了创建球体,我们把摄像机画面转换为了一个RenderTexture,并使用GLSL着色器来将纹理载入到ARCore中。了解使用更详细使用方法以及获取实验中所用到的所有资源请访问文末资源AR Camera Lighting。

特征点颜色
特征点颜色(Feature Point Colors)功能会为你展示如何添加深度和形状,并通过视觉提示来在摄像机视图中突出显示现实世界对象上的独特元素。
使用摄像机画面放置像素立方体到可视特征点上,每个立方体会基于特征点的像素来调整颜色
文末使用了GoogleARCore的TextureReader组件来从GPU获取摄像机的原始纹理,然后通过这个纹理更好地显示出了像素。这些立方体都是根据对象池大小预先产生的,用于提高性能,它们会根据需要启用和停用。

void OnImageAvailable(
TextureReaderApi.ImageFormatType format, int width, int height, IntPtr pixelBuffer, int bufferSize) 
{ 
   if (format != TextureReaderApi.ImageFormatType.ImageFormatColor) 
     return; 
   if (bufferSize != m_PixelBufferSize m_PixelByteBuffer.Length == 0) {
   m_PixelBufferSize = bufferSize; 
   m_PixelByteBuffer = new byte[bufferSize]; m_PixelColors = new Color[width * height]; 
} 
  System.Runtime.InteropServices.Marshal.Copy(pixelBuffer, m_PixelByteBuffer, 0, bufferSize); 
  var bufferIndex = 0; 
  for (var y = 0; y < height; ++y) { 
  for (var x = 0; x < width; ++x) { 
  int r = m_PixelByteBuffer[bufferIndex++]; 
  int g = m_PixelByteBuffer[bufferIndex++]; 
  int b = m_PixelByteBuffer[bufferIndex++]; 
  int a = m_PixelByteBuffer[bufferIndex++]; 
  var color = new Color(r / 255f, g / 255f, b / 255f, a / 255f); int pixelIndex;
  switch (Screen.orientation) { 
  case ScreenOrientation.LandscapeRight: pixelIndex = y * width + width - 1 - x; break; 
  case ScreenOrientation.Portrait: pixelIndex = (width - 1 - x) * height + height - 1 - y; break; 
  case ScreenOrientation.LandscapeLeft: pixelIndex = (height - 1 - y) * width + x; break; 
  default: pixelIndex = x * height + y; break; } m_PixelColors[pixelIndex] = color; 
  } 
  } 
  FeaturePointCubes(); 
}

当能够很好表现出像素颜色后,就会遍历ARCore点云中的所有点,即直到达到对象池的上限,然后我们可以再定位屏幕空间中可见的立方体。每个立方体都会基于特征点屏幕空间位置上的像素着色。

void FeaturePointCubes() { 
foreach 
(
  var pixelObj in m_PixelObjects) {
  pixelObj.SetActive(false); 
} 
  var index = 0; 
  var pointsInViewCount = 0; 
  var camera = Camera.main; 
  var scaledScreenWidth = Screen.width / k_DimensionsInverseScale; 
  while (index < Frame.PointCloud.PointCount pointsInViewCount < poolSize) { 
  var point = Frame.PointCloud.GetPoint(index); 
  var screenPoint = camera.WorldToScreenPoint(point); 
  if (screenPoint.x >= 0 screenPoint.x < camera.pixelWidth screenPoint.y >= 0 screenPoint.y < camera.pixelHeight) { var pixelObj = m_PixelObjects[pointsInViewCount]; 
  pixelObj.SetActive(true); 
  pixelObj.transform.position = point; 
  var scaledX = (int)screenPoint.x / k_DimensionsInverseScale; 
  var scaledY = (int)screenPoint.y / k_DimensionsInverseScale; 
  m_PixelMaterials[pointsInViewCount].color = m_PixelColors[scaledY * scaledScreenWidth + scaledX]; 
  pointsInViewCount++; 
} 
  index++; 
} 
}

完整的FeaturePointColors组件代码请访问文末资源FeaturePointColors组件代码。


索贝尔空间
索贝尔空间(Sobel Spaces)是一个使用摄像机画面来显示现实世界中新图层的案例。它能够突出显示边缘,或是创建有吸引力的滤镜来调整视口。

110029b004spjoppe0oq83.gif
使用摄像机画面来将几何形状从屏幕的一侧绘制到另一侧,从而创造出有趣的空间效果

这个实验基于索贝尔算子方法实现,该方法是个从摄像机画面检测边缘的常用方法,用于生成强调边缘的图像。索贝尔空间基于ARCore SDK中的 ComputerVision示例进行了修改。所修改的内容是索贝尔分类器的工作方式:

{ var halfWidth = width / 2; int bufferSize = width * height;
  if (bufferSize != s_ImageBufferSize s_ImageBuffer.Length == 0) { s_ImageBufferSize = bufferSize; 
  s_ImageBuffer = new byte[bufferSize]; 
} 
System.Runtime.InteropServices.Marshal.Copy(inputImage, s_ImageBuffer, 0, bufferSize); 
  for (int j = 1; j < height - 1; j += 2) { for (int i = 1; i < width - 1; i += 2) 
  { 
    int offset = (j * width) + i; byte pixel = s_ImageBuffer[offset]; 
    int a00 = s_ImageBuffer[offset - halfWidth - 1];
    int a01 = s_ImageBuffer[offset - halfWidth]; 
    int a02 = s_ImageBuffer[offset - halfWidth + 1]; 
    int a10 = s_ImageBuffer[offset - 1];
    int a12 = s_ImageBuffer[offset + 1]; 
    int a20 = s_ImageBuffer[offset + halfWidth - 1];
    int a21 = s_ImageBuffer[offset + halfWidth]; 
    int a22 = s_ImageBuffer[offset + halfWidth + 1]; 
    int xSum = -a00 - (2 * a10) - a20 + a02 + (2 * a12) + a22;
    int ySum = a00 + (2 * a01) + a02 - a20 - (2 * a21) - a22; if ((xSum * xSum) > 128) { 
    outputImage[offset] = 0x2F; } else if((ySum * ySum) > 128) { outputImage[offset] = 0xDF; 
} 
else
{ 
    byte yPerlinByte = (byte)Mathf.PerlinNoise(j, 0f);
    byte color = (byte)(pixel yPerlinByte);
    outputImage[offset] = color; 
} 
} 
}

资源
CaptureScreenshotAsTexture API
https://docs.unity3d.com/ScriptReference/ScreenCapture.CaptureScreenshotAsTexture.html

AR Camera Lighting
https://github.com/johnsietsma/ARCameraLighting

FeaturePointColors组件代码
https://pastebin.com/uAGjdRbd

ComputerVision示例
https://github.com/google-ar/arcore-unity-sdk/blob/master/Assets/GoogleARCore/Examples/ComputerVision/Scripts/EdgeDetector.cs


ARcore锐亚教育

锐亚教育 锐亚科技 unity unity教程