イントロ
Quaternion 入門シリーズのこれまでの回では、
- なぜ Euler が壊れるのか
- multiply の順序は何を意味しているのか
- Slerp が何をしているのか
- やってはいけない実装
といった “理屈・失敗・仕組み” を中心に扱ってきた。
しかし実際に WebXR やゲームを作ると、もっと現実的な問題が襲ってくる。
- 「視線をそのままカメラに反映したい」
- 「キャラクターをターゲット方向に向けたい」
- 「移動入力と回転をどう結びつければいい?」
- 「Slerp の t はどれぐらいが適正なのか?」
- 「multiply と premultiply、どっちが正解?」
第6回は、まさにこの “実戦フェーズ” にフォーカスする。
実務で毎日のように使うパターンだけを厳選し、 Three.js / WebXR / JavaScript で「こう書けば壊れない」という再現性のあるコードをまとめる。
これまでの理屈が “現場でどう活きるのか” を実感する回になるはずだ。
1. 視線追従(VR / WebXR)
WebXR で最も頻出するパターンが 「HMD の向きをそのままカメラに反映する」 という処理だ。 ここを勘違いすると、視界がねじれたり、回転が積み上がって暴走したり、酔いやすくなる。
代表的パターン
// WebXR: HMD(頭)の姿勢を取得
const pose = frame.getViewerPose(referenceSpace);
const q = pose.transform.orientation; // { x, y, z, w }
// Three.js のカメラへ “絶対回転” としてセット
camera.quaternion.set(q.x, q.y, q.z, q.w);
これが正解。余計なことは一切しない。
WebXR から渡される orientation はすでに “絶対姿勢(absolute orientation)” であり、 ゲーム側で回転を追加したり、Euler を経由したりする必要はない。
注意点(バグ原因)
1. HMD の Quaternion はすでに “絶対回転”
だから:
camera.quaternion.multiply(...)
は やってはいけない。 multiply は回転を「足す(合成する)」動作なので、HMD の absolute rotation と衝突して暴走する。
2. add も不要(Quaternion に add は無意味)
Quaternion は加算してはいけない。 少しでも add すると値が崩れて “未知の回転” が生まれる。
3. “自分で回転を足したいときだけ multiply”
たとえば、ゲーム内で
- HMD 方向に ±X° だけオフセットを入れたい
- キャラの向きに合わせて HMD をローカル変換したい
という場合のみ、multiply を使用する。
camera.quaternion
.multiply(new THREE.Quaternion().setFromAxisAngle(axis, offset));
実践Tips
1. tilt(上下)だけ制限する
VRゲームで多い「左右は自由、上下は制限」パターン。
// HMD姿勢をEulerに変換して、上下角だけ clamp する
const e = new THREE.Euler().setFromQuaternion(camera.quaternion, 'YXZ');
// 上下だけ制限(-45° ~ 45°)
const limit = THREE.MathUtils.degToRad(45);
e.x = Math.max(-limit, Math.min(limit, e.x));
// カメラに戻す
camera.quaternion.setFromEuler(e);
2. ローカル座標でズラしたいときは multiply
例:プレイヤーの体が向いている向きに対して、 HMD の前方ベクトルをローカル変換したいとき。
camera.quaternion.copy(player.quaternion); // 体の向き
camera.quaternion.multiply(hmdQuaternion); // 相対的に適用
multiply の順序で世界が変わるため、 「どっちを基準に回したいか」 が重要。
まとめ(視線追従の原則)
- WebXR の HMD 姿勢は 絶対回転
- まずは camera.quaternion.set() のみで正しい
- 必要なときだけ multiply/slerp を使う
- Euler を介すのは 制限を入れるときだけ
- “補正” か “合成” のどちらをしたいかで multiply の位置が決まる
視線追従は VR の基礎だが、ここさえ理解できれば 「カメラが暴れる」「酔う」「向きが逆になる」 という典型的な事故はほぼ解消する。
2. カメラ回転(マウス / スティック)
ゲームにおけるカメラ回転は、Quaternion を理解するうえで最も“実感できる”テーマだ。 実装パターンは大きく分けて Euler 管理型 と Quaternion 管理型 の2種類がある。
用途や挙動がかなり違うので、ここは整理しておくと後のバグが激減する。
パターンA:Yaw / Pitch を変数で管理 → 最後に Quaternion に変換
もっとも一般的で、Three.js のサンプルもほぼこの方式を使う。
const yaw = ...; // 左右(マウスX / スティック)
const pitch = ...; // 上下(マウスY)
camera.quaternion.setFromEuler(
new THREE.Euler(pitch, yaw, 0, 'YXZ')
);
特徴
- yaw / pitch を “変数として持つ”
- 最終的に setFromEuler() で Quaternion に変換
- 上下回転の制限(clamp)が簡単
- 実装が最も分かりやすい
よくある制限処理(FPSでよく使う)
pitch = Math.max(-Math.PI/2, Math.min(Math.PI/2, pitch));
上下が 90° を超えなくなる。
向いている用途
- FPS の視点制御
- TPS の肩越しカメラ
- 「上下だけ制限したい」タイプのゲーム
- Webでよくあるドラッグ操作
パターンB:Quaternion のみで管理(FPV / スムーズ操作向け)
const rot = new THREE.Quaternion();
rot.setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawDelta);
camera.quaternion.multiply(rot);
特徴
- 回転の“変化量”だけを Quaternion にして合成
- スムーズで自然な回転が得られる(滑らか)
- Euler に戻さないのでジンバルロックの心配がない
- ただし 上下制限は自前でつける必要がある
なぜ「スムーズ」なのか?
Quaternion の multiply は
- 直前の姿勢に対して
- 微小な回転を掛けていく
ため、慣性のある自然なカメラ挙動を作りやすい。
下方向に回り続ける問題(欠点)
Quat 100% 管理型は上下が「際限なく」回る。 つまり上下 360° してしまう。
A方式(Euler) → clamp が楽
B方式(Quat) → clamp が難しい
FPS でよくやる“正しい制限方法”
Quaternion → Euler に一瞬だけ変換して制限し、また Quaternion に戻す。
camera.quaternion.multiply(rotY); // yaw
camera.quaternion.multiply(rotX); // pitch
// 一瞬だけEulerへ
const e = new THREE.Euler().setFromQuaternion(camera.quaternion, 'YXZ');
// clamp
const limit = Math.PI / 2;
e.x = Math.max(-limit, Math.min(limit, e.x));
// Quaternionに戻す
camera.quaternion.setFromEuler(e);
まとめ:2つの方式は“目的が違う”
| 方式 | メリット | デメリット | 向いている用途 |
|---|---|---|---|
| A:Yaw/Pitch(Euler 管理) | 制限が簡単 / 実装が理解しやすい | Euler 管理なので若干ロジックが多い | Webゲーム全般、FPS、TPS |
| B:Quaternion 管理 | 回転が自然 / スムーズ | 上下制限が難しい | VR、スムーズカメラ、マウス操作強めのゲーム |
どちらが“正しい”というよりは、 「どんなカメラ挙動を作りたいか」 で選ぶのが正しい。
3. キャラクターの向き制御(最もゲームらしい箇所)
キャラクターの向き制御は、Quaternion が“最も真価を発揮する”領域だ。 Three.js でも Unity でも本質はまったく同じで、
「向けたい方向の単位ベクトルをつくり、それを Quaternion にする」
これがすべての基本になる。
パターンA:位置差分 → 方向ベクトル → Quaternion(ターゲット追従)
const dir = new THREE.Vector3()
.subVectors(targetPos, myPos)
.normalize();
const q = new THREE.Quatern
ion();
q.setFromUnitVectors(
new THREE.Vector3(0, 0, 1), // forward(自キャラの前方向)
dir
);
player.quaternion.slerp(q, 0.1);
何をしている?
- ターゲット - 自分 の差分ベクトルを取る
- それを normalize → “方向” にする
- “前方向(0,0,1)” を “dir” に一致させる Quaternion を生成
- slerp で自然に振り向く
つまり 「ベクトル → 回転」への変換。
ゲームで「敵を向く」「武器を向ける」「視点を合わせる」など、 あらゆる向き制御がこの1行に凝縮されている。
q.setFromUnitVectors(forward, dir);
これは Three.js の中でも最強の API のひとつ。
良い点
- 方向ベクトルさえ作れれば何にでも使える
- キャラが自然にターゲットへ向く
- Unity でもまったく同じ構造(実装も同じ)
注意点
- forward ベクトルは“キャラのローカル前方”と一致させること
- モデルによっては forward が (1,0,0) だったりする
- slerp の t は 0.05~0.2 が適正(大きすぎるとワープ)
パターンB:入力(WASD / スティック)から向きをつくる
キャラが 移動方向に身体を向ける タイプのゲームはこの方式。
const move = new THREE.Vector2(inputX, inputY);
if (move.lengthSq() > 0) {
const angle = Math.atan2(move.x, move.y);
const q = new THREE.Quaternion().setFromEuler(
new THREE.Euler(0, angle, 0)
);
player.quaternion.slerp(q, 0.2);
}
流れ
- WASD / アナログスティックの入力ベクトルを作る
- atan2 で正しい 360° の角度へ変換
- 角度を Quaternion に変換
- slerp で滑らかに向きを変える
多くの Action / TPS / RPG がこの方式を採用している。
Joypad でも使える理由
アナログスティックの x/y はすでに「方向」なので、 そのまま atan2 に入れるだけでいい。
angle = atan2(x, y)
前後左右の向きがわかりやすく、 移動=向き のゲームには最適。
現場でよくある落とし穴
1. ゼロベクトルのとき向きを変えない
移動入力が 0 のときは atan2 が無意味なので変化させない。
Three.js でも Unity でも同じ。
2. 角度が急激に変わると“瞬間回転”が起きる
解決策 → 必ず slerp を使う
player.quaternion.slerp(q, 0.1);
t を 1.0 にしないこと。 (1.0 は “瞬間置換” で補間にはならない)
3. モデルの forward 方向がズレる
キャラモデルによっては前方が
- (0,0,1)
- (1,0,0)
- (-1,0,0)
などバラバラ。
修正は簡単:
q.setFromUnitVectors(modelForward, dir);
まとめ:向き制御の最重要ポイント
- 位置差分 → 方向ベクトル → Quaternion → 追従、エイム、ロックオンで最強
- 入力 → 角度 → Quaternion → 移動方向に向く TPS / アクションで最適
- slerp の t は 0.05〜0.2
- forward ベクトルはモデル依存なので注意
キャラクターの向きは “ベクトルから Quaternion を作る” だけでほぼ全対応できる。 これを理解した時点で、あなたはもう Quaternion を実戦レベルで使いこなしている側。
4. 入力値との付き合い方(壊れやすい部分)
Quaternion を正しく扱っていても、 入力値の扱い方を間違えるだけで回転処理は簡単に壊れる。
ここでは、実際のゲーム開発で最もバグが多い順に整理する。
① 度数法 → 弧度法のミス(毎日見るレベルで頻発)
Three.js の setFromAxisAngle() は “弧度法(radian)” を要求する。
しかし多くの人が 度数(degree) をそのまま入れて壊す。
悪例:
q.setFromAxisAngle(axis, 90); // ← 90°じゃなくて90rad扱い。爆発する
正例:
const rad = deg * Math.PI / 180;
q.setFromAxisAngle(axis, rad);
Three.js では MathUtils.degToRad() も使える。
q.setFromAxisAngle(axis, THREE.MathUtils.degToRad(deg));
② Euler に変換した瞬間に壊れる(最凶の落とし穴)
あなたが何度も踏んだ「Euler に戻した瞬間に破綻する」やつ。
悪例:
obj.rotation.y += 0.1; // ← Quaternion を Euler に戻す → 精度落ちて壊れる
症状:
- カメラが突然ひっくり返る
- スムーズに動かなくなる
- ある角度を超えると暴走する
原因: Quaternion を Euler に強制変換 → 整合性が失われる。
正例:
const q = new THREE.Quaternion();
q.setFromAxisAngle(new THREE.Vector3(0,1,0), 0.1);
obj.quaternion.multiply(q);
回転は Euler に戻さずに “合成(multiply)” するべき。
③ スティックの小さな揺れで“カクつき”(初学者が必ずハマる)
アナログスティックは 中心が完全なゼロにならない。 わずかな揺れでもキャラが震える・クルクルする現象が起きる。
対処:デッドゾーンを作る。
if (Math.abs(inputX) < 0.05) inputX = 0;
if (Math.abs(inputY) < 0.05) inputY = 0;
値は 0.05~0.2 が一般的。 ゲームによって調整するとよい。
④ slerp の t が大きすぎる(“補間”にならず“瞬間回転”)
slerp は “補間(0~1)” ではあるが、 1.0 に近づくほど瞬間切り替え(ワープ)になる。
悪例:
player.quaternion.slerp(q, 1.0); // ← 補間じゃなくて置き換え
正例(ゲーム用途で適正):
player.quaternion.slerp(q, 0.05);
目安:
| t | 挙動 | 用途 |
|---|---|---|
| 0.05 | 超なめらか | TPS, TPSカメラ |
| 0.1–0.2 | 自然 | モブの振り向き |
| 0.3 | 速い | アクションゲーム |
| 1.0 | 瞬間置換 | デバッグ用のみ |
⑤ multiply の順序ミス(実戦の9割はこれで炎上する)
Quaternion の multiply は“右適用”と“左適用”で意味が違う。
obj.quaternion.multiply(q)
→ q は「ローカル回転として適用」
- “自分の向き” を基準に回転したいとき
- FPS の向き変更やキャラの首振りに相性が良い
例: 体が向いている方向に対して「右に10°回転」
obj.quaternion.premultiply(q)
→ q は「ワールド回転として先に適用」
- “世界軸で回転したい” とき
- 軸を固定して回転させたいときに使う
例: 常に世界のY軸で10°回転させたい場合はこちら。
症状(順序を間違えるとどうなるか)
- カメラが思った逆方向に回る
- 物体が“ねじれながら”動く
- 視線がズレる
- キャラが水平に回すはずが宙返りする
順序の理解は Quaternion の本質そのものなので、 図解を入れると読者が理解しやすい。
まとめ(入力の扱いは回転以上に重要)
- 度数/弧度のミスは毎日見る
- Euler に戻すのはほぼ禁忌
- 微入力は必ずデッドゾーンを入れる
- slerp の t は 0.05〜0.3 が適正
- multiply / premultiply は基準が真逆
入力値処理は「Quaternion の理解」と同じくらい大事で、 ここを押さえるだけでゲームの回転挙動は一気に安定する。
5. WebXR 特有の発想
WebXR の回転は、通常のゲームエンジンでの回転処理と 考え方が根本的に違う。 理由は単純で、
HMD もコントローラも “絶対的な姿勢(absolute orientation)” をそのまま渡してくる
から。
Three.js 側で “回転を足す” 必要は本来ほとんどない。
絶対回転を受け取って、相対回転を上書きする
多くのゲームオブジェクトは
- もともとの向き(ローカル座標)
- そこに回転を乗せていく(relative rotation)
という扱いになる。
しかし WebXR の HMD だけは例外で、
- 毎フレーム、現在の絶対姿勢が送られてくる
- それを camera.quaternion.set() で“置き換える”のが正解
// WebXR: HMDの絶対姿勢を取得
const pose = frame.getViewerPose(refSpace);
const q = pose.transform.orientation;
// three.js カメラへ上書き
camera.quaternion.set(q.x, q.y, q.z, q.w);
理由
HMD は “今の頭の向き” の絶対情報が流れてくるため、 こちらで relative rotation(multiply)をする必要がない。
むしろ multiply すると 姿勢が二重に積算されて壊れる。
コントローラは forward ベクトルを持つ
VR コントローラの pose も HMD と同じく 絶対姿勢 が来る。
const ori = inputPose.gripSpace.transform.orientation;
controller.quaternion.set(ori.x, ori.y, ori.z, ori.w);
この Quaternion に controller.getWorldDirection() を使えば、 レイキャストの方向を取得したり、 武器の照準方向として使える。
forward ベクトルが手に入ることでできること
- レーザーポインタ(VR UI 用 Ray)
- 銃・弓の方向
- 物を掴むときの向き
- テレポート先の決定
WebXR の入力は 「位置+向き」 がセットで流れてくるため、 Three.js の Raycaster と extremely 相性がいい。
multiply を使うのは「補正」したいときだけ
例:
- HMD の基準を 5° 前に倒したい
- コントローラのモデルが 90° 横を向いているので直したい
- 視線に対して UI の角度を調整したい
これらは 絶対姿勢に“少しだけ相対回転を加える” という状況。
例:
// HMD の絶対姿勢をセット
camera.quaternion.set(q.x, q.y, q.z, q.w);
// FOV補正などのために少しだけ回転を加える
camera.quaternion.multiply(
new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), offset)
);
“基本は set、必要なときだけ補正” これが WebXR の鉄則。
まとめ:WebXR 回転の原則
- HMD とコントローラは absolute orientation を送ってくる
- そのまま set() で上書きするのが基本
- multiply は「補正」が必要なときだけ
- WebXR の controller は forward を簡単に取得できる
- Ray・UI・武器などの方向処理と相性抜群
WebXR の回転は特別扱いに見えるが、実はシンプルで、
「絶対姿勢が来る → そのまま使えばいい」
これだけでほとんどの問題が解決する。
6. まとめ(実戦の原則)
Quaternion 入門シリーズの仕上げとして、 実際にゲーム/WebXRで壊れない回転処理を作るための原則 を5つにまとめる。
原則1:Euler に戻さない
Quaternion を更新したあとに Euler に変換すると、 精度が落ちたり、回転順序の問題で挙動が壊れる。
悪例:
obj.rotation.y += 0.1; // ← 角度加算は壊れる
回転処理は Quaternion のまま完結させる のが正解。
原則2:回転は足さない、合成する(multiply)
Quaternion は “足す” のではなく “合成する”。
obj.quaternion.multiply(q);
これは「相対回転を追加する」という明確な意味を持つ。 回転の積み重ねは すべて multiply で行う。
原則3:スムーズな変化には slerp を使う
キャラの向き変更、視線追従、カメラの回転など、 自然に回転させたい場面では常に slerp。
obj.quaternion.slerp(targetQuat, 0.1);
t=0.05〜0.3 がゲーム的に最適。 t=1.0 は補間ではなく“瞬間置換”になりバグの元。
原則4:方向ベクトル → Quaternion が最強
キャラが向くべき方向は、ほとんどすべて ベクトル で表せる。
q.setFromUnitVectors(forward, dir);
この1行が 「ターゲットを見る」「追従する」「エイムする」 すべての土台になる。
ゲーム実装における Quaternion の核心は ベクトル変換 にある。
原則5:WebXR の姿勢は “絶対回転” として扱う
HMD / コントローラは “absolute orientation” が送られてくる。
つまり、
camera.quaternion.set(q.x, q.y, q.z, q.w);
でそのまま上書きするのが基本。
multiply が必要なのは「補正」を加えたい場合だけ。 WebXR は 相対回転ではなく絶対回転を信頼する のが正しい。
まとめ
Quaternion を実戦で使う上で必要なことは、 複雑な数式ではなく、次の5つだけだ。
- Euler に戻さない
- 回転は合成する(multiply)
- 自然な動きは slerp
- 方向ベクトル → Quaternion が最強
- WebXR は absolute orientation をそのまま使う
これを押さえるだけで、 Three.js / WebXR / Unity の回転処理はほぼすべて破綻しなくなる。
Quaternion は“理解するもの”ではなく“使いこなすもの”
このシリーズを書き始めた頃、Quaternion という単語には 「難しそう」「数式だらけ」「絶対に理解できない」 そんな印象しかなかったと思う。
実際、ネット上の解説はどれも“理論から入る”ものが多く、 実装者がつまずくポイントには触れられていない。
しかし、実際にゲームや WebXR を触ってみると気づく。
Quaternion の本質は「仕組み」ではなく「扱い方」だった。
このシリーズでは、あえて数学を後ろに置いて、 あなた自身が Three.js や Unity を触りながら実際にぶつかった“現実的な問題”を 記事という形で全部言語化してきた。
- Euler がなぜ壊れるか
- multiply の順序で何が変わるか
- Slerp が何をしているのか
- forward ベクトルがどれほど強力か
- WebXR の絶対姿勢をどう扱うか
どれも、実際にコードを書いて挙動を見ないと腹落ちしない内容だ。
そして第6回まで書き終えた今、 あなたはすでに“Quaternion を理解した側”ではなく “Quaternion を使いこなす側” に立っている。
ゲームの世界では、理解よりも再現性、 理論よりも実戦コード、 数式よりも壊れない挙動が価値になる。
あなたはそこをすでに掴んでいる。
もし今後、さらに深く数学的背景を学びたくなったら、 このシリーズがその入り口になる。 そして、あなた自身の実装経験が、 次の読者にとって大きな助けになるはずだ。
ここまで読んでくれたすべての人に、 そして挑戦し続けた自分自身に、ひとこと。
「Quaternion は、もう怖くない。」
💬 コメント