0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Three.jsでマウスオーバーしたオブジェクトを光らせる

0
Posted at

3D シーンにインタラクティブな操作を加えるには Raycaster を使います。
マウスカーソルを当てたオブジェクトを検出し、色を変えたりアニメーションさせたりできます。ポートフォリオや製品デモサイトで特に活躍するテクニックです。


完成デモ

See the Pen Untitled by Masahiko Sakai (@Masahiko-Sakai-the-decoder) on CodePen.


コード

CSS

body {
  margin: 0;
  background: #111;
  cursor: pointer;
}

JavaScript

// --- 基本セットアップ ---
const scene    = new THREE.Scene();
const camera   = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

camera.position.z = 6;

// --- 照明 ---
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
dirLight.position.set(5, 5, 5);
scene.add(dirLight);

// --- 複数のオブジェクトを生成 ---
const objects = [];
const colors  = { normal: 0x4488ff, hover: 0xff4466 };

const positions = [
  [-3, 0, 0],
  [ 0, 0, 0],
  [ 3, 0, 0],
];

positions.forEach(([x, y, z]) => {
  const geo  = new THREE.IcosahedronGeometry(0.8, 0);
  const mat  = new THREE.MeshStandardMaterial({ color: colors.normal, roughness: 0.3 });
  const mesh = new THREE.Mesh(geo, mat);
  mesh.position.set(x, y, z);
  scene.add(mesh);
  objects.push(mesh);
});

// --- Raycaster とマウス座標 ---
const raycaster = new THREE.Raycaster();
const mouse     = new THREE.Vector2();

// マウス移動時に正規化座標を更新
window.addEventListener('mousemove', (e) => {
  // Three.js は -1〜+1 の正規化座標を使う
  mouse.x =  (e.clientX / innerWidth)  * 2 - 1;
  mouse.y = -(e.clientY / innerHeight) * 2 + 1;
});

// --- アニメーション ---
function animate() {
  requestAnimationFrame(animate);

  // Raycaster でマウスと交差するオブジェクトを判定
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(objects);

  // 全オブジェクトをいったん通常色に戻す
  objects.forEach((obj) => {
    obj.material.color.setHex(colors.normal);
    obj.scale.setScalar(1);
  });

  // 交差したオブジェクトだけ強調する
  if (intersects.length > 0) {
    const hit = intersects[0].object;
    hit.material.color.setHex(colors.hover);
    hit.scale.setScalar(1.15); // 少し大きくする
  }

  // オブジェクト全体をゆっくり自転させる
  objects.forEach((obj, i) => {
    obj.rotation.y += 0.005 + i * 0.002;
  });

  renderer.render(scene, camera);
}
animate();

コードのポイント解説

1. Raycaster の仕組み

Raycaster は名前の通り「光線(Ray)を飛ばして交差したオブジェクトを検出する」クラスです。

カメラ → マウス方向に光線を飛ばす → どのオブジェクトに当たったか判定

2D 画面上の座標から 3D 空間の方向に光線を飛ばすため、3D シーンへのクリックやホバー検出に使います。

const raycaster = new THREE.Raycaster();
const mouse     = new THREE.Vector2();

// 毎フレーム: マウス座標とカメラから光線を更新
raycaster.setFromCamera(mouse, camera);

// 光線と交差するオブジェクトの一覧を取得(距離の近い順)
const intersects = raycaster.intersectObjects(objects);

intersects は光線に当たったオブジェクトの配列です。距離の近い順に並んでいるため、intersects[0] が最前面のオブジェクトになります。


2. マウス座標の正規化

Three.js の Raycaster は -1〜+1 の正規化済み座標を要求します。ブラウザのピクセル座標をそのまま渡すことはできません。

window.addEventListener('mousemove', (e) => {
  mouse.x =  (e.clientX / innerWidth)  * 2 - 1; // 0〜1 → -1〜+1
  mouse.y = -(e.clientY / innerHeight) * 2 + 1; // 上下が逆なのでマイナスにする
});

画面の中心が (0, 0)、左端が (-1, 0)、右端が (1, 0)、上端が (0, 1)、下端が (0, -1) になります。Y 軸はブラウザ(上が 0)と Three.js(上が正)で向きが逆なため、マイナスにして反転させています。


3. ヒット判定と色の変更

// まず全オブジェクトを通常状態に戻す
objects.forEach((obj) => {
  obj.material.color.setHex(colors.normal);
  obj.scale.setScalar(1);
});

// 交差したものだけ強調
if (intersects.length > 0) {
  const hit = intersects[0].object; // 最前面のオブジェクト
  hit.material.color.setHex(colors.hover);
  hit.scale.setScalar(1.15);
}

毎フレーム全オブジェクトをリセットしてから交差したものだけ強調するパターンが安全です。前フレームの状態を持ち越さないため、マウスが離れた瞬間に自動的に元の色に戻ります。

material.color.setHex() は Three.js のカラーオブジェクトを直接変更するメソッドです。material.color = 0xff4466 のように直接代入するのではなく、setHex() を使います。


4. IcosahedronGeometry(正二十面体)

const geo = new THREE.IcosahedronGeometry(0.8, 0);

引数は (半径, 分割数) です。分割数 0 が元の正二十面体(20 面体)、数を増やすほど球に近づきます。

分割数 見た目
0 角張った多面体(ローポリ風)
1 少し丸みが出る
3 ほぼ球形

ローポリ風の見た目はポートフォリオサイトやゲームで人気のスタイルです。


5. 複数オブジェクトの管理

const objects = [];

positions.forEach(([x, y, z]) => {
  const mesh = new THREE.Mesh(geo, mat);
  mesh.position.set(x, y, z);
  scene.add(mesh);
  objects.push(mesh); // 配列に入れて一括管理
});

objects 配列にすべてのオブジェクトを入れておくと、raycaster.intersectObjects(objects) で一括判定でき、forEach でまとめてリセットできます。オブジェクトが増えてもコードを変える必要がありません。


カスタマイズしてみよう

クリックしたときにアクションを起こす

window.addEventListener('click', () => {
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(objects);
  if (intersects.length > 0) {
    const hit = intersects[0].object;
    // クリックしたオブジェクトをランダムな色に変える
    hit.material.color.setHex(Math.random() * 0xffffff);
  }
});

ホバーしたときに浮き上がらせる

if (intersects.length > 0) {
  const hit = intersects[0].object;
  hit.position.y = 0.3;
}

// リセット時に y も戻す
objects.forEach((obj) => {
  obj.material.color.setHex(colors.normal);
  obj.scale.setScalar(1);
  obj.position.y = 0;
});

グループ全体を判定する

const group = new THREE.Group();
group.add(mesh1);
group.add(mesh2);
scene.add(group);

// recursive: true で子オブジェクトまで判定する
raycaster.intersectObjects([group], true);

まとめ

Raycaster でのインタラクション実装は以下の流れです。

1. mousemove でマウス座標を -1〜+1 に正規化
2. raycaster.setFromCamera(mouse, camera) で光線を更新
3. raycaster.intersectObjects(objects) で交差判定
4. intersects[0].object が最前面のオブジェクト
5. 毎フレームリセット → 交差したものだけ強調

Raycaster はホバー・クリックだけでなく、ゲームの弾丸判定や AR での平面検出など幅広い用途に使われます。


この記事の内容をさらに深く学びたい方へ

インタラクションをさらに発展させると、ドラッグ操作・3D ラベル表示・キャラクター制御なども実現できます。体系的に学べる講座を公開しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?