Three.js備忘録(3)

HP制作・コーディングなどのお手伝いをいたします

目次
  1. ピッキング
  2. カメラの向きと平行の面に描画する
  3. BufferGeometry
  4. ジオメトリのマージ

ピッキング

ピッキングとはユーザーがどのオブジェクトをクリックしたか、またはタッチしたかを把握するプロセスの事です
最も一般的なピッキングは「レイキャスティング」でマウスカーソルの位置(2D)からシーンの錐台を通して光線を出してそれと交差した3Dオブジェクトを検出する方法です
*透明な穴には対応できない

THREE.Raycasterは3Dシーン内で光線を出してオブジェクトとの衝突を検出するためのツールです

マウスの位置は「-1.0〜1.0」の割合で管理するための正規化して管理する

const mousePositions = new THREE.Vector2();

window.addEventListener('mousemove', function(e) {
    mousePosition.x = (e.clientX / window.innerWidth) * 2 - 1;
    mousePosition.y = - (e.clientY / window.innerHeight) * 2 + 1;
});

正規化したマウスの位置を使ってレイキャストを更新
rayCaster.setFromCameraでカメラから画面上の特定の点に向かう光線を作成

const rayCaster = new THREE.Raycaster();

//カメラから画面上の特定の点に向かう光線を作成
rayCaster.setFromCamera(mousePosition, camera);
//光線とぶつかったオブジェクトの配列
const intersects = rayCaster.intersectObjects(scene.children);
if(intersects.length > 0){
    //処理
}
  • IntersectObjectsIntersectObjectがある
  • IntersectObjectsは光線との交差をチェックするオブジェクトの配列を渡す必要がある
  • 再帰的にチェックする(すべての子孫要素までチェックする)場合は、第二引数をtrueにする
    デフォルトはfalse
  • 戻り値の配列には、交差したオブジェクトの距離、位置、法線などの情報が含まれる

マウスがのったオブジェクトだけ色がつく

メモ(余談):便利なユーティリティ関数

function rand(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return min + (max - min) * Math.random();
} 
function randomColor() {
  return `hsl(${rand(360) | 0}, ${rand(50, 100) | 0}%, 50%)`;
}

カメラの向きと平行の面に描画する

法線と点で平面を作成するには
THREE.PlanesetFromNormalAndCoplanarPointメソッドを使います
normal:法線を表す3次元ベクトル
point:原点を表す3次元ベクトル
例では原点を通りY軸に平行な平面を定義しPlaneオブジェクトにセットします

const normal = new THREE.Vector3(0, 1, 0);
const point = new THREE.Vector3(0, 0, 0);
const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(normal, point);

ちなみに法線とは3次元空間において、ある平面上の垂直な方向を表すベクトルのこと
*一般的には平面上で上向きを表すベクトルが法線となります

raycaster.setFromCameraでカメラからマウス座標に向かう光線を作成
raycaster.ray.intersectPlaneは平面とレイの交差点を計算、結果をTHREE.Vector3オブジェクトとして返します

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
const point = new THREE.Vector3();

function onMouseMove(event) {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function render() {
    raycaster.setFromCamera(mouse, camera);
    if (raycaster.ray.intersectPlane(plane, point)) {
        console.log(point);
    }
    renderer.render(scene, camera);
}
window.addEventListener('mousemove', onMouseMove, false);

キャンバスを透明にする
WebGLRenderer作成時に「alpha:true」を指定
*マテリアルもopacityを付けることができる

const renderer = new THREE.WebGLRenderer({
  canvas,
  alpha: true,
 // premultipliedAlpha: false,
});
//マテリアル
const material = new THREE.MeshPhongMaterial({
  color,
  opacity: 0.5,
});

クリックでカメラの向きと平行の面に球体のオブジェクトを描画します
*透明のキャンバスを上に重ねている

BufferGeometry

BufferGeometryはジオメトリの表現方法の一つで、通常のThree.jsのジオメトリと異なり、単一の配列バッファを使用してジオメトリ属性(BufferAttributeを保持します

BufferGeometryは大量のオブジェクトを描画する際に、パフォーマンスが向上します

ジオメトリ属性について
*ジオメトリ属性はGeometryオブジェクトのプロパティとして定義されておりBufferGeometryを使用することもできます

  • position:頂点の位置を表す3次元ベクトル
    これは必須の属性で頂点の位置情報を持つ必要があります
  • normal:頂点の法線を表す3次元ベクトル
    照明の影響を受けたり、法線マップを使用する場合に必要な属性です
    *ライトに反応するマテリアルを利用するの場合はnormalも必要
  • color:頂点の色を表す3次元ベクトル
    頂点の色情報を持つ必要がある場合に使用します
    *三角形の3つの頂点で異なるRGB色を持っている(多分^^;)
  • uv:頂点のテクスチャ座標を表す2次元ベクトル
    テクスチャマッピングを使用する場合に必要な属性です
  • index:頂点のインデックスを表す整数
    頂点のインデックス情報を持つ必要がある場合に使用します。

1、データ(配列を作る)
例:1つの立方体の頂点情報
備考:三角形メッシュを作成する場合は重なっている頂点情報は不要で、代わりにインデックス属性を指定します

// 頂点情報を格納する配列
const vertices = [
  // front
  { pos: [-1, -1,  1],norm: [ 0,  0,  1]},
  { pos: [ 1, -1,  1],norm: [ 0,  0,  1]},
  { pos: [-1,  1,  1],norm: [ 0,  0,  1]},
  { pos: [-1,  1,  1],norm: [ 0,  0,  1]},
  { pos: [ 1, -1,  1],norm: [ 0,  0,  1]},
  { pos: [ 1,  1,  1],norm: [ 0,  0,  1]},
  // right
  { pos: [ 1, -1,  1], norm: [ 1,  0,  0]},
  { pos: [ 1, -1, -1], norm: [ 1,  0,  0]},
  { pos: [ 1,  1,  1], norm: [ 1,  0,  0]}, 
  { pos: [ 1,  1,  1], norm: [ 1,  0,  0]},
  { pos: [ 1, -1, -1], norm: [ 1,  0,  0]},
  { pos: [ 1,  1, -1], norm: [ 1,  0,  0]},
  // back
  { pos: [ 1, -1, -1], norm: [ 0,  0, -1]},
  { pos: [-1, -1, -1], norm: [ 0,  0, -1]},
  { pos: [ 1,  1, -1], norm: [ 0,  0, -1]}, 
  { pos: [ 1,  1, -1], norm: [ 0,  0, -1]},
  { pos: [-1, -1, -1], norm: [ 0,  0, -1]},
  { pos: [-1,  1, -1], norm: [ 0,  0, -1]},
  // left
  { pos: [-1, -1, -1], norm: [-1,  0,  0]},
  { pos: [-1, -1,  1], norm: [-1,  0,  0]},
  { pos: [-1,  1, -1], norm: [-1,  0,  0]}, 
  { pos: [-1,  1, -1], norm: [-1,  0,  0]},
  { pos: [-1, -1,  1], norm: [-1,  0,  0]},
  { pos: [-1,  1,  1], norm: [-1,  0,  0]},
  // top
  { pos: [ 1,  1, -1], norm: [ 0,  1,  0]},
  { pos: [-1,  1, -1], norm: [ 0,  1,  0]},
  { pos: [ 1,  1,  1], norm: [ 0,  1,  0]}, 
  { pos: [ 1,  1,  1], norm: [ 0,  1,  0]},
  { pos: [-1,  1, -1], norm: [ 0,  1,  0]},
  { pos: [-1,  1,  1], norm: [ 0,  1,  0]},
  // bottom
  { pos: [ 1, -1,  1], norm: [ 0, -1,  0]},
  { pos: [-1, -1,  1], norm: [ 0, -1,  0]},
  { pos: [ 1, -1, -1], norm: [ 0, -1,  0]},
  { pos: [ 1, -1, -1], norm: [ 0, -1,  0]},
  { pos: [-1, -1,  1], norm: [ 0, -1,  0]},
  { pos: [-1, -1, -1], norm: [ 0, -1,  0]},
];

2、BufferGeometryオブジェクトのインスタンスを作成
3、ジオメトリ属性を指定する
setAttribute (名前, BufferAttribute)
*名前はthree.jsで決められている名前を指定する
*BufferAttributeに浮動小数点数や整数値などのデータを格納します
具体的にはFloat32ArrayUint16ArrayInt8ArrayなどのTypedArrayを使用してデータを格納
*BufferAttributeはネイティブ配列ではなくTypedArrayである必要がある

const geometry = new THREE.BufferGeometry();
//positions, normalsの2つの配列を並列の配列として扱う
const positions = [];
const normals = [];
for (const vertex of vertices) {
  positions.push(...vertex.pos);
  normals.push(...vertex.norm);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
  'position',
  new THREE.BufferAttribute(new Int8Array(positions), 3)
);
geometry.setAttribute(
  'normal',
  new THREE.BufferAttribute(new Int8Array(normals), 3)
);

4、MeshオブジェクトにBufferGeometryオブジェクトとMaterialオブジェクトを設定する。

//3000の立方体
for (let i = 0; i < 3000; i++) {
  const x = 200 * (Math.random() - 0.5);
  const y = 200 * (Math.random() - 0.5);
  const z = 200 * (Math.random() - 0.5);
  makeInstance(geometry, x, y, z)
}
//Meshオブジェクトを作成する関数
function makeInstance(geometry, x, y, z) {
  const material = new THREE.MeshLambertMaterial({ color: 0xff00ff });
  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);
  cube.position.set(x,y,z);
  return cube;
}

3000個の立方体をBufferGeometryで描画

BufferAttributeのcolorを追加して、マテリアルで頂点カラー(vertexColors)を設定


備考:THREE.Meshの代わりにTHREE.Points(3D空間内の点群を表現)やTHREE.Line(3D空間内の直線を表現)を使用して開始でき、THREE.Points クラスを使用する場合は位置属性だけが必要です
*ちなみにマテリアルはTHREE.PointsMaterialとTHREE.LineBasicMaterialという独自のマテリアルになる

ジオメトリのマージ

THREE.BufferGeometryUtils.mergeBufferGeometriesメソッドで複数のジオメトリを結合して単一のジオメトリにマージできます
単一のオブジェクトとしてレンダリングすることができ、パフォーマンスが向上します

このメソッドはThree.js本体のコードに含まれていません
examples/js/utilsフォルダ

three@../examples/js/utils/BufferGeometryUtils.js
const geometries = [];
for (let i = 0; i < 10; i++) {
 //BoxGeometryで立方体を作成
  const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
//BufferGeometryオブジェクトに変換
  const geometry = new THREE.BufferGeometry().fromGeometry(boxGeometry);
// 各BufferGeometryオブジェクトの位置を設定
  geometry.translate(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);
  geometries.push(geometry);
}
// 配列内のBufferGeometryオブジェクトを結合する
const mergedGeometry = THREE.BufferGeometryUtils.mergeBufferGeometries(geometries);

// 結合したGeometryオブジェクトを使用してMeshオブジェクトを作成し、シーンに追加する
const material = new THREE.MeshBasicMaterial({color: 0xff0000});
const mesh = new THREE.Mesh(mergedGeometry, material);
scene.add(mesh);

3000個の立方体を1つのジオメトリとして描画