🦈

WebXR Depth Sensing Moduleについて

2022/12/12に公開

はじめに

TL;DR

WebXR Depth Sensing Module は WebAR アプリから深度情報を取得できる API を提供している、WebXR Device API のモジュールの 1 つです。

記事の概要と対象読者

本記事では WebXR Depth Sensing Module というものについて解説します。
詳細は後述しますが、ブラウザに実装されている WebAR の API なので WebAR 開発者向けの内容になります。

検証環境

筆者が検証に用いた環境を次に示します。

環境 バージョンなど
開発機 Windows 10 Home
デバッグ機 Android 12 Google Pixel 4a 5G
Google Chrome Chrome for Android 106
Node.js 16.13.0
yarn 1.22.18
Vite 3.1.0
TypeScript 4
Babylon.js 5.27.0

サンプルプロジェクト

WebXR Depth Sensing Module を Babylon.js で使ったサンプルプロジェクトを GitHub に公開しておりますので、合わせてご覧ください。

https://github.com/drumath2237/webxr-depth-testbed-babylon

WebXR Depth Sensing Module の概要

この Module はいったい何なのか

WebXR Depth Sensing Module とは、WebXR Device API で提供されている Module の一種です。
W3C の Working Draft に概要や使い方などが記されています。

https://www.w3.org/TR/2022/WD-webxr-depth-sensing-1-20220419/

草案の GitHub リポジトリは以下です。

https://github.com/immersive-web/depth-sensing/

何ができるのか

この Module は名前の通り、Depth、つまり深度情報を取得するための API を提供します。
深度情報とは WebAR デバイスのカメラ画角において、「ここらへんに見えている物はどのくらいユーザから離れているのか」を表すものです。

例えば iPhone には LiDAR センサーが載っている機種がありますが、LiDAR センサーを使うことで光が飛んでいる時間から光が到達した物体までの距離を割り出すことができます。また LiDAR センサーが載っていない ARCore に対応した Android デバイスも、画像処理によって深度推定をして距離を求められます。
今紹介したものはいずれもネイティブアプリの API によって実現可能ですが、これらをブラウザからでも実現したものが WebXR Depth Sensing Module です。
Depth 画像を使うと物体の形状を 3 次元的に復元したり、AR シーンでオクルージョンを実現したり、空間認識の精度を向上させたりできます。

img
Depth 画像の参考(引用:Apple Developer

どうやって使うのか

使い方を知るには GitHub の explaner を見るのが一番簡単です。
基本的な使い方と型定義などが書いてあります。もっと詳しく知りたい場合は W3C の Working Draft を見てみましょう。

https://github.com/immersive-web/depth-sensing/blob/main/explainer.md

機能の有効化(リクエスト)

explaner に書いてある通り、WebXRSession を受け取る際にオプションとして DepthSesning の機能をリクエストします。

const session = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["depth-sensing"],
  depthSensing: {
    usagePreference: ["cpu-optimized", "gpu-optimized"],
    formatPreference: ["luminance-alpha", "float32"],
  },
});

ここで特徴的なのは「Usage」と「Format」を指定できる点でしょう。

Usage と Format

まず Usage ですが、深度データを扱う用途を指定できます。具体的には CPU で処理をする想定なのか、GPU を想定しているかです。扱い方の詳細は後述しますが、Usage によって深度情報をどこから取得して何が返ってくるのかが変わってきますので、用途に合わせて適切なものを選択しましょう。次の Web IDL をご覧ください(explaner から引用)。

深度情報の定義
[SecureContext, Exposed=Window]
interface XRDepthInformation {
  // All methods / attributes are accessible only when the frame
  // that the depth information originated from is active and animated.

  readonly attribute unsigned long width;
  readonly attribute unsigned long height;

  [SameObject] readonly attribute XRRigidTransform normTextureFromNormView;
  readonly attribute float rawValueToMeters;
};

interface XRCPUDepthInformation : XRDepthInformation {
  // Data format is determined by session's depthDataFormat attribute.
  [SameObject] readonly attribute ArrayBuffer data;

  // おそらくここの引数はlongじゃなくてfloatだった気がする(explanerのミス?)
  float getDepthInMeters(unsigned long column, unsigned long row);
};

interface XRWebGLDepthInformation : XRDepthInformation {
  // Opaque texture, its format is determined by session's depthDataFormat attribute.
  [SameObject] readonly attribute WebGLTexture texture;
};

ここにはXRDepthInformationという共通のデータを定義したインタフェースと、それを継承したXRCPUDepthInformationXRWebGLDepthInformationがありますね。
名前を良く見ると CPU・WebGL といった単語が入っているのがわかる通り、これらはそれぞれcpu-optimizeモードとgpu-optimizeモードで受け取れるデータ形式です。
CPU モードでは ArrayBuffer による配列で深度配列を取得できますが、GPU モードでは WebGLTexture として深度画像を取得できます。

次に Format ですが、こちらはluminance-alphaもしくはfloat32を指定できます。
luminance-alphaが指定された場合、深度バッファ(深度配列)は uint16 型(short 型)、つまり 16bit 整数値として返ってきます。float32を指定した場合、深度バッファ(深度画像)は float 型で 4 バイトの浮動小数で返ってきます。詳しい仕様は Working Draft の表がわかりやすかったので引用します。

img
Format の違いを説明した表(引用:Working Draft

深度情報を取得する

深度情報の取得方法は、CPU モードか GPU モードかで変わってきます。
まず CPU モードの場合、深度情報は XRFrame から取得します。

DepthInfoの取得(explanerから引用)
const view = ...;  // Obtained from a viewer pose.
const depthInfo = frame.getDepthInformation(view);

if(depthInfo == null) {
  ... // Handle the case where current frame does not carry depth information.
}

// Obtain the depth at (x, y) normalized view coordinates:
const depthInMeters = depthInfo.getDepthInMeters(x, y);

上の例ではgetDepthInMetersメソッドによって画面の UV から距離を 1 つ取得していますね。
CPU モードでは次のように深度配列も受け取れます。depthInfo.dataArrayBufferとして渡されるので、適当な TypedArray に解釈させます。

深度配列の取得(luminance-alphaの場合)
const uint16Data = new Uint16Array(depthInfo.data);

const index = c + r * depthInfo.width;
const depthInMetres = uint16Data[index] * depthInfo.rawValueToMeters;
深度配列の取得(float32の場合)
const float32Data = new Float32Array(depthInfo.data);

const index = c + r * depthInfo.width;
const depthInMetres = float32Data[index] * depthInfo.rawValueToMeters;

GPU モードの場合は XRWebGLBindings から DepthInformation を取得できます(explaner から引用)。

DepthIndoの取得(GPUモード)
const view = ...;  // Obtained from a viewer pose.
const xrWebGLBinding = ...; // XRWebGLBinding created for the current session and GL context

const depthInfo = xrWebGLBinding.getDepthInformation(view);

深度画像はdepthInfo.textureで取得出来るのでシェーダなどで処理をしますが、
各ピクセルはluminance-alphaだと uint16、float32だと float 型で扱う点だけ注意してください。

動作要件など

2022 年 12 月現在、Immersive Web のホームページによると Depth Sensing Module が動くのは Chrome for Android のみとなっています。おそらく WebXR Incubation のフラグは必要ないと推測されます(筆者の環境では必要なかったです)。

https://immersiveweb.dev/#supporttable

また筆者の手元の Android で試したところ、どうやらcpu-optimizeluminance-alphaの組み合わせのみサポートしているようでした。こちらはあまり調べられていないのですが、最新のハイスペックスマホだとサポートされていたりするのでしょうか。

おわりに

今回は WebXR Depth Sensing Module についてご紹介しました。面白い機能ではあるものの、ネット記事が公式以外ほぼ無かったので実際に実装して動作させながら勉強していました。
もしみなさまのお役に立てたら嬉しいです。最後まで読んでいただきありがとうございました。

参考文献

https://www.w3.org/TR/2022/WD-webxr-depth-sensing-1-20220419/

https://github.com/immersive-web/depth-sensing/

https://zenn.dev/drumath2237/scraps/9df1e32f7de989

GitHubで編集を提案

Discussion