Webからのお手軽ARの手段(model-viewer)、glTF/usdによる表現力の違いについて

この記事は [WebXR ( WebVR/WebAR ) Advent Calendar 2021](https://adventar.org/calendars/6556) の21日目の記事です。

ここでは、3DモデルをglTFまたはUSDとして用意した場合にスマートフォンやタブレットで
OSネイティブの機能でARする手段についてまとめていきます。
また、その際のglTFとUSDでの表現力の違いを列挙しました。
対象OSはAndroidまたはiOS/iPadOSになります。

以下で確認しました。
2021/12/21段階の最新OSになります。

* Pixel 4a (Android 12)
* iPhone 8 (iOS 15.2)
* iPadOS 第8世代 (iOS 15.2)

ここで記載しているデモやglTF/usdファイルはCC0としていますので、ご自由にご利用くださいませ。

## model-viewer

ARする場合の入り口として「model-viewer」( https://modelviewer.dev/ )を使用します。
model-viewerは3DモデルをWebXRとしてブラウザ表示、AR表示を簡単に行うための機能を提供する、オープンソースのプロジェクトです。

https://github.com/google/model-viewer

HTMLを記述して3DモデルであるglTFやUSDにリンクするだけで、
WebサーバにアップロードしたHTMLのURLにアクセスすることでOSネイティブの機能に飛ばしてAR表示することができるようになります。
OSの違いを意識する必要もありません。   

ARで特にインタラクティブなことをする必要がない場合は、3DモデルをARで表示するだけで十分、ということも多いと思います。
また、クリエイターの方で3Dモデルをとにかく現実世界に召喚したいんだ、という方でもmodel-viewerの使用は難易度が低いため、
身構えずに使用できると思います。

### OSネイティブでのAR表示 (iOS/iPadOS)

iOS/iPadOSでは、「AR Quick Look」でOSネイティブのAR表示を行います。
3Dモデルで使用されるフォーマットは「USD」(Universal Scene Description) https://graphics.pixar.com/usd/release/index.html です。

### OSネイティブでのAR表示 (Android)

Androidでは「SceneViewer」でOSネイティブのAR表示を行います。
https://developers.google.com/ar/develop/java/scene-viewer

3Dモデルで使用されるフォーマットは「glTF」( https://github.com/KhronosGroup/glTF )です。

## model-viewerを使用

iOS/iPadOSまたはAndroidで、ARで使用される3Dモデルのフォーマットは異なります。
model-viewerはこの違いを吸収し、glTFフォーマットのglbファイルさえあればiOS/iPadOSに持って行ったときに自動的にusd形式に変換してくれます。
もちろん、glTFとUSDの両方を用意しても問題ありません。     

それでは、model-viewerを使ったAR表示を行います。
必要なファイルは以下になります。

* HTML
* 3Dモデル (glbファイルまたはusdzファイル)

glb形式は、glTFの表現に必要なファイル（gltf/bin/画像ファイル）を1つにまとめたファイルです。 
usdz形式は、USDの表現に必要なファイル（usd/画像ファイル）を1つにまとめたファイルです。  

HTMLファイルはテキストエディタで以下のように記述しました。 

```html
<html>
    <head>
        <title>Title</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
    </head>
    <body>
        <model-viewer
          ar
          ar-modes="scene-viewer webxr quick-look"
          src="./cyawan.glb"
          ios-src="./cyawan.usdz"
          alt="Cyawan"
          auto-rotate
          camera-controls
        ></model-viewer>
        <br>
    </body>
</html>
```
別途HTMLファイルやJavaScriptを用意する必要はありません。

```html
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
```
としてmodel-viewerのJavaScriptを指定し、&lt;model-viewer&gt; タグでARのための情報を指定します。
以下を指定しました。

| 属性名 |説明|    
|---|---|    
| ar |ARとして使用することを明示|    
| ar-modes |ARの表現手段を優先順位ごとに指定|    
| src |glbファイルを指定|    
| auto-rotate |Webブラウザ上にて、3Dモデルの自動回転を許可|    
| camera-controls |タップによるカメラ操作を許可|    

このHTMLと同一のディレクトリに「cyawan.glb」と「cyawan.usdz」を配置しています。
「ar-modes」でARの表現手段を順番に指定します。
「scene-viewer webxr quick-look」のように指定し、先頭から優先して使用されます。
「scene-viewer」(Androidのみ)は、AndroidでOSネイティブの「SceneViewer」を使用。
「webxr」は、WebXRの仕組みでAR表示します。WebGLフレームワークとしてはthree.jsを使用しているようです。
「quick-look」(iOS/iPadOSのみ)は、iOS/iPadOSでOSネイティブの「AR Quick Look」を使用。

iOS/iPadOSでは「scene-viewer」「webxr」はスキップされてAR Quick Lookの表示になるようでした。
Androidでは「scene-viewer webxr」と指定した場合は「SceneViewer」が使用され、
「webxr scene-viewer」と指定した場合はWebXR(three.js)が使用されます。

以下にサンプルをアップしました。
AndroidまたはiOS/iPadOSでアクセスし、右下のARボタンをタップすると茶碗がARとして表示されます。
![modelviewer_ar_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/450f04eb-8a11-cbce-7be3-09fa0f22bedc.jpeg)

Demo : https://ft-lab.github.io/ar/modelviewer/cyawan/index.html

## usdzを使用しない場合

以下のようにusdzを記載しない場合は、iOS/iPadOSで見た場合に **glTFファイルから自動的にusdzファイルが生成され** AR表示されます。

```html
    <model-viewer
      ar
      ar-modes="scene-viewer webxr quick-look"
      src="./cyawan.glb"
      alt="Cyawan"
      auto-rotate
      camera-controls
    ></model-viewer>
```
ここでは「ios-src」を指定していません。

以下にサンプルをアップしました。
Demo : https://ft-lab.github.io/ar/modelviewer/cyawan/index_glb.html

ただし、このglTFからUSDの自動コンバートはいくつかうまくいかない問題があるようでした。
それらについては後述します。

## 各環境でのAR表示

WebXR(Android)、SceneViewer(Android)、AR Quick Look(iOS/iPadOS)の各環境でのAR表示を比較します。

ここでは、以下の反射要素の強い鍋で確認していきました。
![modelviewer_ar_02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/b67b9d9e-b4db-b8eb-adbb-9dd3e7feba51.jpeg)
上の画像は、glTF Viewer ( https://gltf-viewer.donmccurdy.com/ )でglTFを見たときの画像です。

### WebXR (Android)

Pixel 4a(Android 12)でのARです。

Demo : https://ft-lab.github.io/ar/modelviewer/nabe/index_webxr.html

「ar-modes」は「webxr scene-viewer quick-look」のように指定しました。
この場合はAR表示時にWebXRが優先されます。

```html
    <model-viewer
      ar
      ar-modes="webxr scene-viewer quick-look"
      src="./nabe.glb"
      alt="Nabe"
      auto-rotate
      camera-controls
    ></model-viewer>
```

WebGL表示はthree.jsを使用して行われます。
また、WebXR時の背景のHDRIは固定のものが与えられます。
そのため、環境に合わないHDRIを指定している場合はライティングに違和感が出ることになります。     
以下はARでのキャプチャです。
![modelviewer_ar_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/39928504-323e-837e-759e-7deca3fb1e1f.jpeg)
映り込みは周囲のものを反映しているわけではなく、固定のものが使用されています。

別途、model-viewerでは「skybox-image」にHDRIを指定でき、「xr-environment」と併用することでWebXR時のHDRIを指定できる仕様になっています。

```html
    <model-viewer
      ar
      ar-modes="webxr scene-viewer quick-look"
      src="./nabe.glb"
      skybox-image="./background.hdr"
      alt="Nabe"
      auto-rotate
      camera-controls
      xr-environment
    ></model-viewer>
```
※ ARするときのカメラからの周りの環境に左右されるため、サンプルはありません。

「skybox-image」でEquirectangularのpng/jpeg/hdrファイルを指定します。
exrは指定できません。
また、「xr-environment」を追加することによりskybox-imageがAR時の背景/IBL時の光源として使用されます。
ただし、以下の「xr-environment」にKnown issuesとして記載がありますが、現状は映り込みが真っ暗になってしまいました。
https://modelviewer.dev/docs/index.html#augmentedreality-attributes       

![modelviewer_ar_06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/f18fa0a1-bbb4-f697-2345-f9be70c01a4c.jpeg)

### SceneViewer (Android)

Pixel 4a(Android 12)でのARです。

Demo : https://ft-lab.github.io/ar/modelviewer/nabe/index.html

「ar-modes」は「scene-viewer webxr quick-look」のように指定しました。
この場合はAR表示時にSceneViewerが優先されます。
以下はARでのキャプチャです。
![modelviewer_ar_04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/4f02d07a-96ed-d3b2-b2da-e7e25353302f.jpeg)
カメラからの周囲を考慮してHDRIが推定されるため、よりその場に存在する感が高まってます。
3Dモデルのマテリアルの指定や周りの環境によって変化するため、少し明るすぎたり暗すぎたりする場合もあります。

### AR Quick Look (iOS)

iPhone 8(iOS 15.2)でのARです。

Demo : https://ft-lab.github.io/ar/modelviewer/nabe/index.html
※ これは、Android時と同じURLです｡ 

「ar-modes」は「scene-viewer webxr quick-look」のように指定しました。
iOS/iPadOSの場合は、「AR Quick Look」での表示になります。

以下はARでのキャプチャです。
![modelviewer_ar_05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/b30231fe-16d6-d597-14b6-f427dbb4f1fa.jpeg)

AndroidのSceneViewerの時と同様に、AR Quick Lookでもカメラからの周囲を考慮してHDRIが推定されます。
いい感じにその場に存在する感が出てます。
なお、iOS 15.1までは3Dモデルのライティングが全体的に暗すぎる感じになっていました。

以下、そのレポートです。
https://developer.apple.com/forums/thread/654518

これは、細かいバージョンは把握できていませんが iOS 13.xあたりで発生した問題になります。
それより前のバージョンのAR Quick Lookでは綺麗でした。
iOS 15.2ではこの部分が修正されたのか、かなりよいライティングになっているようでした。

## glTFとUSDの表現の違い

AndroidはglTFファイル、iOS/iPadOSはUSDファイルを使用するため必ずしも同じ表現になるとは限りません。
ただし、両方ともメッシュでのジオメトリ、UVを指定したテクスチャマッピング、PBRマテリアル(Metallic-Roughnessマテリアルモデルを使う)をベースとしており、ほとんどは同じです。

AR時に環境によって表現が異なる場合は、以下の要因が考えられます。

* glTFからUSDへの自動変換
* WebGLフレームワーク(model-viewerの場合はthree.js)の実装
* OSでのネイティブARでの実装 (AndroidやiOS/iPadOSのバージョンに依存)
* glTFとUSDの仕様の違い

model-viewerはARに橋渡しをする存在ですので、この部分で問題が起きることは少なそうです。
ありそうとすればglTFからUSDへの自動変換周りでしょうか。
このUSDへの変換は、現状多くの問題があると思われます。

WebGLフレームワークのthree.jsでのglTFの表現はかなり洗練されており、ここで不正な挙動をすることはほとんどないと思います。

ネイティブARの場合(SceneViewerまたはAR Quick Look)は、OSのバージョンアップによりAR表現がたまに変わることがあり、情報がそれほど出ないため要注意になります。
しれっと直っていたり、不都合が再発しているということもありました。

ここでは、OSでのネイティブARでの実装、glTFとUSDの仕様の違いについて
Android 12とiOS/iPadOS 15.2段階での機能別の比較を行いました。
特にglTFはWebXRではよく使われる3Dモデルのフォーマットになりますので、「何が表現できる」かは把握しておいたほうがいいかもしれません。

### glTFとUSDの仕様による違い

ARのチェック用のシーンを用意しました。
以下はglTF Viewerでの表示です。これをリファレンス（お手本）とします。
![modelviewer_ar_check_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/0687d796-c1dd-d06e-f223-1a02a15401a0.jpeg)
これは、以下の検証を行うサンプルになります。

* BaseColorのsRGB処理の確認 （テクスチャ未使用、使用時）
* Metallicの表現
* Roughnessの表現
* 法線マップの強さの指定
* Emissiveの表現
* Occlusionマップの表現
* Alpha Mode (Mask)
* Alpha Mode (Blend)
* Vertex Color (テクスチャ未使用、使用時)
* Double Sided
* Unlit
* Texture Transform (テクスチャのタイリング指定で使用)

glTFとUSDの仕様の違いは以下のようになっています。

※ USDの場合は「UsdPreviewSurface」という固定のShaderを使用します。
この仕様がマテリアルのパラメータを決定づけています。

|項目|glTF|USD|補足|    
|---|---|---|---|     
|マテリアル要素|テクスチャと値(Factor)は両方持つことができ、乗算される|~~テクスチャと値はどちらか一方~~<br>テクスチャと値(Factor)は両方持つことができる|BaseColor<br>Metallic<br>Roughness<br>Emissive<br>も同様<br><br>~~glTF→USDに渡す際はベイクが必要~~|    
|BaseColorの単一色|Linearで格納|Linearで格納||    
|BaseColorのテクスチャ|sRGBで格納|sRGBで格納|png/jpeg形式|    
|法線マップの強さ|normalTextureのscale|機能なし|glTF→USDに渡す際はベイクが必要|    
|AlphaMode (Mask)|alphaCutoffでカットオフ指定|opacityThreshold&gt;0にする<br>opacityThresholdでカットオフ指定|テクスチャのアルファを使ったトリミング表現|    
|AlphaMode (Blend)|BaseColorのAlpha成分を使用|opacityThreshold=0にする<br>opacityTextureを指定|半透明表現|    
|Vertex Color|Meshの頂点ごとにカラーバッファを指定|Meshの頂点ごとにカラーバッファを指定|頂点カラー|    
|Double Sided|Materialの"doubleSided"をtrueにする|Meshの"doubleSided"を1にする|両面表示<br><br>USDではMeshにDoubleSided指定がある点に注意!|    
|Unlit|extensionの"KHR_materials_unlit"|機能なし|シェーディングせずにBaseColorをそのまま表現|    
|テクスチャのタイリング|extensionの"KHR_texture_transform"|UsdTransform2dの"scale"||    

2021/12/30追記 :
「マテリアル要素」でUSDの場合は「テクスチャと値はどちらか一方」と書いていましたが、
USDの仕様を再度チェックしたところUsdUVTexture側で「float4 inputs:scale」を与えることでテクスチャに対して色を乗算合成できました。

~~USDのUsdPreviewSurfaceは、glTFのマテリアルに比べて少し自由度がありません。~~
~~そのため、glTFからUSDにコンバートする場合はテクスチャに対してベイクが発生する箇所(Factorを別途指定している場合に乗算)がでてきます。~~
Unlitの機能はUSDにはないため、Emissive(発光)で代役するなどの工夫が必要になります。

ここには列挙していませんがこれ以外にも、
glTFの場合はUVSetを2つ持つことができる（USDの場合はUVを複数持てはしますが、それをマテリアルで明示する仕様が見当たらず）、
アニメーション機能など、glTFとUSDで差があります。

### Android 12 : glTFだけを用意したデモ (WebXR優先)    

Demo : https://ft-lab.github.io/ar/modelviewer/pbrtest/index_webxr.html

```html
    <model-viewer
      ar
      ar-modes="webxr scene-viewer quick-look"
      src="./gltfTest_PBRMaterial.glb"
      alt="PBRTest"
      auto-rotate
      camera-controls
    ></model-viewer>
```
WebXRを優先にして、Pixel 4a (Android 12)で見た場合は以下のようになりました。
![modelviewer_ar_check_04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/9c2a5bb4-5505-8bbd-757d-c67088ee4e97.jpeg)
リファレンスのglTF Viewerと同じになりました。
glTFとしての再現性は完璧です。

### Android 12 : glTFだけを用意したデモ (SceneViewer優先)    

Demo : https://ft-lab.github.io/ar/modelviewer/pbrtest/index.html

```html
    <model-viewer
      ar
      ar-modes="scene-viewer webxr quick-look"
      src="./gltfTest_PBRMaterial.glb"
      alt="PBRTest"
      auto-rotate
      camera-controls
    ></model-viewer>
```

SceneViewerを優先にして、Pixel 4a (Android 12)で見た場合は以下のようになりました。
![modelviewer_ar_check_05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/127ee2b7-fd54-74a2-f635-c80d4326a026.jpeg)
SceneViewerの場合は、カメラからの映像含めて少し解像度が荒く感じます。
alphaCutoffの指定が0.5固定になってるような動きになっています。
それ以外はすべてglTFの仕様通りに再現できています。

### iOS/iPadOS 15.2 : glTFだけを用意したデモ (USDに自動変換) 

Demo : https://ft-lab.github.io/ar/modelviewer/pbrtest/index.html
※ これは、Android時と同じURLです｡ 

iPad 第8世代(iPadOS 15.2)で見た場合は以下のようになりました。
内部的にglTFからusdzに変換されます。
![modelviewer_ar_check_02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/33f7a1e7-992d-87a0-ebfd-a6a2910afb45.jpeg)
glTFからUSDへの自動コンバートは以下が正しく動作しないようです。

* glTFのAlphaModeの「Mask」「Blend」が正しく変換されない
* glTFのBaseColorとBaseColorTextureがある場合、USDに渡す際にベイクが必要だがsRGBに統一された形で乗算されていない（USDで暗くなってしまう）
* USDで頂点カラーは反映されない (これはAR Quick Look側の実装がないと思われる)
* USDでUnlitは反映されない (USDの仕様でUnlitがない)
* DoubleSidedが反映されない (これはAR Quick Look側の実装の問題)

### iOS/iPadOS 15.2 : usdファイルをあらかじめ用意したデモ

Demo : https://ft-lab.github.io/ar/modelviewer/pbrtest/index_usdz.html

```html
    <model-viewer
      ar
      ar-modes="scene-viewer webxr quick-look"
      src="./gltfTest_PBRMaterial.glb"
      ios-src="./usd_PBRMaterial.usdz"
      alt="PBRTest"
      auto-rotate
      camera-controls
    ></model-viewer>
```
「ios-src」として「usd_PBRMaterial.usdz」を指定しています。
iPad 第8世代(iPadOS 15.2)で見た場合は以下のようになりました。
![modelviewer_ar_check_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62860/423fcd7d-066d-35e8-34d2-1bf463ba5e4a.jpeg)
UnlitはUSDの仕様自体でサポートされていないため表現できていません。
ここではEmissiveに置き換えて代わりにUnlitっぽい表現を行っています。
Double SidedはUSDの仕様としてあるのですが、iOS/iPadOS 15.2では反映されていません（AR Quick Lookの実装の問題）。

参考 : https://developer.apple.com/forums/thread/673958

頂点カラーはUSDの仕様として持つことができるのですが、iOS/iPadOS 15.2では反映されていません（AR Quick Lookの実装の問題）。

AlphaのCutoff(USDのopacityThresholdの指定)があるのですが、これが0.0以上であってもglTFでのAlpha Blendと同じような挙動になっているようです。
これはUSDの仕様とは異なる動きで、AR Quick Lookの解釈の違いのように思われます。

## glTF/USDの表現の違いのまとめ

これらの検証結果より、改めてglTF/USDの表現の違いをまとめました。

機能が存在するものは O 、動作が正しくないのは NG と表現しています。
「glTF」「USD」はそれぞれのフォーマットで仕様として存在するかを表します。

|項目|glTF|USD|glTFからUSDの自動変換|Android 12<br>WebXR|Android 12<br>SceneViewer|iOS/iPadOS 15.2|    
|---|---|---|---|---|---|---|     
|BaseColor|単一色 + テクスチャ|単一色 + テクスチャ|単一色 + テクスチャの表現はNG|O|O|O<br>|    
|Rougnhess/Metallic|単一値 + テクスチャ|単一値 + テクスチャ|O|O|O|O|    
|Occlusion|テクスチャ|テクスチャ|O|O|O|O|    
|法線マップ|テクスチャ|テクスチャ|O|O|O|O|    
|法線マップの強さ|O|機能なし|NG|O|O|1.0以外の場合は要ベイク|    
|EmissiveColor|単一色 + テクスチャ|単一色 + テクスチャ|単一色 + テクスチャの表現はNG|O|O|O|    
|テクスチャのカットオフ (Alpha Mask)|O|O|NG|O|△ (0.5固定?)|△ (Blendで表現される)|    
|半透明 (Alpha Blend)|O|O|NG|O|O|O|    
|頂点カラー|O|O|NG|O|O|NG|    
|Double Sided|O|O|?|O|O|NG|    
|Unlit|O|機能なし|NG|O|O|NG|    
|テクスチャのタイリング|O|O|O|O|O|O|    

BaseColor/EmissiveColorの場合はglTF/USD共に、テクスチャがsRGB、単一色(Factor)はLinear(Raw)のカラースペースで格納します。
そのため、USD変換時は単一色をいったんsRGBに変換してからテクスチャに乗算合成する、もしくはUsdUVTextureのscaleで色指定する必要があります。
Rougnhess/Metallicの場合はテクスチャも単一値(Factor)もLinear(Raw)で格納されるため、USD変換時はテクスチャに対してFactorを乗算合成すればいいことになります。
そのため、glTFからUSDの自動変換時もうまく動作しているようです。

Android上のWebXRの場合は、glTFを一通りうまく再現できていました。
SceneViewerの場合はAlpha MaskでのCutOff指定だけ反映されておらず、それ以外は問題なさそうです。
iOS/iPadOSの場合は、これはUSDの仕様の問題ではなくAR Quick Lookの実装により表現がうまくできない箇所がいくつかありました。

model-viewerを使ったARを行う場合、現状はDCCツール側でglTFファイルのほかにUSDファイルも用意しておいたほうが無難だと思われます。