📝

Babylon.jsではじめるHoloLens 2 / Meta Quest対応WebXR入門(イベントレポート)

2023/02/11に公開

概要

2/11(土)開催の下記イベントのレポート記事兼プラスαの実装記事になります。
https://xr-fukuoka.connpass.com/event/270139/

事前準備

  • 機材の準備
    • HoloLens2またはOculus Quest1,2(要OSアップデート)
    • 開発用PC (Win/MacどちらもOK)
  • ハンドトラッキングの有効化
    • HoloLens2
      • Windowsアップデートで最新版に更新
      • Edgeを起動
      • アドレスバーに edge://flags と入力して検索
      • webxrで検索
      • WebXR IncubationsをEnabledに変更
    • OculusQuest
      • 最新版にアップデート
      • ハンドトラッキング機能をON
        (参考: https://apicodes.hatenablog.com/entry/oculus-handtracking )
      • Webブラウザを起動
      • アドレスバーに chrome://flags と入力して検索
      • handで検索
      • WebXR experiences with hand and joints trackingをEnabledに変更
  • Glitchのアカウント準備
  • ハンズオン資料のダウンロード

開発

開発環境は、Web上でコーディングから公開までできるGlitchを使用
https://glitch.com/
テンプレートサイトにアクセス
https://glitch.com/~babylon-template
Remix your ownをクリック
⇒下記のような画面が立ち上がる

詳細はこの記事では割愛しますが、スライドでは各部の解説までされています

いきなり完成品で申し訳ないですが、最終的な成果物はこちら

<html>
  <head>
    <!--babylon.jsの読み込み-->
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>    
    <!--実際の処理の実装-->
    <script type="text/javascript">
      let canvas; //canvas要素 (描画領域)
      let engine; //Babylon.jsによる描画機能
      let scene; //仮想3D空間
      //ページの読み込み終了後に各種初期化を行う
      window.onload = function() {
        //描画領域(canvas要素)をHTMLから取得
        canvas = document.getElementById("renderCanvas");
        //Babylon.jsを使ってcanvasに描画する準備 (引数:描画先,アンチエイリアス)
        engine = new BABYLON.Engine(canvas, true);
        //カメラやライトの設定を行ったscene(3D空間)を作成
        createScene(); 
        //描画オブジェクトの作成
        addObjects();     
        //XRモードの初期化
        initializeXR();        
        //毎フレーム描画を更新
        engine.runRenderLoop(function () {
          if (scene && scene.activeCamera) {
            scene.render();
          }
        });        
      }
      //3D空間の初期化
      function createScene() {
        //3D空間を作成
        scene = new BABYLON.Scene(engine);
        //背景色を設定
        scene.clearColor = new BABYLON.Color3.Black();
        //Lightを設定 (引数:名前,反射の方向,追加先)
        let light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, -0.5));
        //カメラを作成(引数:名前, alpha, beta, 注視点からの距離, 注視点,追加先) 
        let camera = new BABYLON.ArcRotateCamera("camera", -Math.PI/2,Math.PI/2, 3, new BABYLON.Vector3(0, 0, 0));
        //マウスやキーボードによるカメラ操作を可能にする
        camera.attachControl(canvas, true);        
      }
      //WebXRの初期化
      async function initializeXR(){
        let xr = await scene.createDefaultXRExperienceAsync({/*ここでオプションも設定可能*/});
        //XRモード利用可能か不可かを確認
        if (!xr.baseExperience) {}
        else {
          //利用可能な場合はハンドトラッキングをオンにする 
          xr.baseExperience.featuresManager.enableFeature(BABYLON.WebXRFeatureName.HAND_TRACKING, "latest", {
            xrInput: xr.input
          });
        }        
      }
      //表示オブジェクトの初期化
      function addObjects(){    
        //地面を作成(原点の位置確認用。あとで削除してOK)
        let ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 1, height: 1});  
        //箱状のオブジェクト作成(30cm)
        let box = BABYLON.MeshBuilder.CreateBox("box", {width: 0.3, height: 0.3, depth: 0.3});  
        box.position.x = 0;
        box.position.y = 1;
        //箱の色をランダムに設定
        let boxMaterial = new BABYLON.StandardMaterial("material");
        boxMaterial.diffuseColor = BABYLON.Color3.Random();
        box.material = boxMaterial; 
        //8面体を作成
        let octa = BABYLON.MeshBuilder.CreatePolyhedron("octa", {type:1,size: 0.15}); 
        octa.position.x=0.5;
        octa.position.y=1;
        //boxに手で掴んで移動できる属性を追加
        let dragBehavior = new BABYLON.SixDofDragBehavior();
        //片手での操作のみ受け付ける(両手でのスケールでの挙動がおかしいため)
        dragBehavior.allowMultiPointer=false;
        //離れた位置からポインターで操作する場合は位置のみ追従
        dragBehavior.rotateWithMotionController=false;
        //上記設定をboxに適用
        box.addBehavior(dragBehavior); 
        //SceneLoaderを使って3Dモデルを読み込む
        BABYLON.SceneLoader.LoadAssetContainer(
          "https://cdn.glitch.global/d0ecf79e-59af-4280-90df-a28f3efaad3e/figure.glb?v=1675334298545", //3Dモデルが置かれたフォルダ or 3DモデルのURL
          "", //3Dモデルのファイル名。上記でモデルのURLを指定した場合は空でOK
          scene, //オブジェクトを追加する先のScene 
          function (container) {
            //Babylon.jsでは0番目のメッシュはただのroot。1番目にモデルの実体。
            let glbMesh = container.meshes[1];
            //スケールを10倍
            glbMesh.scaling=new BABYLON.Vector3(10,10,10);  
            //Y軸(=鉛直方向の軸)を中心に180度(=πラジアン)回転
            glbMesh.rotation =new BABYLON.Vector3(0, Math.PI, 0);    
            //読み込んだオブジェクト用にマニピュレーションの挙動を作成
            let dragBehavior2 = new BABYLON.SixDofDragBehavior();
            dragBehavior2.allowMultiPointer=false;
            dragBehavior2.rotateWithMotionController=false;
            glbMesh.addBehavior(dragBehavior2);             
            //scene(3D空間)にオブジェクトを追加
            scene.addMesh(glbMesh);
          }
        );        
      }  
    </script>
  </head>
  <body>
    <!--描画する領域-->
    <canvas id="renderCanvas" style="width: 100%; height: 100%;"></canvas> 
  </body>
</html>

+α実装

モデル変更

モデルはAssets > Upload An Assetを選択し、アップロードできます

アップロードしたモデルを選択すると、URLが作られているのでコピー

85行目付近のモデルURLをコピーしたものに差し替え
glbMeshのpositionなどを調整して完成

パススルー対応

Questでパススルーを使うには、45行目付近のscene.createDefaultXRExperienceAsyncのオプションを下記のように設定する

let xr = await scene.createDefaultXRExperienceAsync(
{
    uiOptions: {
    sessionMode: 'immersive-ar'
}                                         
});

このとき、モデルがカメラ位置の所に来てしまうので、58行目付近のXRモード利用可能のときの処理に下記を追記

//カメラの初期位置を調整
xr.baseExperience.sessionManager.onXRFrameObservable.addOnce(() => {
xr.baseExperience.camera.position = new BABYLON.Vector3(0,scene.activeCamera.position.y,-2);
});   

宣伝

参考

https://github.com/TakashiYoshinaga/babylonjs-webxr
https://www.docswell.com/s/Tks_Yoshinaga/5NRMM7-webxr

Discussion