Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
37
Help us understand the problem. What is going on with this article?
@ukonpower

WebブラウザでマーカーレスAR (Three.js + 8th Wall)

More than 1 year has passed since last update.

概要

WebGLでのARライブラリといえばAR.jsなどがありますが、こちらはマーカーが必要で、あまり実用的ではありません。
そこでこちらの、8th Wallというサービスを使って、マーカーレスARをThree.jsで実装してみたいと思います。

こんなの作ります。


8th Wallについて

8th Wallは8th Wall SLAM(Simultaneous Localization and Mapping)という独自の6DoF ARエンジンを使い、Webやアプリに汎用的に使用できるAPIを提供しているようです。
参考(公式ページ)
また、8th Wallには以下の3つのモード(?)があるようです。

8th Wall Web

ブラウザで動作するARのライブラリです。Three.js、A-Frame、AmazonSumerianに対応しているようです。
今回はこちらを使います。

AR Camera

image.png
Webブラウザ上のGUI上でぽちぽちやってARモデルを表示するページを作れるツールのようです。ARkitのAR Quick Look的な立ち位置でしょうか。

8th Wall XR

Unity用のライブラリらしいです。通常6DofのARはARkitかARCoreに対応しているデバイスのみですが8th Wall XRを使うと、ARkitやARCoreに対応していないデバイスには代わりにSLAMを使用してカバーするようです。
image.png
強い

費用

個人向けには1000view/月以下のローカル開発での使用までは無料のようです。
それ以上については公式ページを参照してください。

8th Wall - Pricing

作ってみよう

実装して行きます。
公式サンプルを参考にしました。

実装の流れ

three.jsでの8th WallはPipeline Moduleというひとまとまりの処理を複数繋げてARを実装していくようです。Pipeline Moduleは独自に定義することもでき、それを使って様々な処理を行わせるようです。

以下が実装の大まかな流れです。

  1. APIキーの発行、8thWall APIを読み込ませる
  2. タップした場所にモデルをロードするPipeline Moduleを定義
  3. Pipeline Moduleをつなぎ、アプリを動作させる。

1.APIキーの発行・登録

アカウントを作成し、ログイン後、Web Developer > Create a new Web Appを選択。
image.png

プロジェクト名を入力し、Createをクリック
image.png

APIキーが発行されます。
image.png

Three.jsの場合、以下のXXXXを自分のAPIキーに書き換えます。

<script defer src="https://apps.8thwall.com/xrweb?appKey=XXXXXXXXXX"></script>

ロード画面やエラー処理などのユーティリティ(?)を読み込ませます。

<script src="//cdn.8thwall.com/web/xrextras/xrextras.js"></script>

HTML全体はこんな感じです。

<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>8thWall - Demo</title>
    <script src="./js/script.js"></script>
    <script src="//cdn.8thwall.com/web/xrextras/xrextras.js"></script>
    <script defer src="https://apps.8thwall.com/xrweb?appKey=XXXXXXXXXX"></script>
    <link rel="stylesheet" href="./css/style.css">
</head>
<body>
    <canvas id="canvas"></canvas>
</body>

</html>

2.カスタムPipeLineModuleを作成

PipeLineModuleはオブジェクトで、nameプロパティと実行開始時に呼ばれるonStartプロパティを最低限含んでいれば動くようです。

let pipelineModule = {
    name: 'name',
    onStart: () => {
    }
}

onStartにはCanvasや回転の情報などが送られてくるようです。
image.png
引用(公式ドキュメント)

PipeLineMoudle全体は以下のようになりました。
サンプルでは関数の返り値をpipelineModuleとしていましたが、自分はクラスに持たせちゃいました。

export default class putModelPipelineModule{
    constructor(){
        this.raycaster = new THREE.Raycaster();
        this.loader = new THREE.GLTFLoader();
        this.scene;
        this.camera;

        this.xrmodule = {
            name: 'putModel',
            onStart: ({canvas,canvasWidth,canvasHeight}) => {
                let {scene, camera} = XR.Threejs.xrScene();
                this.scene = scene;
                this.camera = camera;
                this.initScene();
                canvas.addEventListener('touchstart', this.onTouch.bind(this), true)
                XR.XrController.updateCameraProjectionMatrix({
                    origin: this.camera.position,
                    facing: this.camera.quaternion,
                })
            }
        }
    }

    initScene(){
        let groundGeo = new THREE.PlaneGeometry(100,100,10,10);
        let groundMat = new THREE.MeshBasicMaterial({
            transparent: true,
            opacity: 0.0,
            side: THREE.DoubleSide
        });
        this.ground = new THREE.Mesh(groundGeo,groundMat);
        this.ground.rotateX(-Math.PI / 2);
        this.ground.position.set(0,0,0);
        this.scene.add(this.ground);

        this.light = new THREE.DirectionalLight();
        this.light.position.set(0,10,0);
        this.scene.add(this.light);
        this.camera.position.set(0, 1, 0);

        this.alight = new THREE.AmbientLight();
        this.scene.add(this.alight);
    }

    onTouch(e){
        let tapPos = new THREE.Vector2((e.touches[0].clientX / window.innerWidth) * 2 - 1,- (e.touches[0].clientY / window.innerHeight) * 2 + 1);
        this.raycaster.setFromCamera(tapPos,this.camera);

        let intersects = this.raycaster.intersectObject(this.ground);
        if (intersects.length == 1) {
            let o = intersects[0];
            this.loader.load('assets/models/baku.glb',( gltf ) => {
                let model = gltf.scene;
                let baku = model.getObjectByName('baku_lowpoly');
                baku.position.set(o.point.x,o.point.y,o.point.z);
                this.scene.add(baku);
            });
        }

    }
}

基本的には通常のThree.jsと変わらずです。
SceneはXR.Threejs.xrScene()から取得できるので、そこに好きなオブジェクトをおけば、AR表示してくれます。すごい。

3.PipeLineModuleを組み合わせる

さっき作ったpipelineModuleを初期化します。

this.pModelModule = new PutModelModule();

一覧のPipelineModuleをXR.addCameraPipeLlineModulesを用いて登録します。
組み込みのPipelineModuleもいろいろあるようですが、よくわからないのでサンプル通りに設定しました。

XR.addCameraPipelineModules([
    XR.GlTextureRenderer.pipelineModule(),
    XR.Threejs.pipelineModule(),
    XR.XrController.pipelineModule(),
    XRExtras.AlmostThere.pipelineModule(),
    XRExtras.FullWindowCanvas.pipelineModule(),
    XRExtras.Loading.pipelineModule(),
    XRExtras.RuntimeError.pipelineModule(),
    this.pModelModule.xrmodule, //カスタムpipelineModule
])

最後に、XR.runを実行します。描画用のcanvasを一緒に渡します。

XR.run({
    canvas: document.getElementById('canvas')
})

全体はこんな感じです。

import { BaseScene } from './utils/ore-three/';
import PutModelModule from './utils/putModelPipelineModule';

export default class MainScene extends BaseScene {
    constructor(renderer) {
        super(renderer);
        this.pModelModule = new PutModelModule();

        if(window.XR){
            this.init();
        }else{
            window.addEventListener('xrloaded', this.init.bind(this));
        }
    }

    init() {
        XR.addCameraPipelineModules([
            XR.GlTextureRenderer.pipelineModule(),
            XR.Threejs.pipelineModule(),
            XR.XrController.pipelineModule(),
            XRExtras.AlmostThere.pipelineModule(),
            XRExtras.FullWindowCanvas.pipelineModule(),
            XRExtras.Loading.pipelineModule(),
            XRExtras.RuntimeError.pipelineModule(),
            this.pModelModule.xrmodule,
        ])

        XR.run({
            canvas: document.getElementById('canvas')
        })
    }
}

最後に

今回は8th Wallを使用して、マーカーレスARを実装してみました。
ARkitやARCoreを使わず、ブラウザでできてしまうのは未来を感じました...
ドキュメントを見る限りまだまだ機能がたくさんあるようなので、色々やってみたいですね!

公式ドキュメント

37
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ukonpower
シェーダー!ブレンダー!
junni
デジタル領域における広告デザインの企画・制作を行っています。アソビゴコロで、世界をハッピーに変えていく。それがわたしたちの理念です。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
37
Help us understand the problem. What is going on with this article?