<model-viewer>でアニメーションつきモデルをAndroid【Scene Viewer】, iOS【AR Quick Look】で表示してみた

Aug. 15, 2019, 4:02 a.m. edited Dec. 21, 2019, 5:16 a.m.

#iOS  #AR  #Android 

先日のGoogle I/OでScene Viewerが発表されました.

これでAndroid, iOSともに(一部のハイエンド限定となりますが)WebARができるようになりました.GAFAのうちのGoogleとAppleがここまで推しているのですから,簡単に実装できるのだろうと思い試してみました.

...非常に大変じゃないか?!?!?!

Android, iOSの順番でアニメーションつきモデルの表示方法を説明していきます.

ちなみに完成したものはこの記事に載せてます.

Android 【Scene Viewer】

Androidなので<model-viewer>で呼ばれるのはARCoreによるScene Viewerです.

必要なものはモデルのglTFファイルとhttpsサーバーです.

適当にBlenderなどで3Dモデルを用意し,glTF出力できるのであればそれで出力し,できないのであればCollada (.dae)にエクスポートしてCOLLADA2GLTF(もしくはそのオンラインサービス)で変換しましょう.

glTFファイルができたら,それをhttpsサーバーに置きましょう.特に持っていないのであれば,GitHub Pagesあたりで良いかと(あとはHerokuあたりか).

そしてHTMLに

<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.js"></script>
<model-viewer src="/path/to/hoge.glb"
                  background-color="#70BCD1"
                  shadow-intensity="1"
                  camera-controls
                  autoplay
                  interaction-prompt="auto"
                  auto-rotate ar magic-leap></model-viewer>

と書けば表示されます.指定したプロパティについてはドキュメントを参照してください.特にこのうちのarでScene Viewerが動くようになります.

直りました,やったー

が,2019-08-17時点で私のXperia XZ1では動きません(以前は動いた).どうやらGalaxyなど一部のデバイスでクラッシュする不具合が起きているようです.もっとも,すごいGoogleエンジニアがクラッシュするソースコードは特定したと言っているので,もうすぐ動くようになるでしょう...

iOS 【AR Quick Look】

2019-10-04追記

もしかして答えはGoogle製ツールのgoogle/usd_from_gltf
試してないけど,Readmeを読む限りAR Quick Lookとの互換性にかなり注力してるので,以下の苦労(特にアニメーション部分)をしなくても良さそう.

追記終わり

さて,本題のiOSです.iOSにおいて<model-viewer>で呼ばれるのはARKit 2以降によるAR Quick Lookです.

必要なものはUSDZファイル(USDAでも動いた)です...誰?????

USDZファイルはPixarが開発しAppleが発表したARのための新しいファイルフォーマットのようです.

そうかそうか,...新しいファイルフォーマットかぁ変換が大変そうだなあ...!

まずは最も簡単なアニメーションなしの場合です.Blenderあたりで適当にobjでエクスポートし,Xcode付属のxcrunで,

$ xcrun usdz_converter model.obj model.usdz

だけです.簡単♪

次にシンプルなトランスレーションのみのアニメーションの場合です.Blenderあたりで適当にabcでエクスポートし,同じように,

$ xcrun usdz_converter model.abc model.usdz

だけです.

ボーンアニメーションつきUSDAファイルとの闘い

ラスボスです.ヤバいです.(^o^)

武器を手に入れます.ないとゲームオーバーです.

https://developer.apple.com/jp/augmented-reality/quick-look/ にアクセスし,一番下の「usdzツールをダウンロードする(英語)」をクリックします.Apple Developerアカウントでログインし,USDPythonをダウンロードします.私は0.60を選びました.

このUSDPythonはUSDファイルを作成するためのツール・ライブラリ群となっています.ただし,動かすためにはMacのシステムのPython (/usr/bin/python) が必要です.brewなどで入れたPythonではクラッシュします.

$ python usdpython/usdzconvert/usdzconvert -h

------------------------ 'Python' is dying ------------------------
Python crashed. FATAL ERROR: Failed axiom: ' Py_IsInitialized() '
in operator() at line 148 of /USD/pxr/base/lib/tf/pyTracing.cpp

USDPythonにはusdpython/usdzconvert/usdzconvertが含まれ,これを用いることでOBJもしくはglTF(!!!!)からusd/usda/usdc/usdzへ変換することができます.つまり,Androidの方で用いたアニメーションつきglTFファイルをusdzconvertで変換するという作戦です.

usdzconvertを動かすには,

$ export PATH=$PATH:/path/to/usdpython/USD
$ export PYTHONPATH=/path/to/usdpython/USD/lib/python/:/path/to/usdpython/USD/lib/python/pxr/Usd/:$PYTHONPATH

と事前に各種パスを通す必要があります.そして,

$ /usr/bin/python /path/to/usdpython/usdzconvert/usdzconvert -h

でヘルプが表示されればOKです.

さて,持っているglTFファイルをこれで変換するのですが,ただ変換するだけ

$ /usr/bin/python /path/to/usdpython/usdzconvert/usdzconvert model.glb

だとMac上ではアニメーションしても,いざiOSで表示してみても全然アニメーションしないということになります(私はなった.ならないならこれで終わり.お疲れ様でした).そこで,人の読めるUSDAファイルで出力してみます.

$ /usr/bin/python /path/to/usdpython/usdzconvert/usdzconvert model.glb model.usda

また,usdpython/samples/cdして,

$ /usr/bin/python 108_skinnedAnimation.py > 108.usda

としてやってAR Quick Lookでも動作するボーンアニメーションつきモデルを出力します.そして,model.usda108.usdaを目で見比べてみます(?!?!??!?目grep?!).

ここからは具体的にBlender 2.78で作成した以下のモデルを考えます.このモデルは単純にCubeを作り,そのCubeのうち一面のみに緑色のマテリアルを割り当て,それ以外に白色のマテリアルを割り当てました.そして,2本のボーンからなるArmatureを作ってCube→Armatureの順で選択してCtrl+pからWith Automatic Weightsで関連付けました.最後に,特にDope Sheet等は開かずに単純にDefault layoutの下にあるタイムラインにて,ボーンをPose Modeで動かしたものをキーフレームで打ち込みました.このとき,全フレーム数は24にしてあります(参考にしたものが24フレームだったため.異なっていても動くかもしれない).

CubeとArmatureを選択した状態で,Colladaで出力し(このときSelection Onlyにチェックした),Androidのときと同様にglTFに変換しました(得られたファイルをmodel.glbとする).そして,上で示したようにusdzconvertmodel.usdaに変換しました.


(このままだとARモードにしてもiOSデバイスでアニメーションしない)

そして,この2つを見比べてみます.

#usda 1.0
(
    defaultPrim = "skinnedAnimation"
    endTimeCode = 24
    startTimeCode = 1
    timeCodesPerSecond = 24
    upAxis = "Y"
)

def Xform "skinnedAnimation" (
    assetInfo = {
        asset identifier = @skinnedAnimation.usd@
        string name = "skinnedAnimation"
    }
    kind = "component"
)
{
    def SkelRoot "cubeModel"
    {
        def Mesh "geometry" (
            prepend apiSchemas = ["SkelBindingAPI"]
        )
        {
            float3[] extent = [(-1.165, -1.165, -1.165), (1.165, 1.165, 1.165)]
            int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
            int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4]
            normal3f[] normals = [(0, 0, 1), (0, 1, 0), (0, 0, -1), (0, -1, 0), (1, 0, 0), (-1, 0, 0)] (
                interpolation = "uniform"
            )
            point3f[] points = [(-1.165, -1.165, 1.165), (1.165, -1.165, 1.165), (-1.165, 1.165, 1.165), (1.165, 1.165, 1.165), (-1.165, 1.165, -1.165), (1.165, 1.165, -1.165), (-1.165, -1.165, -1.165), (1.165, -1.165, -1.165)]
            matrix4d primvars:skel:geomBindTransform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
            int[] primvars:skel:jointIndices = [2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0] (
                elementSize = 4
                interpolation = "vertex"
            )
            float[] primvars:skel:jointWeights = [0.57, 0.57, 0, 0, 0.57, 0.57, 0, 0, 0.57, 0.57, 0, 0, 0.57, 0.57, 0, 0, 0.57, 0.57, 0, 0, 0.57, 0.57, 0, 0, 0.57, 0.57, 0, 0, 0.57, 0.57, 0, 0] (
                elementSize = 4
                interpolation = "vertex"
            )
            prepend rel skel:animationSource = </skinnedAnimation/cubeModel/animation>
            prepend rel skel:skeleton = </skinnedAnimation/cubeModel/SkeletonRoot>
            uniform token subdivisionScheme = "none"
        }

        def Skeleton "SkeletonRoot"
        {
            uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.0231736898422241, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, -0.9544228911399841, 0, 1) )]
            uniform token[] joints = ["SkeletonRoot", "SkeletonRoot/joint1", "SkeletonRoot/joint2"]
            uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.0231736898422241, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, -0.9544228911399841, 0, 1) )]
        }

        def SkelAnimation "animation"
        {
            uniform token[] joints = ["SkeletonRoot", "SkeletonRoot/joint1", "SkeletonRoot/joint2"]
            quatf[] rotations.timeSamples = {
                1: [(1, 0, 0, 0), (1, 0, 0, 0), (1, 0, 0, 0)],
                2: [(0.99785894, 0, 0.065403126, 0), (1, 0, 0, 0), (1, 0, 0, 0)],
                ...
                24: [(0.065403126, 0, 0.99785894, 0), (1, 0, 0, 0), (1, 0, 0, 0)],
            }
            half3[] scales.timeSamples = {
                1: [(1, 1, 1), (1.18066, 1, 1.18066), (2.49414, 1, 2.49414)],
                2: [(1, 1, 1), (0.635742, 1, 0.635742), (1.12012, 1, 1.12012)],
                ...
                24: [(1, 1, 1), (0.014122, 1, 0.014122), (1.05176, 1, 1.05176)],
            }
            float3[] translations.timeSamples = {
                1: [(0, 0.9395472, 0), (0, -0.26373994, 0), (0, -1, 0)],
                2: [(0, 0.9395472, 0), (0, 1.695752, 0), (0, -1, 0)],
                ...
                24: [(0, 0.9395472, 0), (0, 4.188043, 0), (0, -1, 0)],
            }
        }
    }
}
#usda 1.0
(
    customLayerData = {
        string creator = "usdzconvert preview 0.6"
    }
    defaultPrim = "box_2_mat"
    endTimeCode = 24
    metersPerUnit = 1
    startTimeCode = 1
    timeCodesPerSecond = 24
    upAxis = "Y"
)

def Xform "box_2_mat" (
    assetInfo = {
        string name = "box_2_mat"
    }
    kind = "component"
)
{
    def Scope "Materials"
    {
        def Material "Material_001_effect"
        {
            token outputs:surface.connect = </box_2_mat/Materials/Material_001_effect/surfaceShader.outputs:surface>

            def Shader "surfaceShader"
            {
                uniform token info:id = "UsdPreviewSurface"
                color3f inputs:diffuseColor = (0, 0.8, 0)
                color3f inputs:emissiveColor = (0, 0, 0)
                float inputs:metallic = 0
                float inputs:roughness = 1
                token outputs:surface
            }
        }

        def Material "Material_002_effect"
        {
            token outputs:surface.connect = </box_2_mat/Materials/Material_002_effect/surfaceShader.outputs:surface>

            def Shader "surfaceShader"
            {
                uniform token info:id = "UsdPreviewSurface"
                color3f inputs:diffuseColor = (0.64, 0.64, 0.64)
                color3f inputs:emissiveColor = (0, 0, 0)
                float inputs:metallic = 0
                float inputs:roughness = 1
                token outputs:surface
            }
        }
    }

    def Scope "Geom"
    {
        def Xform "Z_UP"
        {
            matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, 0, -1, 0), (0, 1, 0, 0), (0, 0, 0, 1) )
            uniform token[] xformOpOrder = ["xformOp:transform"]

            def Xform "Armature"
            {
                def SkelRoot "Bone"
                {
                    def Skeleton "Skeleton"
                    {
                        uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (0, 0, -0, 1) ), ( (1, 0, -0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (-0, 0, 0.5309846997261047, 1) )]
                        uniform token[] joints = ["Bone", "Bone/Bone_001"]
                        uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 3.422854177870249e-8, 0.9999999657714582, 0), (0, -0.9999999657714582, 3.422854177870249e-8, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.5309846997261047, 0, 1) )]
                        prepend rel skel:animationSource = </box_2_mat/Animations/skelAnim_0>
                    }
                }
            }

            def Xform "Cube"
            {
                def Mesh "primitive_0"
                {
                    int[] faceVertexCounts = [3, 3]
                    int[] faceVertexIndices = [0, 1, 2, 0, 3, 1]
                    rel material:binding = </box_2_mat/Materials/Material_001_effect>
                    normal3f[] normals = [(0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0)] (
                        interpolation = "vertex"
                    )
                    point3f[] points = [(1, -1, 1), (-1, -1, -1), (1, -1, -1), (-1, -1, 1)]
                    matrix4d primvars:skel:geomBindTransform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
                    int[] primvars:skel:jointIndices = [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0] (
                        elementSize = 4
                        interpolation = "vertex"
                    )
                    float[] primvars:skel:jointWeights = [0.30464703, 0.695353, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.8133193, 0.1866807, 0, 0] (
                        elementSize = 4
                        interpolation = "vertex"
                    )
                    prepend rel skel:skeleton = </box_2_mat/Geom/Z_UP/Armature/Bone/Skeleton>
                    uniform token subdivisionScheme = "none"
                }

                def Mesh "primitive_1"
                {
                    int[] faceVertexCounts = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
                    int[] faceVertexIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, 15, 1, 3, 16, 4, 6, 17, 7, 9, 18, 10, 12, 19, 13]
                    rel material:binding = </box_2_mat/Materials/Material_002_effect>
                    normal3f[] normals = [(-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (-1, 0, 0), (0, 1, 0), (1, 0, 0), (0, 0, -1), (0, 0, 1)] (
                        interpolation = "vertex"
                    )
                    point3f[] points = [(-1, -1, 1), (-1, 1, -1), (-1, -1, -1), (-1, 1, 1), (1, 1, -1), (-1, 1, -1), (1, 1, 1), (1, -1, -1), (1, 1, -1), (1, 1, -1), (-1, -1, -1), (-1, 1, -1), (-1, 1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1), (1, 1, 1), (1, -1, 1), (1, -1, -1), (-1, -1, 1)]
                    matrix4d primvars:skel:geomBindTransform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
                    int[] primvars:skel:jointIndices = [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0] (
                        elementSize = 4
                        interpolation = "vertex"
                    )
                    float[] primvars:skel:jointWeights = [0.8133193, 0.1866807, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.30464703, 0.695353, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.8133193, 0.1866807, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.30464703, 0.695353, 0, 0, 0.30464703, 0.695353, 0, 0, 0.8133193, 0.1866807, 0, 0, 0.30464703, 0.695353, 0, 0, 0.8133193, 0.1866807, 0, 0, 0.30464703, 0.695353, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.8133193, 0.1866807, 0, 0] (
                        elementSize = 4
                        interpolation = "vertex"
                    )
                    prepend rel skel:skeleton = </box_2_mat/Geom/Z_UP/Armature/Bone/Skeleton>
                    uniform token subdivisionScheme = "none"
                }
            }
        }
    }

    def Scope "Animations"
    {
        def SkelAnimation "skelAnim_0"
        {
            uniform token[] joints = ["Bone", "Bone/Bone_001"]
            quatf[] rotations.timeSamples = {
                1: [(-0.70710677, -0.70710677, -0, -0), (-1, -0, -0, -0)],
                12: [(-0.70710677, -0.70710677, -0, -0), (-0.6463091, -0.76307577, -0, -0)],
                24: [(-0.70710677, -0.70710677, -0, -0), (-1, -0, -0, -0)],
            }
            half3[] scales.timeSamples = {
                1: [(1, 1, 1), (1, 1, 1)],
                12: [(1, 1, 1), (1, 1, 1)],
                24: [(1, 1, 1), (1, 1, 1)],
            }
            float3[] translations.timeSamples = {
                1: [(0, 0, 0), (0, 0.5309847, 0)],
                12: [(0, 0, 0), (0, 0.5309847, 0)],
                24: [(0, 0, 0), (0, 0.5309847, 0)],
            }
        }
    }
}

108.usdaのアニメーション部分は長いので省略しました.この2つを見比べてわかるのは,108.usdaの方が

  • 階層が浅い
  • SkelAnimationとメッシュ定義部分が同じ階層にある
  • prepend rel skel:animationSourceSkeletonではなくMeshの方にある

ということです.したがって,こうなるように手で編集してやります(?!?!?!).すると,

#usda 1.0
(
    customLayerData = {
        string creator = "usdzconvert preview 0.6"
    }
    defaultPrim = "box_2_mat"
    endTimeCode = 24
    metersPerUnit = 1
    startTimeCode = 1
    timeCodesPerSecond = 24
    upAxis = "Y"
)

def Xform "box_2_mat" (
    assetInfo = {
        string name = "box_2_mat"
    }
    kind = "component"
)
{
    def Scope "Materials"
    {
        def Material "Material_001_effect"
        {
            token outputs:surface.connect = </box_2_mat/Materials/Material_001_effect/surfaceShader.outputs:surface>

            def Shader "surfaceShader"
            {
                uniform token info:id = "UsdPreviewSurface"
                color3f inputs:diffuseColor = (0, 0.8, 0)
                color3f inputs:emissiveColor = (0, 0, 0)
                float inputs:metallic = 0
                float inputs:roughness = 1
                token outputs:surface
            }
        }

        def Material "Material_002_effect"
        {
            token outputs:surface.connect = </box_2_mat/Materials/Material_002_effect/surfaceShader.outputs:surface>

            def Shader "surfaceShader"
            {
                uniform token info:id = "UsdPreviewSurface"
                color3f inputs:diffuseColor = (0.64, 0.64, 0.64)
                color3f inputs:emissiveColor = (0, 0, 0)
                float inputs:metallic = 0
                float inputs:roughness = 1
                token outputs:surface
            }
        }
    }

    def Scope "Geom"
    {
        def SkelRoot "Bone"
        {
            matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, 0, -1, 0), (0, 1, 0, 0), (0, 0, 0, 1) )
            uniform token[] xformOpOrder = ["xformOp:transform"]

            def Mesh "primitive_0"
            {
                int[] faceVertexCounts = [3, 3]
                int[] faceVertexIndices = [0, 1, 2, 0, 3, 1]
                rel material:binding = </box_2_mat/Materials/Material_001_effect>
                normal3f[] normals = [(0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0)] (
                    interpolation = "vertex"
                )
                point3f[] points = [(1, -1, 1), (-1, -1, -1), (1, -1, -1), (-1, -1, 1)]
                matrix4d primvars:skel:geomBindTransform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
                int[] primvars:skel:jointIndices = [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0] (
                    elementSize = 4
                    interpolation = "vertex"
                )
                float[] primvars:skel:jointWeights = [0.30464703, 0.695353, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.8133193, 0.1866807, 0, 0] (
                    elementSize = 4
                    interpolation = "vertex"
                )
                prepend rel skel:animationSource = </box_2_mat/Geom/Bone/skelAnim_0>
                prepend rel skel:skeleton = </box_2_mat/Geom/Bone/Skeleton>
                uniform token subdivisionScheme = "none"
            }

            def Mesh "primitive_1"
            {
                int[] faceVertexCounts = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
                int[] faceVertexIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, 15, 1, 3, 16, 4, 6, 17, 7, 9, 18, 10, 12, 19, 13]
                rel material:binding = </box_2_mat/Materials/Material_002_effect>
                normal3f[] normals = [(-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (-1, 0, 0), (0, 1, 0), (1, 0, 0), (0, 0, -1), (0, 0, 1)] (
                    interpolation = "vertex"
                )
                point3f[] points = [(-1, -1, 1), (-1, 1, -1), (-1, -1, -1), (-1, 1, 1), (1, 1, -1), (-1, 1, -1), (1, 1, 1), (1, -1, -1), (1, 1, -1), (1, 1, -1), (-1, -1, -1), (-1, 1, -1), (-1, 1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1), (1, 1, 1), (1, -1, 1), (1, -1, -1), (-1, -1, 1)]
                matrix4d primvars:skel:geomBindTransform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
                int[] primvars:skel:jointIndices = [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0] (
                    elementSize = 4
                    interpolation = "vertex"
                )
                float[] primvars:skel:jointWeights = [0.8133193, 0.1866807, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.30464703, 0.695353, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.8133193, 0.1866807, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.9371129, 0.06288713, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.30464703, 0.695353, 0, 0, 0.30464703, 0.695353, 0, 0, 0.8133193, 0.1866807, 0, 0, 0.30464703, 0.695353, 0, 0, 0.8133193, 0.1866807, 0, 0, 0.30464703, 0.695353, 0, 0, 0.8802035, 0.119796485, 0, 0, 0.8133193, 0.1866807, 0, 0] (
                    elementSize = 4
                    interpolation = "vertex"
                )
                prepend rel skel:animationSource = </box_2_mat/Geom/Bone/skelAnim_0>
                prepend rel skel:skeleton = </box_2_mat/Geom/Bone/Skeleton>
                uniform token subdivisionScheme = "none"
            }

            def Skeleton "Skeleton"
            {
                uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (0, 0, -0, 1) ), ( (1, 0, -0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (-0, 0, 0.5309846997261047, 1) )]
                uniform token[] joints = ["Bone", "Bone/Bone_001"]
                uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 3.422854177870249e-8, 0.9999999657714582, 0), (0, -0.9999999657714582, 3.422854177870249e-8, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.5309846997261047, 0, 1) )]
            }

            def SkelAnimation "skelAnim_0"
            {
                uniform token[] joints = ["Bone", "Bone/Bone_001"]
                quatf[] rotations.timeSamples = {
                    1: [(-0.70710677, -0.70710677, -0, -0), (-1, -0, -0, -0)],
                    12: [(-0.70710677, -0.70710677, -0, -0), (-0.6463091, -0.76307577, -0, -0)],
                    24: [(-0.70710677, -0.70710677, -0, -0), (-1, -0, -0, -0)],
                }
                half3[] scales.timeSamples = {
                    1: [(1, 1, 1), (1, 1, 1)],
                    12: [(1, 1, 1), (1, 1, 1)],
                    24: [(1, 1, 1), (1, 1, 1)],
                }
                float3[] translations.timeSamples = {
                    1: [(0, 0, 0), (0, 0.5309847, 0)],
                    12: [(0, 0, 0), (0, 0.5309847, 0)],
                    24: [(0, 0, 0), (0, 0.5309847, 0)],
                }
            }
        }
    }
}

となります.

あとは,AndroidのScene Viewerと同様に,さらにios-srcを加えて,

<model-viewer src="/path/to/hoge.glb"
                  ios-src="/path/to/hoge.usda"
                  background-color="#70BCD1"
                  shadow-intensity="1"
                  camera-controls
                  autoplay
                  interaction-prompt="auto"
                  auto-rotate ar magic-leap></model-viewer>

でOKです(usdzにしたい人はxcrun usdz_converter hoge.usda hoge.usdz -v -a -lすれば良い.usdzは要はzipなので拡張子をzipにすれば開けたりする).

これでようやく

とAndroid, iOS双方で動くものが得られました.

最後に

適切なモデルさえあれば 手軽にAndroid, iOS両方でARできるのでラクですね〜〜()

とはいえ,Androidでは大体のアニメーションは動くのでほぼ問題ないですし,iOSも実機側でもMacと同じレベルで対応できれば問題なくなるので,十分期待はできると信じたいです.

あとは複数モデルの同時表示とか位置・向き・大きさ等のAPIとかが来れば良いなぁ〜〜〜