markerRoot = new THREE.Group()
を使っているのは、AR.js のマーカー検出結果を「まとめて受け止める”空のコンテナ”」として機能させるためです。詳しく分解すると…
-
マーカー座標系の原点を作る
-
ArMarkerControls
は「マーカーを見つけたら、その位置・向きの変換行列(model‐view 行列)を指定のオブジェクトにコピーする」仕組みです。 - 直接
scene
やcamera
にコピーするとシーン全体やカメラの挙動がおかしくなるので、まずは “何も描かれていない空の Object3D” に書き込みます。
-
-
複数のメッシュを一括管理できる
-
THREE.Group
は子オブジェクトを複数持てるコンテナです。 -
markerRoot.add(meshA, meshB, …)
のように、マーカー上に表示したいモデル(立方体や glTF、UI など)をまとめてmarkerRoot
の子にぶら下げれば、 →markerRoot
の行列更新だけで 中のすべての子メッシュ が一斉に正しい位置・角度に動きます。
-
-
シーン構造をシンプルに保つ
-
Group
を使わずに、いきなりscene.add(mesh)
をしてしまうと、マーカーが見えないときでもオブジェクトがちらついたり、座標系の切り替え処理が複雑になります。 -
markerRoot.visible
を使えば「マーカー未検出時はmarkerRoot
以下をまるごと非表示にする」なども簡単に制御できます。
-
まとめ
markerRoot
(THREE.Group
)=マーカーの座標系(原点)ArMarkerControls
→ markerRoot.matrix を更新- markerRoot の子に追加したモデル群が「マーカーにくっついて動く」
という役割分担にすることで、シーン/カメラ本体には手を入れずに、マーカーに連動したコンテンツだけをグルーピングして管理できるようになるわけです。
ほぼそのとおりです。
厳密には、markerRoot
自体が「マーカー検出結果を書き込まれるオブジェクト(=ArMarkerControls が行列を設定するターゲット)」で、その下に「実際に表示したいメッシュやモデル」をぶら下げるイメージです。
-
ArMarkerControls が起動時に登録された
markerRoot
の行列(markerRoot.matrix
)を、毎フレーム「検出されたマーカーの位置・向き」に書き換え - その結果として、
でぶら下がった
markerRoot.add( yourMesh ); // 例えばこんな感じで
yourMesh
は自動的に「マーカーに追従して動く」
──という順序になります。ですので「①マーカー検出結果をどうオブジェクトに反映させるか」は markerRoot
自体の役割、「②表示するオブジェクト」はその子要素、という扱いで正しい理解です。
たとえば、以下のように具体的な数値を使って考えてみましょう。
例:マーカー検出結果から得られる markerRoot.matrix
AR.js 側でマーカーを検出すると、以下のようなワールド変換行列(4×4)が markerRoot.matrix
にセットされたとします。
R₁₁ = cos 45° ≈ 0.707 | 0 | R₁₃ = sin 45° ≈ 0.707 | Tₓ = 0.2 |
0 | 1 | 0 | Tᵧ = 0.1 |
R₃₁ = –sin 45° ≈ –0.707 | 0 | R₃₃ = cos 45° ≈ 0.707 | T_z = –1.3 |
0 | 0 | 0 | 1 |
- ここで 回転は Y 軸まわりに 45°(マーカー面がやや横を向いている)
- 並進(Tₓ, Tᵧ, T_z)はマーカー原点がワールド(カメラ座標系)上の (0.2, 0.1, –1.3) にある
markerRoot
の子オブジェクト(メッシュ)の配置例
1. 子メッシュをマーカー原点に置く
// ローカル座標で (0, 0.5, 0)=マーカーの真上 0.5m
cube.position.set(0, 0.5, 0);
markerRoot.add(cube);
ワールド座標 は
x = Tₓ + (0.707 * 0 + 0.707 * 0) = 0.2
y = Tᵧ + 1 * 0.5 = 0.1 + 0.5 = 0.6
z = T_z + (–0.707 * 0 + 0.707 * 0) = –1.3
→ (0.2, 0.6, –1.3) にキューブが置かれる
2. 子メッシュをマーカー原点の右側に 1mずらす
// ローカル座標で (1, 0, 0)=マーカー右側 1m
cube2.position.set(1, 0, 0);
markerRoot.add(cube2);
ワールド座標 は
x = Tₓ + (0.707 * 1 + 0.707 * 0) = 0.2 + 0.707 = 0.907
y = Tᵧ + 1 * 0 = 0.1
z = T_z + (–0.707 * 1 + 0.707 * 0) = –1.3 – 0.707 = –2.007
→ (0.907, 0.1, –2.007) にキューブ2が置かれる
なぜ Group()
が必要か、振り返り
-
マーカーの回転・平行移動(上の R, T)をまず
markerRoot.matrix
に一括で設定 -
その下にぶら下がっているすべての 子オブジェクト(
cube
,cube2
…) が- ローカル座標で指定した (x, y, z) を
- 上記行列でワールド座標に変換され
自動的に「マーカーに対する相対位置」を保ったまま動きます。
具体的な流れ
- AR.js →
markerRoot.matrix =
上の回転+並進行列 - Three.js →
markerRoot.updateMatrixWorld()
により、- 各子オブジェクトのワールド行列が
markerRoot.matrix * 子オブジェクト.matrix
として計算
- 各子オブジェクトのワールド行列が
- レンダラー → それぞれのワールド行列から画面に投影
――というわけで、「① マーカー検出結果(R,T)を書き込むのが markerRoot
」「② その下に置いたメッシュが具体的に描画される」 という役割分担だ、という実感が掴めると思います。