8th WallでマーカーレスARを試す
8th Wallとは
8th Wall社が提供するARプラットフォームです。
月1000viewまでは無料で利用できます。ただしローカル環境のみです。
使い方
アカウントを作成する
8th Wallの公式サイトでアカウント作成します。
利用するデバイスを認証する
「Device Authorization」ボタンを押します。
モバイルぼ場合は実機で、QRコードを読み込みます。
デバイスが認証されます。
サンプルファイルを動かしてみる
サイドナビの「QUICK START」を押します。
QRコードが表示されるので、認証したデバイスでQRコードを読み込みます。
サンプルファイルが表示されます。
他のサンプルはgithubからダウンロードすることができます。
ダウンロードしたファイルは以下です。
examplesの中にサンプルがあるので、こちらをひな形にプロジェクトを作成すると良さそうです。
. ├── README.md ├── examples ├── gettingstarted ├── images ├── index.html ├── serve └── xrextras
プロジェクトを作成する
サイドナビの「DASHBOARD」から、「Create a new project」を押します。
プロジェクト名を入力してプロジェクトを作成すると、アプリケーションKEYが作成されます。
このKEYを<script async src="//apps.8thwall.com/xrweb?appKey=XXXXXXXX"></script>
に設定します。
以下はサンプルファイルの
/examples/threejs/placeground/index.html`になります。
<!doctype html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>8th Wall Web: three.js</title> <link rel="stylesheet" type="text/css" href="index.css"> <!-- THREE.js must be supplied --> <script src="//cdnjs.cloudflare.com/ajax/libs/three.js/93/three.min.js"></script> <!-- Required to load glTF (.gltf or .glb) models --> <script src="//cdn.rawgit.com/mrdoob/three.js/master/examples/js/loaders/GLTFLoader.js"></script> <!-- Javascript tweening engine --> <script src="//cdnjs.cloudflare.com/ajax/libs/tween.js/16.3.5/Tween.min.js"></script> <!-- XR Extras - provides utilities like load screen, almost there, and error handling. See github.com/8thwall/web/xrextras --> <script src="//cdn.8thwall.com/web/xrextras/xrextras.js"></script> <!-- 8thWall Web - Replace the app key here with your own app key --> <script async src="//apps.8thwall.com/xrweb?appKey=XXXXXXXX"></script> <!-- client code --> <script src="index.js"></script> </head> <body> <canvas id="camerafeed"></canvas> </body> </html>
ローカルファイルを起動
サンプルファイルの/examples/threejs/placeground
をひな形に新しいプロジェクトを作成してみます。
まず上記のディレクトリをコピーして複製します。
名前はdinosaur
としました。
. ├── README.md ├── dinosaur │ ├── index.html │ ├── index.js │ ├── model.gltf │ ├── test.md │ └── tree.glb ├── examples ├── gettingstarted ├── images ├── index.html ├── serve └── xrextras
次にnpmモジュールをインストールします。
cd serve npm i cd ..
ローカルサーバーを起動します。
./serve/bin/serve -d dinosaur
サンプルファイルを少し変更してみる
サンプルファイルをひな形に、以下のようにしてみました。
// Copyright (c) 2018 8th Wall, Inc. // Returns a pipeline module that initializes the threejs scene when the camera feed starts, and // handles subsequent spawning of a glb model whenever the scene is tapped. const placegroundScenePipelineModule = () => { const modelFile = ['models/d1.glb','models/d2.glb','models/d3.glb','models/d4.glb','models/d5.glb','models/tree.glb','models/tree2.glb'] // 3D model to spawn at tap const startScale = new THREE.Vector3(0.0001, 0.0001, 0.0001) // Initial scale value for our model const endScale = new THREE.Vector3(0.003, 0.003, 0.003) // Ending scale value for our model const animationMillis = 750 // Animate over 0.75 seconds const raycaster = new THREE.Raycaster() const tapPosition = new THREE.Vector2() const loader = new THREE.GLTFLoader() // This comes from GLTFLoader.js. let surface // Transparent surface for raycasting for object placement. // Populates some object into an XR scene and sets the initial camera position. The scene and // camera come from xr3js, and are only available in the camera loop lifecycle onStart() or later. const initXrScene = ({ scene, camera }) => { console.log('initXrScene') surface = new THREE.Mesh( new THREE.PlaneGeometry( 100, 100, 1, 1 ), new THREE.MeshLambertMaterial({ color: 0xffff00, transparent: true, opacity: 0.0, side: THREE.DoubleSide }) ) surface.rotateX(-Math.PI / 2) surface.position.set(0, 0, 0) scene.add(surface) const light = new THREE.AmbientLight( 0x404040, 5 ) scene.add(light) // Add soft white light to the scene. // 影を付けたいので平行光源をつけました const dLight = new THREE.DirectionalLight(0xffffff) scene.add(dLight) // Set the initial camera position relative to the scene we just laid out. This must be at a // height greater than y=0. camera.position.set(0, 3, 0) } const animateIn = (model, pointX, pointZ, yDegrees) => { console.log(`animateIn: ${pointX}, ${pointZ}, ${yDegrees}`) const scale = Object.assign({}, startScale) model.scene.rotation.set(0.0, yDegrees, 0.0) model.scene.position.set(pointX, 0.0, pointZ) model.scene.scale.set(scale.x, scale.y, scale.z) XR.Threejs.xrScene().scene.add(model.scene) new TWEEN.Tween(scale) .to(endScale, animationMillis) .easing(TWEEN.Easing.Elastic.Out) // Use an easing function to make the animation smooth. .onUpdate(() => { model.scene.scale.set(scale.x, scale.y, scale.z) }) .start() // Start the tween immediately. } const getModelFile = () => { const modelFileLength = modelFile.length const n = Math.floor(Math.random() * modelFileLength) return modelFile[n] } // Load the glb model at the requested point on the surface. const placeObject = (pointX, pointZ) => { console.log(`placing at ${pointX}, ${pointZ}`) loader.load( getModelFile(), // resource URL. (gltf) => { animateIn(gltf, pointX, pointZ, Math.random() * 360) }, // loaded handler. (xhr) => {console.log(`${(xhr.loaded / xhr.total * 100 )}% loaded`)}, // progress handler. (error) => {console.log('An error happened')} // error handler. ) } const placeObjectTouchHandler = (e) => { console.log('placeObjectTouchHandler') // Call XrController.recenter() when the canvas is tapped with two fingers. This resets the // AR camera to the position specified by XrController.updateCameraProjectionMatrix() above. if (e.touches.length == 2) { XR.XrController.recenter() } if (e.touches.length > 2) { return } // If the canvas is tapped with one finger and hits the "surface", spawn an object. const {scene, camera} = XR.Threejs.xrScene() // calculate tap position in normalized device coordinates (-1 to +1) for both components. tapPosition.x = (e.touches[0].clientX / window.innerWidth) * 2 - 1 tapPosition.y = - (e.touches[0].clientY / window.innerHeight) * 2 + 1 // Update the picking ray with the camera and tap position. raycaster.setFromCamera(tapPosition, camera) // Raycast against the "surface" object. const intersects = raycaster.intersectObject(surface) if (intersects.length == 1 && intersects[0].object == surface) { placeObject(intersects[0].point.x, intersects[0].point.z) } } return { // Pipeline modules need a name. It can be whatever you want but must be unique within your app. name: 'placeground', // onStart is called once when the camera feed begins. In this case, we need to wait for the // XR.Threejs scene to be ready before we can access it to add content. It was created in // XR.Threejs.pipelineModule()'s onStart method. onStart: ({canvas, canvasWidth, canvasHeight}) => { const {scene, camera} = XR.Threejs.xrScene() // Get the 3js sceen from xr3js. initXrScene({ scene, camera }) // Add objects to the scene and set starting camera position. canvas.addEventListener('touchstart', placeObjectTouchHandler, true) // Add touch listener. // Enable TWEEN animations. animate() function animate(time) { requestAnimationFrame(animate) TWEEN.update(time) } // Sync the xr controller's 6DoF position and camera paremeters with our scene. XR.XrController.updateCameraProjectionMatrix({ origin: camera.position, facing: camera.quaternion, }) }, } } const onxrloaded = () => { XR.addCameraPipelineModules([ // Add camera pipeline modules. // Existing pipeline modules. XR.GlTextureRenderer.pipelineModule(), // Draws the camera feed. XR.Threejs.pipelineModule(), // Creates a ThreeJS AR Scene. XR.XrController.pipelineModule(), // Enables SLAM tracking. XRExtras.AlmostThere.pipelineModule(), // Detects unsupported browsers and gives hints. XRExtras.FullWindowCanvas.pipelineModule(), // Modifies the canvas to fill the window. XRExtras.Loading.pipelineModule(), // Manages the loading screen on startup. XRExtras.RuntimeError.pipelineModule(), // Shows an error image on runtime error. // Custom pipeline modules. placegroundScenePipelineModule(), ]) // Open the camera and start running the camera run loop. XR.run({canvas: document.getElementById('camerafeed')}) } // Show loading screen before the full XR library has been loaded. const load = () => { XRExtras.Loading.showLoading({onxrloaded}) } window.onload = () => { window.XRExtras ? load() : window.addEventListener('xrextrasloaded', load) }
変更したのは、並行光源を追加したのと、modelを複数ランダムに表示することくらいです。
出来上がりがこちらです。
無料枠だと、なかなか制限が多いので使いづらいですが、簡単にマーカーレスARを試すことができました。