threejs-core-raycaster について
threejs-core-raycaster は、Three.js 開発者向けのレイキャスティング解説スキルです。ピッキング API、交差結果、性能面の注意点を扱い、オブジェクト選択の実装を支援します。
主な機能
- 01Raycaster の構築と設定
- 02カメラ座標からレイを生成
- 03オブジェクトを検査して交差情報を取得
- 04レイヤーによる対象の絞り込み
- 05結果配列の再利用による割り当て削減
- 06InstancedMesh のピッキング
活用シーン
- 01マウスでの選択やホバーの実装
- 02シーン内の操作対象の絞り込み
- 03ヒットしたインスタンスと番号の特定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ impertio-studio/ three.js-claude-skill-package --skill threejs-core-raycasterインストール後、Claude Code で/threejs-core-raycasterを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/anti-patterns.mdexamples.mdmethods.md詳細
threejs-core-raycaster
クイックリファレンス
Raycaster コンストラクター
import * as THREE from 'three';
const raycaster = new THREE.Raycaster(
origin, // Vector3 — default: (0, 0, 0)
direction, // Vector3 — MUST be normalized, default: (0, 0, -1)
near, // number — minimum distance, default: 0
far // number — maximum distance, default: Infinity
);
プロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
ray | Ray | -- | 内部で使用する Ray オブジェクト(原点+方向) |
near | number | 0 | 最小交差距離 |
far | number | Infinity | 最大交差距離 |
camera | Camera | -- | Sprite オブジェクトに対するレイキャストに必要です |
layers | Layers | layer 0 | レイヤーマスク -- 一致するレイヤー上のオブジェクトのみをテストします |
params | object | 下記を参照 | 型ごとの交差判定しきい値 |
Params のデフォルト値
raycaster.params = {
Mesh: {},
Line: { threshold: 1 }, // world units distance for line hits
LOD: {},
Points: { threshold: 1 }, // world units distance for point hits
Sprite: {}
};
重要な警告
mousemove イベントのたびにスロットリングせずにレイキャストを実行してはなりません。複雑なシーンでは、これによりフレームレートが大幅に低下します。必ず requestAnimationFrame を使ってレンダリングループに合わせてスロットリングするか、チェック頻度を毎秒 30~60 回に制限してください。
ターゲットオブジェクトのフラットな配列がある場合、大規模なシーングラフに対して recursive: true を指定してはなりません。不要なツリー走査を避けるため、選択可能なオブジェクトのフラットな配列を必ず維持し、recursive: false を指定してください。
raycaster.set() を使う際、方向ベクトルの正規化を忘れてはなりません。正規化されていない方向を指定すると、すべての交差結果で距離の値が不正になります。
フレームごとに新しい結果配列を確保してはなりません。必ず optionalTarget パラメーターで配列を再利用し、処理後に array.length = 0 でクリアしてください。
操作可能なオブジェクトが一部だけの場合、シーン全体の scene.children に対してレイキャストを実行してはなりません。選択可能なオブジェクトの配列を別途必ず維持するか、レイヤーを使ってフィルタリングしてください。
Sprite オブジェクトに対してレイキャストする際、raycaster.camera を省略してはなりません。スプライトのスクリーン空間の境界を計算するには、レイキャスターにカメラの参照が必要です。
コアメソッド
set(origin, direction)
レイの原点と方向を手動で設定します。方向は必ず正規化してください。
const origin = new THREE.Vector3(0, 1, 0);
const direction = new THREE.Vector3(0, -1, 0); // already normalized
raycaster.set(origin, direction);
setFromCamera(coords, camera)
正規化デバイス座標(NDC)とカメラからレイを設定します。これは標準的なマウスピッキングの方法です。
coords:x と y が [-1, +1] の範囲にあるVector2camera:PerspectiveCameraはカメラからその点を通るレイを生成し、OrthographicCameraは平行なレイを生成します
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersectObject(object, recursive?, optionalTarget?)
単一のオブジェクト(および任意でその子孫)との交差をテストします。
recursive(デフォルト:true):trueの場合、すべての子孫をテストしますoptionalTarget:確保を避けるために再利用する配列- 戻り値:
Intersection[]。距離順(近い順)に必ずソートされます
intersectObjects(objects, recursive?, optionalTarget?)
オブジェクトの配列との交差をテストします。
recursive(デフォルト:true):trueの場合、各オブジェクトの子孫もテストしますoptionalTarget:確保を避けるために再利用する配列- 戻り値:
Intersection[]。距離順(近い順)に必ずソートされます
Intersection オブジェクトの形式
返される配列内の各交差は、次の構造になります。
interface Intersection {
distance: number; // distance from ray origin to hit point
point: Vector3; // hit point in world space
face: Face | null; // hit face ({a, b, c} vertex indices + normal)
faceIndex: number; // index of hit face in the geometry
object: Object3D; // the intersected object reference
uv?: Vector2; // UV coordinates at intersection point
uv1?: Vector2; // second UV set (if available)
normal?: Vector3; // interpolated surface normal at hit point
instanceId?: number; // instance index (only for InstancedMesh)
}
オブジェクトにヒットしなかった場合、配列は空です。intersects[0] にアクセスする前に、必ず intersects.length > 0 を確認してください。
NDC への変換(正規化デバイス座標)
setFromCamera には、DOM のマウス座標を NDC に変換する必要があります。NDC 空間の範囲は -1(左/下)から +1(右/上)です。
// For fullscreen canvas (canvas fills entire window)
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// For non-fullscreen canvas (canvas has offset within page)
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
キャンバスがビューポート全体を占めていない場合は、必ず getBoundingClientRect() を使用してください。一部の領域にしか表示されないキャンバスで window.innerWidth/Height を使用すると、NDC の値が不正になり、ピッキング位置がずれます。
InstancedMesh のピッキング
InstancedMesh に対するレイキャストでは、交差結果にヒットしたインスタンスを識別する instanceId が含まれます。
import * as THREE from 'three';
const intersects = raycaster.intersectObject(instancedMesh);
if (intersects.length > 0) {
const instanceId = intersects[0].instanceId;
// Retrieve the instance's transform matrix
const matrix = new THREE.Matrix4();
instancedMesh.getMatrixAt(instanceId, matrix);
// Example: change the color of the hit instance
const color = new THREE.Color();
instancedMesh.getColorAt(instanceId, color);
instancedMesh.setColorAt(instanceId, new THREE.Color(0xff0000));
instancedMesh.instanceColor.needsUpdate = true;
}
使用する前に、必ず instanceId !== undefined を確認してください。InstancedMesh 以外のオブジェクトにはこのプロパティがありません。
レイヤーによるフィルタリング
Raycaster は Layers システムに従います。レイヤーが raycaster.layers と重なるオブジェクトのみがテストされます。
// Assign objects to layers
selectableMesh.layers.set(1); // exclusively on layer 1
decorationMesh.layers.set(2); // exclusively on layer 2
// Configure raycaster to only test layer 1
raycaster.layers.set(1);
// Now intersectObjects skips all objects NOT on layer 1
// Enable multiple layers
raycaster.layers.enable(1);
raycaster.layers.enable(2);
// Now tests objects on layer 1 OR layer 2
// Reset to default (layer 0 only)
raycaster.layers.set(0);
レイヤーを使ってピッキンググループを作成します。インタラクティブなオブジェクトはレイヤー 1、インタラクティブではない装飾はレイヤー 2、ヘルパー/ギズモはレイヤー 3 に配置します。
パフォーマンスに関するガイドライン
-
mousemove によるレイキャストを間引く --
mousemoveイベントのたびにレイキャストを実行してはいけません。レンダーループ内で確認するフラグを使います。let needsRaycast = false; canvas.addEventListener('pointermove', (event) => { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; needsRaycast = true; }); function animate() { if (needsRaycast) { raycaster.setFromCamera(mouse, camera); // perform intersection tests needsRaycast = false; } renderer.render(scene, camera); requestAnimationFrame(animate); } -
レイヤーを使う -- インタラクティブなオブジェクトを特定のレイヤーに割り当て、それに応じて
raycaster.layersを設定し、インタラクティブではないジオメトリを完全にスキップします。 -
選択可能なオブジェクトのフラットな配列を維持する --
recursive: trueを指定してsceneに対してレイキャストするのではなく、別途selectableObjects[]配列を維持し、recursive: falseを指定します。 -
交差配列を再利用する -- ガベージコレクションを避けるため、
optionalTargetを渡します。const intersections = []; raycaster.intersectObjects(selectableObjects, false, intersections); // process results... intersections.length = 0; -
バウンディングスフィアによる事前チェック -- Three.js は三角形との交差判定の前に、バウンディングスフィアを自動的にテストします。
geometry.computeBoundingSphere()が呼び出されていることを確認します(初回のレンダリング時に自動で呼び出されますが、初回のレンダリング前にレイキャストする場合は手動での呼び出しが必要です)。 -
near/farを制限する -- 想定される交差距離がわかっている場合は、レイの範囲を狭めて遠くのオブジェクトを早い段階でスキップします。
参考リンク
- references/methods.md -- Raycaster API のシグネチャ一覧
- references/examples.md -- ピッキング、ホバー、InstancedMesh の動作するコード例
- references/anti-patterns.md -- レイキャスティングで避けるべきこと