LingBot-Depth 2.0 在Unity中调用深度信息的集成方法

文章导读
要把 LingBot-Depth 2.0 的深度数据接进 Unity,核心不是把 SDK 跑起来,而是把该 SDK 的深度缓冲转换成 Unity 的 Texture 和 Shader 能理解的格式。下面按初始化、Texture 转换、Shader 消费三步走,给出可落地的集成骨架。无论 SDK 通过回调、轮询还是共享内存输出数据,最终都要落到这两个问题上:数据何时到达、数据以什么格式排布。
📋 目录
  1. 初始化与数据回调
  2. 深度值到 Texture 的编码与上传
  3. 在 Shader 中消费深度信息
  4. 验证与常见问题
  5. 常见问题
A A

要把 LingBot-Depth 2.0 的深度数据接进 Unity,核心不是把 SDK 跑起来,而是把该 SDK 的深度缓冲转换成 Unity 的 Texture 和 Shader 能理解的格式。下面按初始化、Texture 转换、Shader 消费三步走,给出可落地的集成骨架。无论 SDK 通过回调、轮询还是共享内存输出数据,最终都要落到这两个问题上:数据何时到达、数据以什么格式排布。

集成 LingBot-Depth 2.0 时,建议先确认 SDK 以什么回调方式送出深度数组(如 ushort[] 或 IntPtr),再决定 Texture 格式和坐标翻转方式。深度图在 Unity 中通常用 Texture2D 配合 R16 或 RGBA32 存储,Shader 中通过自定义纹理采样。先做单相机深度显示,再谈配准和多相机融合。

初始化与数据回调

在 Unity 中初始化深度 SDK,通常放在 MonoBehaviour 的 Awake 或 Start 里。优先确认三件事:SDK 是否要求特定线程、是否自动开启深度流、回调数据是同步返回还是事件驱动。以常见的回调式 SDK 为例,先拿到设备句柄,再注册深度帧回调。注意回调线程不是主线程,不能在回调里直接操作 Unity 对象,需要把数据拷贝到临时缓冲区,再用主线程的 Update 或 Coroutine 更新 Texture。

public class DepthSource : MonoBehaviour
{
    IntPtr device = IntPtr.Zero;
    ushort[] depthBuffer;
    Texture2D depthTexture;
    bool hasNewFrame;

    void Start()
    {
        // 替换为实际 SDK 初始化入口
        device = LingBotDepth.Init(0);
        LingBotDepth.RegisterDepthFrame(device, OnDepthFrame);
        depthBuffer = new ushort[640 * 480]; // 按 SDK 实际分辨率改
        depthTexture = new Texture2D(640, 480, TextureFormat.R16, false);
    }

    void OnDepthFrame(IntPtr data, int width, int height)
    {
        // 回调节点上拷贝数据,不直接 new 或上传 GPU
        System.Runtime.InteropServices.Marshal.Copy(data, depthBuffer, 0, depthBuffer.Length);
        hasNewFrame = true;
    }

    void Update()
    {
        if (!hasNewFrame) return;
        hasNewFrame = false;
        depthTexture.LoadRawTextureData(depthBuffer);
        depthTexture.Apply();
    }
}

这段代码假设 SDK 返回的是连续 ushort 数组,且分辨率固定。如果深度图尺寸会动态变化,需要在回调里判断 width、height,并在主线程重新创建 Texture2D。注意 LoadRawTextureData 要求 byte 数组长度与 Texture 格式匹配,R16 正好是 2 字节/像素。

深度值到 Texture 的编码与上传

深度值通常是毫米单位的 ushort,直接转 R16 比较自然。但 R16 在部分移动平台或某些渲染管线下不受支持,这时可以把高低字节拆进 RGBA32 的 R 和 G 通道,Shader 里再合并。下面是两种编码方式的选择。如果只是调试,可以用 Color32 数组手动填充,但性能不如 LoadRawTextureData。

LingBot-Depth 2.0 在Unity中调用深度信息的集成方法
void Update()
{
    // 方式一:R16(推荐)
    depthTexture.LoadRawTextureData(depthBuffer);
    depthTexture.Apply();

    // 方式二:RGBA32 兜底
    // Color32[] pixels = new Color32[depthBuffer.Length];
    // for (int i = 0; i < depthBuffer.Length; i++)
    // {
    //     pixels[i] = new Color32(
    //         (byte)(depthBuffer[i] & 0xFF),
    //         (byte)(depthBuffer[i] >> 8),
    //         0,
    //         255);
    // }
    // depthTexture.SetPixels32(pixels);
    // depthTexture.Apply();
}

R16 方案的优点是 Shader 里直接取 .r 作为归一化深度,RGBA32 方案则在 Shader 中需要 decode:depth = (r + g*256)/65535。两种方案都要注意平台字节序,但实际深度值在内存中就是小端,所以上面的拆法是对的。

在 Shader 中消费深度信息

深度纹理传入 Shader 后,需要按实际用途处理。下面是一个调试用 Shader,把深度映射成红绿渐变,方便直接看到大概距离。对深度感知类应用,采样后最好转换为线性距离再做后续计算。

LingBot-Depth 2.0 在Unity中调用深度信息的集成方法
Shader "Unlit/DepthViewer"
{
    Properties
    {
        _DepthTex ("Depth Texture", 2D) = "white" {}
        _Near ("Near Distance (m)", Float) = 0.1
        _Far ("Far Distance (m)", Float) = 5.0
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _DepthTex;
            float _Near;
            float _Far;

            struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; };
            struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 如果使用 R16,深度值已经被归一化到 0-1
                float d = tex2D(_DepthTex, i.uv).r;
                // 映射到实际距离范围,便于显示或参与逻辑
                float dist = lerp(_Near, _Far, d);
                float t = saturate((dist - _Near) / (_Far - _Near));
                return fixed4(t, 1.0 - t, 0.0, 1.0);
            }
            ENDCG
        }
    }
}

如果使用 RGBA32 编码,需要把上面 frag 第一行改成:float raw = tex2D(_DepthTex, i.uv).r + tex2D(_DepthTex, i.uv).g * 256.0; float d = raw / 65535.0; 具体写在哪里,取决于你的图像到底是 raw 还是已经归一化成了 0-1。先跑通调试 Shader,再用 OnRenderImage 或屏幕后处理进一步验证。

验证与常见问题

完成基础通路后,建议做三样检查:一、在屏幕上显示 depthTexture,看深度值是否随物体远近单调变化;二、移动物体,确认深度纹理刷新率符合预期;三、用 Debug.Log 打印深度数组的最大最小值,确认没有溢出或全零。

  • 深度图全黑或全白: 通常不是没数据,而是深度范围没有映射到 0-1。先输出原始值范围,再选择归一化参数。
  • 深度图和 RGB 图错位: 先确认两路图像的分辨率、FOV 是否一致,再检查 Y 轴是否需要翻转。如果错位是整体平移,可能来自传感器硬件配准,这不在本文集成范围内。

另外注意,某些 SDK 在设备挂起或插拔后不会自动恢复,需要在 OnApplicationPause 或 OnDisable 里做反初始化。集成时不要把深度数据直接和 MainCamera 的 depthTexture 混淆,那是引擎自己渲染的深度,不是传感器深度。

LingBot-Depth 2.0 在Unity中调用深度信息的集成方法

常见问题

深度数据回调频率不稳定怎么办? 先检查设备是否工作在固定帧率模式,或者是否因为 USB 带宽被色彩流挤占。可以试着降低分辨率或关闭 RGB 流,单独跑深度流。

在移动端 R16 不可用? 改用 RGBA32 编码,或者用 ComputeShader 把 ushort 转成四个 byte。也可以用 GraphicsFormat.R16_UInt,但需要确认目标平台支持情况。