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 API | API 版本限制 |
类型二: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);
}
}
}