ARCore 中文文档 下载 App

Depth API 失效和遮挡渲染问题排查

问题类型总览

Depth API 相关的问题主要集中在三个方面:

类型表现排查方向
Depth API 完全不工作深度图全黑/全零、遮挡没有效果设备支持 / Session 配置
Depth 数据质量差深度值抖动、遮挡关系不稳定传感器 / 被动深度模式
遮挡渲染有 bug边缘锯齿、穿透、不完整渲染管线 / 深度缓冲

类型一:Depth API 完全不工作

检查清单

1. 设备支持吗?
   → session.isDepthSupported() 返回 true 吗?
   → 查官方支持设备列表

2. Session 配置对吗?
   → setDepthMode(Session.Config.DepthMode.AUTOMATIC) 有没有调用?
   → 配置是在哪个阶段设置的?(必须在 configure() 时)

3. 设备硬件有没有问题?
   → 同一应用在其他支持 Depth 的设备上正常吗?
   → 其他 ARCore Depth Demo(如 Depth Lab)在这个设备上正常吗?

4. Play Services for AR 版本?
   → 更新到最新版再试

Session 配置时机

// 正确:在创建 Session 后、resume 之前配置
val session = Session(context)
session.configure(
    Session.Config(context).apply {
        if (session.isDepthSupported()) {
            setDepthMode(Session.Config.DepthMode.AUTOMATIC)
        }
    }
)
session.resume()

// 错误:在 Session.resume() 之后才配置(可能不生效)
session.resume()
session.configure(...)  // 太晚了

某些设备禁用 Depth 的情况

设备情况Depth 行为原因
有 ToF 传感器(Pixel 4+、三星 S20+)自动使用硬件深度最佳
有双摄但无 ToF可能用被动深度视设备实现
单摄无深度传感器可能降级为被动深度需要足够视差
Android 版本过低(< 8.1)完全不支持 Depth APIAPI 版本限制

类型二:Depth 数据质量差

深度图大部分区域无效

// 检查深度值有效性
val depthImage = frame.acquireDepthImage()
val plane0 = depthImage.planes[0]

// 遍历所有像素(如果你的应用需要)
var validPixelCount = 0
for (i in 0 until plane0.rowStride * plane0.height) {
    val depth = plane0.buffer.getFloat(i)  // 注意:可能需要字节序转换
    if (depth > 0f && depth < 50f) {  // 合理范围:0~50 米
        validPixelCount++
    }
}

val validRatio = validPixelCount.toFloat() / (plane0.rowStride * plane0.height)
if (validRatio < 0.3f) {
    // 超过 70% 的像素无效 → 深度质量差
}

depthImage.close()

无效像素多的原因

原因说明解决
距离太近离物体 < 30cm 时被动深度无法工作提示用户"请离物体远一些"
距离太远超过 5 米被动深度精度急剧下降被动深度的有效范围有限
纹理太少光滑墙壁、玻璃、纯天空没有特征点引导对准有纹理的物体
光照不足画面太暗导致无法提取特征点改善光照
ToF 传感器被遮挡手机壳/保护膜盖住了 ToF 发射器检查硬件

ToF vs 被动深度的质量差异

ToF 深度:
  - 有效范围:0.1m ~ 5m
  - 精度:±1cm
  - 像素完整性:> 90%(除非玻璃等特殊材质)
  - 不会因快速运动失准

被动深度:
  - 有效范围:0.5m ~ 3m
  - 精度:±5~15cm
  - 像素完整性:30%~70%(取决于纹理丰富度)
  - 会因快速运动失准

Unity 中检测深度质量

using UnityEngine.XR.ARFoundation;

public class DepthQualityMonitor : MonoBehaviour
{
    public AROcclusionManager occlusionManager;

    void Update()
    {
        if (occlusionManager.TryAcquireEnvironmentDepthConfidenceImage(out var confidenceImage))
        {
            // confidenceImage 包含每个像素的深度置信度
            // 值范围 0~255,越高表示 ARCore 对该深度值越有信心
            // 低于某个阈值(如 128)的像素可以视为无效

            confidenceImage.Dispose();
        }
    }
}

类型三:遮挡渲染问题

问题一:虚拟物体穿透真实物体

原因:深度缓冲里没有写入真实世界的深度值,或者写入的深度值不对。

检查清单:

1. Depth API 开启了吗?
   → session.isDepthSupported() && DepthMode != DISABLED

2. 深度图正确写入 GPU 深度缓冲了吗?
   → 用 RenderDoc / Android GPU Debugger 检查深度纹理

3. 深度值正确吗?
   → 调试时把深度纹理渲染成可视化灰度图
   → 近处物体应该是深色,远处是浅色

问题二:遮挡边缘锯齿

原因:深度图分辨率低于 RGB 画面分辨率,遮挡边界粗糙。

解决方案:

方案一:边缘膨胀(Dilation)
  对深度图做 N×N 的膨胀操作,让有效像素向外扩展
  遮挡更宽松 → 减少穿模,但虚拟物体可能被挡得过多

方案二:屏幕空间边缘检测 + 平滑
  检测 RGB 和深度图的差异边缘,用 α 混合做软过渡

方案三:提高深度图分辨率
  setDepthTextureSize() 设置更高分辨率
  但会显著增加 GPU 开销

问题三:远处不遮挡

原因:Depth API 被动模式在远距离精度不够。

解决方案:

1. 如果设备有 ToF,硬件深度在远距离更好
2. 用 Geospatial API + Streetscape Geometry 补充(如果在室外)
3. 接受现实:远距离遮挡本来就很难做好
4. 降级:只在近距离(< 2m)做遮挡

Unity 遮挡分级策略

// 根据深度置信度选择遮挡策略
void UpdateOcclusion()
{
    var occlusionManager = GetComponent<AROcclusionManager>();

    if (GetDevicePerformanceLevel() == DeviceTier.HighEnd)
    {
        // 高端设备:完整遮挡
        occlusionManager.environmentOcclusionMode = EnvironmentOcclusionMode.Automatic;
        occlusionManager.environmentOcclusionPreferenceMode =
            OcclusionPreferenceMode.PreferEnvironmentOcclusion;
    }
    else if (GetDevicePerformanceLevel() == DeviceTier.MidRange)
    {
        // 中端:简化遮挡(只做完全遮挡,不做软过渡)
        occlusionManager.environmentOcclusionMode = EnvironmentOcclusionMode.Minimal;
    }
    else
    {
        // 低端:关闭遮挡
        occlusionManager.environmentOcclusionMode = EnvironmentOcclusionMode.Disabled;
    }
}

调试技巧:可视化深度图

在应用中把深度图渲染成一个小窗口(或全屏调试模式),直观看到深度数据的质量:

// 伪代码:把深度 Image 渲染成灰度图
fun renderDebugDepthOverlay(frame: Frame) {
    val depthImage = frame.acquireDepthImage()
    // 把每个像素的深度值归一化到 0~1
    // 0 = 最近(黑),1 = 最远(白)
    // 渲染到屏幕上的一个小区域
    depthImage.close()
}

Unity 中更简单:

// 用 CommandBuffer 把 Depth Texture 绘制到屏幕角落
public class DepthDebugView : MonoBehaviour
{
    public AROcclusionManager occlusionManager;

    void OnGUI()
    {
        if (occlusionManager.TryAcquireEnvironmentDepthTexture(out var tex))
        {
            GUI.DrawTexture(new Rect(0, 0, 200, 150), tex);
        }
    }
}