在 AR Foundation Android 应用中使用“深度”功能

Depth API 可帮助设备的相机了解场景中真实对象的大小和形状。它使用相机来创建深度图像或深度图,从而为您的应用添加一层 AR 逼真效果。您可以利用深度图像提供的信息,让虚拟对象准确显示在真实对象的前面或后面,从而实现沉浸式逼真用户体验。

深度信息通过运动计算得出,并且可能与硬件深度传感器(例如飞行时间 (ToF) 传感器)的信息结合使用(如有)。设备不需要 ToF 传感器即可支持 Depth API

前提条件

确保您了解基本 AR 概念以及如何配置 ARCore 会话,然后再继续。

将应用配置为 Depth RequiredDepth Optional(仅限 Android)

如果您的应用需要 Depth API 支持,要么因为 AR 体验的核心部分依赖于深度,或者由于应用中使用深度的部分没有优雅回退机制,您可以选择限制在 Google Play 商店中将应用分发到支持 Depth API 的设备

让您的应用Depth Required

导航到 Edit > Project Settings > XR Plug-in Management > ARCore

Depth 默认设置为 Required

让您的应用Depth Optional

  1. 导航到 Edit > Project Settings > XR Plug-in Management > ARCore

  2. Depth 下拉菜单中,选择 Optional 可将应用设置为“深度”可选。

启用深度

为节省资源,ARCore 默认情况下不会启用 Depth API。如需在支持的设备上充分利用深度,您必须通过 CameraARCameraBackground 组件手动将 AROcclusionManager 组件添加到 AR 相机游戏对象。如需了解详情,请参阅 Unity 文档中的自动遮盖

新的 ARCore 会话中,检查用户的设备是否支持深度和 Depth API,如下所示:

// Reference to AROcclusionManager that should be added to the AR Camera
// game object that contains the Camera and ARCameraBackground components.
var occlusionManager = …

// Check whether the user's device supports the Depth API.
if (occlusionManager.descriptor?.supportsEnvironmentDepthImage)
{
    // If depth mode is available on the user's device, perform
    // the steps you want here.
}

获取深度图像

AROcclusionManager 获取最新的环境深度图片。

// Reference to AROcclusionManager that should be added to the AR Camera
// game object that contains the Camera and ARCameraBackground components.
var occlusionManager = …

if (occlusionManager.TryAcquireEnvironmentDepthCpuImage(out XRCpuImage image))
{
    using (image)
    {
        // Use the texture.
    }
}

您可以将原始 CPU 映像转换为 RawImage,以提高灵活性。有关如何执行此操作的示例,请参阅 Unity 的 ARFoundation 示例

了解深度值

给定实测几何图形上的点 A 和代表深度图像中同一点的 2D 点 a,Depth API 在 a 处给出的值等于投影到主轴上的 CA 的长度。这也可称为 A 相对于相机原点 C 的 z 坐标。使用 Depth API 时,请务必注意,深度值不是光线 CA 本身的长度,而是光线的投影

遮挡虚拟对象并直观呈现深度数据

请参阅 Unity 的博文,简要了解深度数据以及如何使用它来遮盖虚拟图像。此外,Unity 的 ARFoundation 示例展示了如何遮挡虚拟图像以及将深度数据可视化。

您可以使用双通道渲染或每个对象的前向渲染来渲染遮挡。每种方法的效率取决于场景的复杂性和其他特定于应用的注意事项。

每个对象的前向传递渲染

每个对象的前向渲染决定了对象在其 Material 着色器中的遮挡效果。如果像素不可见,系统会对其进行裁剪(通常通过 alpha 混合),从而在用户设备上模拟遮挡。

双通道渲染

采用双通道渲染时,第一通道会将所有虚拟内容渲染到中间缓冲区中。第二通道根据现实世界深度与虚拟场景深度之间的差异,将虚拟场景与背景混合。与前向方法相比,这种方法不需要额外的对象特定着色器工作,并且通常会产生更加一致的结果。

从深度图像中提取距离

如需将 Depth API 用于遮蔽虚拟对象或直观呈现深度数据之外的用途,请从深度图像中提取信息。

Texture2D _depthTexture;
short[] _depthArray;

void UpdateEnvironmentDepthImage()
{
  if (_occlusionManager &&
        _occlusionManager.TryAcquireEnvironmentDepthCpuImage(out XRCpuImage image))
    {
        using (image)
        {
            UpdateRawImage(ref _depthTexture, image, TextureFormat.R16);
            _depthWidth = image.width;
            _depthHeight = image.height;
        }
    }
  var byteBuffer = _depthTexture.GetRawTextureData();
  Buffer.BlockCopy(byteBuffer, 0, _depthArray, 0, byteBuffer.Length);
}

// Obtain the depth value in meters at a normalized screen point.
public static float GetDepthFromUV(Vector2 uv, short[] depthArray)
{
    int depthX = (int)(uv.x * (DepthWidth - 1));
    int depthY = (int)(uv.y * (DepthHeight - 1));

    return GetDepthFromXY(depthX, depthY, depthArray);
}

// Obtain the depth value in meters at the specified x, y location.
public static float GetDepthFromXY(int x, int y, short[] depthArray)
{
    if (!Initialized)
    {
        return InvalidDepthValue;
    }

    if (x >= DepthWidth || x < 0 || y >= DepthHeight || y < 0)
    {
        return InvalidDepthValue;
    }

    var depthIndex = (y * DepthWidth) + x;
    var depthInShort = depthArray[depthIndex];
    var depthInMeters = depthInShort * MillimeterToMeter;
    return depthInMeters;
}

后续步骤