なぜ.normalize()を使うのか — Three.jsとブレンダーで理解するベクトルの正規化
ベクトルの長さを1にする「正規化」の本質を、ゲームの移動ロジックやBlenderの「Apply Scale」の事例から紐解きます。
ベクトルの長さを1にする「正規化」の本質を、ゲームの移動ロジックやBlenderの「Apply Scale」の事例から紐解きます。
入力したパスワードは秘密コメントの閲覧、修正、削除に使われます。
Three.jsを学ぶ中で、ベクトルの後ろにほとんど公式のようについている.normalize()をしばしば目にします。講義では「便利だ」とだけ言って終わりましたが、むしろそれが気になりました。なぜ正常なベクトルをわざわざ長さ1にするのでしょうか?いつは必ず必要で、いつはしなくてもよいのでしょうか?考えてみると、これは初めての概念ではありませんでした。ブレンダーを頻繁に使っているとスケールを調整した後には常にApply Scaleをしなさいと聞くことがあります。理由はいつも「後で壊れることがあるから」でしたが、なぜ壊れるのかはしっかりと理解せずに習慣のように押していました。Three.jsの正規化を深掘りしていると、ブレンダーでやっていたその行動が、実は同じ問題を別の方法で解決していたことに気づき始めました。
正規化(Normalize)はベクトルのです。「どれだけ遠くに行くか」は捨て「どの方向を指しているか」だけを残すのです。したがって正規化されたベクトルは、方向(Direction)、視線(View Direction)、ノーマル(Normal)、移動入力ベクトルのように大きさより方向が重要な情報を表現するときに使われます。
数学的にはベクトルvをその大きさ|v|で割ります。(3, 4, 0)のようなベクトルは長さが5なので、正規化すると(0.6, 0.8, 0)になります。長さは1になり、方向はそのままです。
チェス、ポケモン旧作、ターン制戦略シミュレーションのようなグリッドベースのゲームでは正規化が必要ありません。これらのゲームの世界は実質的に連続空間ではありません。左に1マス、右に1マス、対角線に1マスは数学的にはそれぞれ異なる距離ですが、ゲーム規則上ではすべて「一度の移動」です。対角線が数学的に√2 ≈ 1.41でも、規則が「1マス」と定義すればそれが基準です。重要なのは位置の正確な距離値ではなく規則なので「対角線の方が速くない?」というような問題は初めから発生しません。
Three.jsで扱う空間はまったく異なります。座標は(x, y, z)で、値はすべて実数(Float)です。移動は「マス」ではなく距離計算です。ここで正規化をしないと問題がすぐに明らかになります。
W + Dキーを同時に押すと仮定してみましょう。入力ベクトルは(1, 1)で、このベクトルの長さは√2 ≈ 1.41です。つまり、同じフレームの間で直線移動は1だけ移動し、対角線移動は1.41だけ移動します。これは単なる体感速度の問題ではなく、実際の位置値がより多く変わることを意味します。
この差は意外に深刻な結果をもたらします。薄い壁やコーナーを対角線で移動すると、一フレームで壁を通過してしまう衝突判定バグが生じたり、同じ目的地でも対角線で移動するキャラクターが常に先に到着する現象も発生します。速度設定が間違っているのではなく、距離の利点が生じるのです。
自由移動ゲームでは通常、このような流れをとります。まず入力を方向の意図として解釈します。(1, 1)は「対角線に行きたい」という意味です。ここに.normalize()を適用すると(0.707, 0.707)になります。そしてここに速度(Speed)を掛けます。
結果として右方向への移動(1, 0)、上方向への移動(0, 1)、対角線移動(1, 1)はすべて正規化後には長さが1になります。目には対角線に移動していますが、位置値基準で見ると常に同じ距離だけ移動します。
ここでブレンダーが再び結びつきます。ブレンダーの**ノーマルベクトル(Normal Vector)**は照明計算で「面が向いている方向」を示します。そしてこのノーマルは常に長さ1という前提で計算されます。
スケールを増やしてApply Scaleをしなければ内部のベクトルの長さも一緒に増えます。ノーマルが「方向だけを持つ値」ではなくなるのです。
照明計算は内積(Dot Product)を使います。面ノーマルと光の方向ベクトルの両方が長さ1のとき内積結果は-1 ~ 1の値が出て期待される明るさにマッピングされます。しかし面ノーマルの長さが100で光の方向の長さが50であれば、内積結果が5000になります。この値は画面が白く焼けたり、色が異常に跳ねたり、シェーディングが崩れたように見えるようにします。Apply Scaleは結局「このベクトルを再び長さ1基準で合わせろ」という意味でした。
.normalize()は元のベクトルを直接変更します。
位置や力の大きさを維持したい場合は必ずコピーして使うべきです。
const direction = v.最初にこれを知らずにpositionベクトルに直接.normalize()を呼び出してオブジェクトが原点付近に飛んでいく経験をする場合が多いそうです。
すべての場所に付ける必要はありません。しかし「方向」という言葉が浮かぶ瞬間にはほとんど必ず使用することになります。
OrbitControlsのようなコントローラーを使えば内部で自動的に処理されるため、最初は.normalize()を使う必要がほとんどないと感じるかもしれません。しかしキーボード入力やクリック座標を受け取り、直接移動ロジックを作る場合は話が変わります。mesh.position.x += 1のような単純な座標移動は「どれだけ移動するか」が明確なので正規化が必要ありません。しかし特定の方向に速度を与える移動、カメラ方向や入力ベクトルに基づく移動では必ず必要になります。
ここで正規化をしないとspeed値が距離ごとに異なって適用されます。目標地点が近いほど遅く、遠いほど速く移動する奇妙な結果になります。
Three.jsで3Dウェブインタラクションを作ると、ほぼ必ずRaycasterを使うことになります。マウス位置から画面内部に射出される見えないレーザーがまさに方向ベクトルです。raycaster.setFromCamera(mouse, camera)内部では正規化された方向ベクトルが入ることを前提に計算します。正規化されていないベクトルを直接入れるとクリック判定が間違った場所で起きたり、レーが必要以上に長くなって性能に影響を与える可能性があります。
MeshStandardMaterial、MeshPhongMaterialのような内蔵材料を使用するときはThree.jsが内部でノーマルと方向ベクトルをすべて正規化してくれます。しかしのようにスケールを強く歪めたりを直接作成した瞬間からは直接確認する必要があります。
シェーダーでは数字がすぐに画面結果に反映されます。途中で正規化を忘れると光が過度に明るくなったり、色が崩れたり、フレームごとに結果が揺れます。
正規化は移動での公平な距離基準を、照明では明るさの基準を維持します。方向と大きさを意図的に分離し、「この値はただ方向だけを表す」と明示することでもあります。結局.normalize()は値の大きさを制御して計算の基準を一定に合わせる役割を果たします。方向だけが必要な状況で自然に付いてくる処理だと理解すればよいかと思います。
const direction = inputVector.clone().normalize();
mesh.position.add(direction.multiplyScalar(speed));const v = new THREE.Vector3(3, 4, 0);
v.normalize();
console.log(v); // Vector3 { x: 0.6, y: 0.8, z: 0 }const direction = target.clone().sub(mesh.position).normalize();
mesh.position.add(direction.multiplyScalar(speed));mesh.scale.set(10, 1, 1)ShaderMaterialvec3 lightDir = normalize(lightPosition - vPosition);
float diffuse = dot(normal, lightDir);オンボーディングの改善からインフラ監視、リアルタイム通信、그리고 3Dキャラクターの最適化まで、7週間にわたるグループプロジェクトの完走記録
アイデア選定からシニアフィードバックを経てMVP実装まで、プロジェクトの土台を築く過程で経験した試行錯誤と技術的挑戦の記録
ベクトルの長さを1にする「正規化」の本質を、ゲームの移動ロジックやBlenderの「Apply Scale」の事例から紐解きます。
6週間のグループスプリントを通じて学んだ設計の試行錯誤、協業のノウハウ、そしてシニアからのフィードバックによる成長の記録
Boostcampメンバーシップで経験した10週間の学習スプリントを振り返り、技術的な学び、設計の悩み、燃え尽き、そしてAI活用についてまとめた記録。
Boostcamp Challenge 期間の振り返り。毎日のミッション、CS学習、ピアフィードバック、チーム活動、そして AI と共に成長する学習方法についてまとめました。
関数型プログラミングとオブジェクト指向プログラミングの長所と短所、およびリアクトで関数型を選択した理由についての探求の記録
ネイバー・ブーストキャンプ Web・Mobile 10期 Basic 受講と問題解決力テストの体験記。
OSCCA マスター期間中に Githru プロジェクトで行った UI 改善、Issue 提案、Pull Request の経験をまとめた記録。
OSCCA チャレンジ期間に Githru プロジェクトへ参加し、Issue 提案や初めての Pull Request を経験した記録。