Blender + AR.js + A-FrameでWebARを作る方法
はじめに
この記事ではBlenderでモデリングした自作の3Dモデルを使ってWebARを実装する手順について解説しています。
WebARとは
Webブラウザ上で動くAR(拡張現実)です。
ブラウザ上でカメラを起動させてARマーカーを読み取ると、3Dモデルなどのコンテンツが表示されます。
実際に見てみよう
スマホで下のQRコードを読み取ってみてください。
URLから飛んだ先でカメラを許可してQRコードを写すと、下の画像のように表示されると思います。
これがWebARです。
この一連の仕組みはAR.jsというJavaScriptのライブラリと、A-Frameというフレームワークで作ることができます。[1]
実装に必要なもの
- 3Dモデルのデータ(glTF形式)
- QRコードの画像
- ARマーカー(
.patt
と.png
ファイル) - レンタルサーバー
- テキストエディタ
今回ネックになるのがWebARアプリのURLへ遷移させる方法なのですが、今回はQRコードの画像をARマーカー化することで、画像1枚でURLへのアクセスとARマーカーの読み取りができるようにします。
作成手順
1.プロジェクトフォルダの作成
まず、データを保存しておくプロジェクトフォルダを作っておきます。
今回はwebar
というフォルダの中にindex.html
というHTMLファイルとassets
というフォルダを作りました。
3Dモデルの.glb
ファイルとARマーカーの.patt
ファイルはassets
フォルダの中に格納するようにします。
2.3Dモデルの用意
Blenderで作った3DモデルをglTF形式で保存します。
マテリアルがプリンシプルBSDFになっているか確認
マテリアルのサーフェスの種類が「ディフューズBSDF」など、プリンシプルBSDF以外になっているとマテリアルが反映されません。
「マテリアル」タブを開いて「プリンシプルBSDF」に変更します。
全てのオブジェクトをメッシュに変換
カーブ、テキスト、ヘアーパーティクルなどのオブジェクトは全てメッシュに変換します。
オブジェクトを選択した状態で、「オブジェクト」>「変換」>「メッシュ」
ヘアーパーティクルのメッシュ変換は下記リンクを参考にしました。
モディファイアーを全て適用
サブディビジョンサーフェスなどのモディファイアーを全て適用させます。
自動でやる場合
エクスポート画面でできます。後述のエクスポートの章を見てください。
手動でやる場合
自動でやったら適用されていない箇所がある。表示がおかしい。そういった場合は手動で行います。
「モディファイアー」タブを開き、「適用」ボタンを押します。
オブジェクトがリンク複製されている場合は、モディファイアーの適用ボタンが押せません。
その場合は、該当のオブジェクトを選択した状態で、「オブジェクト」>「関係」>「シングルユーザー化」>「オブジェクトとデータ」を選択すると、適用ボタンが押せるようになります。
モディファイアーが未適用のままだと、モディファイアーを設定する前の状態で書き出されてしまいますので注意です。
エクスポート
オブジェクトを選択した状態で「ファイル」>「エクスポート」>「glTF 2.0」を選択
↓出力オプションのダイアログが表示されます。
フォーマットは「glTFバイナリ(.glb)」を選択します。
「ジオメトリ」>「モディファイアーを適用」にチェックを入れると、一括で適用してくれます。
テクスチャを設定している場合は、「UV」にチェックを入れると、オブジェクトと一緒にテクスチャも書き出してくれます。
エクスポートしたファイルをglTF Viewerで見てみます。
特に問題なく表示されていたらOKです。
3.QRコードを作成
QRコード作成ジェネレーターのQR Code GeneratorでQRコードを作ります。
今回は https://ドメイン名/webar/ で作りました。
4.ARマーカーを作成
ARマーカー作成ジェネレーターのAR.js Marker TrainingでARマーカーを作ります。
UPLOAD
ボタンを押して先ほど作ったQRコードの画像をアップします。
Pattern Ratioの値は黒い枠の太さです。
スライドバーを動かすことで変更が可能で、値を小さくすると枠が太く、値を大きくすると枠が細くなります。
しかし枠の太さを変更するとコードを書き加える必要があるので、一旦今回は変更しないことにします。
QRコードの画像をアップできたら、画面真ん中のDOWNLOAD MAKER
とDOWNLOAD IMAGE
ボタンを押して、.patt
と.png
ファイルを入手します。
補足
本記事の実際に見てみようで載せたマーカーの画像のように、ダウンロードした.png
ファイルの画像をPhotoshopなどで加工してもOKです。
ただし、黒い枠は改変しないようにします。
また、黒い枠の周りの余白も狭くしない方が良いです。
理由は、枠のサイズがジェネレーターで作ったものと違っていると、マーカーが認識されないからです。
また、枠の周りに余白が無いと、マーカーの認識精度が低下してしまいます。
マーカーの縦横の比率を保持したまま拡大縮小するのはOKです(テスト済み)。
5.コードを書く
index.html
にコードを書いていきます。
書き方はAR.jsの公式ドキュメントとA-Frameの公式ドキュメントを参考にしています。
まず<head>
要素の中に<script>
要素でA-FrameとAR.jsを読み込みます。
<head>
<!-- A-Frameを読み込む -->
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<!-- AR.jsを読み込む -->
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>
</head>
<body>
要素にstyle
属性を追加します。
カメラ起動時のブラウザ画面は全画面表示なので、余白やスクロールが出ないようにするためです。
<body style="margin: 0; overflow: hidden">
</body>
次に<body>
の中に<a-scene>
要素を追加します。
<!-- A-FrameにAR.jsを紐づけ、VRボタン非表示、深度バッファ追加 -->
<a-scene embedded arjs vr-mode-ui="enabled: false;" renderer="logarithmicDepthBuffer: true;">
</a-scene>
<a-scene>
はオブジェクトを配置するための舞台です。
ここにembedded
とarjs
属性を追加し、A-FrameとAR.jsを紐づけます。
このとき任意でvr-mode-ui="enabled: false;"
も追加することで、ブラウザ起動時に右下に表示されるVRのボタンを非表示にできます。
また、renderer="logarithmicDepthBuffer: true;"
も追加して、対数深度バッファを設定します。
深度バッファを設定しないと、3Dモデルの表示にノイズが走ったり、チカチカ明滅したりすることがあります。
門松の表示がジャギっている
これは同じ場所に複数のポリゴンがある場合、奥行きの計算がうまく出来ず、レンダリングの順番が競合するために起こる現象で、「Z-fighting(Zファイティング)」と呼ばれます。
対数深度バッファというのは、同じ場所にあるポリゴンの前後関係を明確にして、正しい順番でレンダリングするための方法です。
深度バッファを設定すると、下の写真のように滑らかな表示になります。
次に<a-scene>
の中には、以下のタグを追加します。
<!-- 3Dモデルを読み込む -->
<a-assets>
<a-asset-item id="tiger" src="./assets/tiger.glb"></a-asset-item>
</a-assets>
<!-- マーカーの.pattファイルを読み込む -->
<a-marker type="pattern" url="./asset/pattern-tiger.patt">
<!-- 3Dモデルを呼び出す -->
<a-entity gltf-model="#tiger"></a-entity>
</a-marker>
<!-- カメラを追加 -->
<a-entity camera></a-entity>
まず、<a-asset-item>
要素で3Dモデルを読み込みます。
<a-marker>
のtype
属性は3種類ありますが[2]、ジェネレーターで作ったパターンマーカーを使うので、ここではpatternにします。
次に読み込んだ3Dモデルを<a-entity>
のgltf-model
属性で呼び出します。
最後にカメラを追加して、完了です!
↓全体のコードです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- A-Frame を読み込む -->
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<!-- AR.js を読み込む -->
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>
<title>任意のページタイトル</title>
</head>
<body style="margin: 0; overflow: hidden">
<!-- A-FrameにAR.jsを紐づけ、VRボタン非表示、深度バッファ追加 -->
<a-scene embedded arjs vr-mode-ui="enabled: false;" renderer="logarithmicDepthBuffer: true;">
<!-- 3DCGモデルを読み込む -->
<a-assets>
<a-asset-item id="tiger" src="./asset/tiger.glb"></a-asset-item>
</a-assets>
<!-- マーカーの.pattファイルを読み込む -->
<a-marker type="pattern" url="./asset/pattern-tiger.patt">
<!-- 3Dモデルを呼び出す -->
<a-entity gltf-model="#tiger"></a-entity>
</a-marker>
<!-- カメラを追加 -->
<a-entity camera></a-entity>
</a-scene>
</body>
</html>
6.サーバーにアップ
プロジェクトフォルダをサーバーにアップロードします。
そして作ったARマーカーをパソコンのディスプレイに映したり、印刷して、スマホで読み取れるように準備します。
スマホのQRコードリーダーでQRコードを読み取るとカメラが起動するので、そのままQRコードを読み取り続けると3Dモデルが表示されます。
表示されたら成功です!
Tips
サンプルコードをアレンジする方法をまとめました。
ARマーカーの黒枠を細くする
Pattern Ratioの値を変更し、DOWNLOAD
ボタンを押して.patt
ファイルと.png
ファイルをダウンロードします。
Pattern Ratioの値は覚えておきます。
次に、<a-scene>
のarjs
属性にプロパティを追加します。
<a-scene embedded arjs="patternRatio: 設定した値">
例:Pattern Ratioを0.9に設定した場合
<a-scene embedded arjs="patternRatio: 0.9">
3Dモデルの向きを変える
これは、Blender側で変更することもできますし、コードを書き加える方法でもできます。
<a-entity>
にrotation
属性を追加します。
<a-entity gltf-model="#3Dモデルのid" rotation="X軸 Y軸 Z軸"></a-entity>
X軸、Y軸、Z軸を基準に、回転させる角度を入力します。
X軸は奥から手前へ回転します。
Y軸は時計回りに回転します。
Z軸は反時計回りに回転します。
反対向きに回転させたいときは、マイナスを付けます。
余談になりますが、通常、ARマーカーは座布団のように3Dモデルの真下に来ます。
イメージ図
パソコンのディスプレイなどにARマーカーを表示してスマホで読み取る場合は、ディスプレイが地面に対して垂直になっているので、3Dモデルの頭しか見えない、という事態が起きます。
下の画像のように3DモデルをX軸方向へマイナス90度傾けて寝かせてあげることで、ARマーカーと並行に表示させることができます。
3Dモデルに回転アニメーションを付ける
下記Twitterの投稿のように、3Dモデルを回転させることができます。
<a-entity>
にanimation
属性を追加します。
<a-entity gltf-model="#3Dモデルのid" animation="property: rotation; to: X軸 Y軸 Z軸; loop: true; dur: 10000"></a-entity>
to: X軸 Y軸 Z軸
は、それぞれの軸に対して何度回転させるかを0-360度の間で入力します。
例えば、Z軸方向に1回転させたい場合は、to: 0 0 360
と入力します。
loop: true
にすると、無限に回転します。
dur
というのは「duration(間隔)」のことで、アニメーションの1サイクルの長さです。
単位は「millisecond」なので、1秒は1000ミリ秒になります。
デフォルトではdur: 1000
で1秒と設定されています。ここでは10秒になるように変更しています。
お好きな秒数に設定してください。
A-Frameの基礎知識
サンプルコード以外のものを実装しようとするとA-Frameの知識が必要になる場合が多いので、A-Frameについても参考程度に書き留めておきたいと思います。
Entity Component System
A-Frameのコード記法は、3Dやゲーム開発でよく使われる Entity Component System という設計方法に基づいています。
これは文字通り、Entity、Component、Systemの3つの要素で構成されます。
Entity(エンティティ) とは、その世界に存在する物体を指します。ゲームで例えると、キャラクター、武器、乗り物などです。
エンティティだけでは機能せず、後述のコンポーネントで肉付けをしていくことによって実体化します。
A-Frameでは<a-entity>
要素で表現されます。
Component(コンポーネント) とは、物体に付随しているデータのことです。
ゲームで例えると、キャラクターの攻撃力や防御力、体力など、そのキャラクターを形作る要素に当たります。
A-Frameでは<a-entity>
のHTML属性で表現されます。
EntityとComponentの相関
System(システム) とは、その世界の設定のことです。
ゲームで例えると、キャラクターと敵の衝突時に体力を減らす(体力をマイナスして更新する)というシステム設定などがこれに当たります。
A-Frameでは<a-scene>
のHTML属性で表現されます。
まとめると、
-
<a-scene>
のHTML属性で全体に関わる設定を書き、 -
<a-entity>
でオブジェクトを追加し、 -
<a-entity>
のHTML属性でオブジェクトに肉付けをする
というのが、A-Frameの基本的な書き方です。
Asset Management System
5.コードを書くの章において、3Dモデルを表示させるコードを書きました。
これはモデルを一旦読み込んでから呼び出すという手順を踏んでいます。これはなぜなのでしょうか?
A-Frameには、Asset Management System というシステムが存在します。
オブジェクト(アセット)の読み込みと表示を分けることで、読み込み時間の短縮やレンダリングパフォーマンスを向上させることができるというものです。
まず、<a-assets>
の中に<a-asset-item>
で3Dモデルを読み込みます。
呼び出すときは、<a-entity>
のgltf-model
属性で呼び出します。
<a-scene>
<a-assets>
<a-asset-item id="3Dモデル" src="3Dモデル.glb"></a-asset-item>
</a-assets>
<a-entity gltf-model="#3Dモデル"></a-entity>
</a-scene>
このように、3Dモデルなどのアセットを事前に読み込んでおくことで、遅延なくARコンテンツを表示させることができます。
おわりに
BlenderとAR.jsとA-Frameを使ってWebARを作成する方法を解説してみました。
JSを書く必要がなくHTMLだけで完結するため、すごく簡単に作ることができるのでおすすめです。
みなさんも良かったら作ってみてください!
参考リンク
公式ドキュメント
Tips
A-Frameの基礎知識
Blender
Zファイティング(Z-fighting)
更新履歴
2023年2月9日
- A-Frameのバージョンを最新版に書き換え
- Zファイティング、深度バッファの記述を追加
- 参考リンクにZファイティングの項目を追加
2022年2月12日
- 記事を投稿
Discussion
はじめまして。
Akaneさんのこの投稿を拝見しながら、勉強している初心者です。
とてもわかりやすい内容で、順序通り行った際にしっかり表示させることが出来ました。
ありがとうございます!
ひとつ質問させてください。
私がBlenderで作成したモデリングデータなのですが、記載通りに出力した後、glTF Viewerにドラッグ&ドロップすると正確に表示されるのですが、実装して、スマホ上で表示させるとオブジェクトによってジャギが走ってしまう現象(チラついてしっかりと表示されていない)になってしまいます。
AkaneさんがあげていらっしゃるQRコードをスマホで読み込むと門松あたりが同様のジャギが走った表示になるのですが、原因はわかりますでしょうか?
スマホによる性能の違い?オブジェクトの厚さがないから?
もし改善できるとしたら対処法も知りたい次第です。
何卒よろしくお願いいたします!
iidaaaさん、初めまして。嬉しいお言葉ありがとうございます!
ジャギが走ってしまう現象ですが、わたしのスマホ(Android)でもiidaaaさんと同様の表示になりました。↓
調べた結果、この現象は「Zファイティング」と呼ばれていることがわかりました。
同じ場所に複数のポリゴンがある場合、奥行きの計算がうまく出来ずレンダリングの順番が競合してしまうため、そのポリゴンの表示がジャギったり、チカチカ明滅することがあるそうです。
対処法は、
<a-scene>
にrenderer="logarithmicDepthBuffer: true;"
を追加してください。これで下の写真のように滑らかに表示されると思います。↓
※このコードは、対数深度バッファ(logarithmicDepthBuffer)を追加する=ポリゴンを正しい前後関係でレンダリングする、という意味になります。
ジャギるのは手ブレのせいかなと思っていて、深く追求していなかったのですが、iidaaaさんのおかげでさらに綺麗に表示させることができました。ありがとうございます!
この内容は、記事にも追記しておきます。
コードを試していただいても改善されない場合は、またご返信いただけますと幸いです。
よろしくお願いいたします。
Akaneさん
丁寧に解説いただき、また、解決方法までご享受いただきありがとうございました!
挙げていただいたコードを入力したらZファイティングがない状態で表示されました。
大変助かりました!
余談なのですが、
別の方が提示していたコードとも組み合わせてBlender上でアニメーションをつけたデータをUPして試したのですが、こちらだと一筋縄では改善されなかったです。(表示されるがZファイティングが改善ならず)
A-Frameのバージョンの違い、A-FrameとAR.jsを読み込む際のhttps以降のアドレスが違う、等少々コード記述が違う部分がある
みたいなので、そこが関係しているのかなと思います。少し自分でも解決策を模索してみます。
もし埒が明かない場合はまたご相談させていただきたく思います。
何卒よろしくお願いします!
iidaaaさん、お返事が遅くなってしまいすみません。
ご提案させていただいたコードで正常に表示ができたとのことで、ほっとしております。
Blenderでアニメーションをつけた3Dモデルでは、まだ不具合があるということですね。
こちらは解決策が思い浮かばずすみません。
また私の方でも試してみて、何かわかりましたらこちらに書き込みいたします!