🌎

【Three.js】入門!環境構築から、実際に3Dオブジェクトをレンダリングしてみる🌏

2023/08/13に公開

Three.jsとは

手軽にWebサイト上で3Dコンテンツの制作ができるJSライブラリです。
ライブラリなしで3D表現をしようとした場合、高度な技術が必要になりますがThree.jsライブラリを導入することで、手軽に3D表現ができるようになります🌏

インストール方法

色んな方法がありますが、今回はViteを使用してインストールします🚀

https://ja.vitejs.dev/guide/

yarn create vite プロジェクト名

フレームワークの使用や言語を聞かれるので回答。
私はVanilla、JavaScriptで作成しました。

cd プロジェクト名

npm i three

npm run devでローカルサーバーが立ち上がります。

main.jsを下記のように編集し、Three.jsのモジュールが正常に読み込まれていれば環境構築は終了です🛠

import * as THREE from "three";
console.log(THREE);

スクリーンショット 2023-06-27 10.42.50.png

3Dグラフィックス構築に必要な機能

シーン、カメラ、レンダラーなど、3Dグラフィックスを構築するためには様々な機能が必要です。

シーンの追加

シーン(Scene)は、3Dオブジェクトや光源などの要素を含む仮想的な空間を表します🖼️
カメラで撮影するためのステージのようなものです。

import * as THREE from "three";

// シーンの追加
const scene = new THREE.Scene();

カメラの追加

カメラ(Camera)は、シーンのどの部分を表示するかを定義します🎥
PerspectiveCamera()は4つの引数をとります。

new THREE.PerspectiveCamera(視野角, アスペクト比, 開始距離, 終了距離)

// カメラの追加
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);

レンダラーの追加

シーンを描画して実際の画面に表示するための機能を提供します📺

// レンダラーの追加
const renderer = new THREE.WebGLRenderer({
  alpha: true,
});
// サイズ調整
renderer.setSize(window.innerWidth, window.innerHeight);
// レンダリングしたいDOM要素と紐付け
document.body.appendChild(renderer.domElement);

ジオメトリの追加

ジオメトリ(Geometry)とは、Three.jsで3Dオブジェクトの形状を定義するためのクラスです。
Three.jsには、さまざまな形状を作成するための多くのジオメトリクラスがあります。
その一つであるSphereGeometry()は球体の形状を定義し、3次元空間に球を作成するための関数です🌏
Three.js SphereGeometry公式ドキュメントで7つのパラメーターとれることが確認できます。
下記コードは、radius、widthSegments、heightSegmentsを設定しています。

// ジオメトリ作成
const ballGeometry = new THREE.SphereGeometry(100, 64, 32);

マテリアルの追加

マテリアル(Material)とは、Three.jsの3Dオブジェクトが画面にレンダリングされるときの外観や質感を決定するものです。
こちらも色々なメソッドが用意されていますが、その一つであるMeshPhysicalMateria()は物理ベースのレンダリング(PBR)をサポートするマテリアルで、粗さや金属製など、現実の物質のような設定ができます。
パラメーターでは色や、mapというテクスチャの指定ができ、画像をジオメトリに貼り付けたりできます。

// マテリアル作成
const ballMaterial = new THREE.MeshPhysicalMaterial();

メッシュの追加

メッシュ(Mesh)とは、ジオメトリ(形状)とマテリアル(材質)の組み合わせからなります。
これにより、形状と外観が定義された3Dオブジェクトが作成されます。
Mesh()を用いることでメッシュの追加ができます。
Mesh()はパラメーターとして、ジオメトリとマテリアルをとります。

// メッシュ化
const ballMesh = new THREE.Mesh(ballGeometry, ballMaterial);

シーンに追加

メッシュ化したものをシーンに追加します。

scene.add(ballMesh);

レンダリング関数実行

そして最後に、「レンダラーの追加」で追加したレンダリング関数を実行し、ようやく画面にレンダリングされます🌸
引数には設定したシーンと、カメラを渡します。

// レンダリング関数
renderer.render(scene, camera);

...と思いきやブラウザを確認すると何も映ってないです。
どうやらカメラ位置の設定が必要なようで、現状カメラは原点位置になっていて球が見えない状態になっています。
下記コードをPerspectiveCameraインスタンスの下部分に追加して、カメラ位置を調整します。
引数は、x軸、y軸、z軸になっており、ここではz軸方向に+500させます。

camera.position.set(0, 0, 500);

このようにカメラを球から適切な距離に移動させると見えるようになりました!

スクリーンショット 2023-06-27 16.50.22.png

MeshPhysicalMaterial()で色やテクスチャを設定していないので真っ黒な球体のままです。

ここまでのコード

import * as THREE from "three";

// シーンの追加
const scene = new THREE.Scene();

// カメラの追加
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / innerHeight, 0.1, 1000);
camera.position.set(0, 0, 500);

// レンダラーの追加
const renderer = new THREE.WebGLRenderer({
  alpha: true,
});
// サイズ調整
renderer.setSize(window.innerWidth, window.innerHeight);
// レンダリングしたいDOM要素と紐付け
document.body.appendChild(renderer.domElement);

// ジオメトリ作成
const ballGeometry = new THREE.SphereGeometry(100, 64, 32);

// マテリアル作成
const ballMaterial = new THREE.MeshPhysicalMaterial();

// メッシュ化
const ballMesh = new THREE.Mesh(ballGeometry, ballMaterial);
scene.add(ballMesh);

// レンダリング関数
renderer.render(scene, camera);

cssで調整や背景画像などを設置するといい感じになります。

body {
  margin: 0;
  width: 100%;
  height: 100%;
  background: url("./images/space.jpg") no-repeat center center / cover;
}

スクリーンショット 2023-06-27 16.53.11.png

平行光源の追加

DirectionalLight()はThree.jsで提供されているライトの一種で、無限遠光源(平行光源)を模倣します。
太陽の光など、非常に遠くの光源からの光を表現するのに適しています。パラメーターは16新数(0x部分)でカラーと、光の強さを渡せます。

// 平行光源の追加
const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
// 光の当たる位置調整
directionalLight.position.set(1, 1, 1); 
// シーンに追加
scene.add(directionalLight);

下記のように、x軸1, y軸1, z軸1の位置に0xffffff, 2の光が表現できているかと思います。
スクリーンショット 2023-06-27 17.16.48.png

ポイント光源を追加

PointLight()もThree.jsで提供されているライトの一種で特定の点から全方向へ等しく光を放つ光源を模倣します。
これは、実際の物理的な光源、例えば電球や蝋燭のように振る舞います🔦
とれるパラメーターはDirectionalLight()と同じです。
下記コードはポイント光源の追加と位置調整、シーンの追加をしています。

// ポイント光源を追加
const pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(-200, -200, -200);
scene.add(pointLight);

スクリーンショット 2023-06-27 17.36.42.png

ポイント光源を動かすアニメーション

pointLight.position.set()で光源位置を指定しましたが、この光源位置を動的に変更し、フレーム毎にレンダリングすることによって光源が移動しているアニメーションを再現できます。

// ポイント光源のアニメーション
function animate() {
  // ポジションを動的に指定
  pointLight.position.set(200 * Math.sin(Date.now() / 500), 200 * Math.sin(Date.now() / 1000), 200 * Math.cos(Date.now() / 500));
  // フレーム単位で関数実行
  requestAnimationFrame(animate);

  // この関数内でレンダリング関数実行
  renderer.render(scene, camera);
}
animate(); // アニメーション関数の実行

タイトルなし.gif

マウスで動かす

OrbitControls()はユーザーインターフェースコントロールの一つで、ユーザーはマウスやタッチジェスチャーを使用してカメラを回転させ、ズームイン/アウト、パン(水平または垂直に移動)を行うことができます。
引数にはカメラとDOMエレメントをとります。

viteで環境構築を行いましたがOrbitControls()はThree.jsのコアライブラリには含まれていませんので、別途インポートする必要があります。

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

// マウス操作
const Controls = new OrbitControls(camera, renderer.domElement);

タイトルなし.gif

テクスチャの追加

画像をダウンロードし、マテリアルにてテクスチャ設定をしてみます。
TextureLoader()を使用します。

// テクスチャの追加
const texture = new THREE.TextureLoader().load("./textures/earth.jpg");

マテリアルの追加の章で説明した通り、MeshPhysicalMateria()はパラメーターでテクスチャの設定ができますので
このTextureLoader()の返却値が格納されたtextureをmapプロパティのvalueとして渡します。

パラメーターでは色や、mapというテクスチャの指定ができ、画像をジオメトリに貼り付けたりできます。

// マテリアル作成
const ballMaterial = new THREE.MeshPhysicalMaterial({ map: texture });

スクリーンショット 2023-06-27 19.04.58.png

完成!

コードのリファクタリングや、リサイズ対応などを少しして完成です!

最終的なコード

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

window.addEventListener("load", init);

function init() {
  // シーンの追加
  const scene = new THREE.Scene();

  // カメラの追加
  const camera = new THREE.PerspectiveCamera(50, window.innerWidth / innerHeight, 0.1, 1000);
  camera.position.set(0, 0, 500);

  // レンダラーの追加
  const renderer = new THREE.WebGLRenderer({
    alpha: true,
  });
  // サイズ調整
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 画質調整
  renderer.setPixelRatio(window.devicePixelRatio);
  // レンダリングしたいDOM要素と紐付け
  document.body.appendChild(renderer.domElement);

  // テクスチャの追加
  const texture = new THREE.TextureLoader().load("./textures/earth.jpg");

  // ジオメトリ作成
  const ballGeometry = new THREE.SphereGeometry(100, 64, 32);

  // マテリアル作成
  const ballMaterial = new THREE.MeshPhysicalMaterial({ map: texture });

  // メッシュ化
  const ballMesh = new THREE.Mesh(ballGeometry, ballMaterial);
  scene.add(ballMesh);

  // 平行光源の追加
  const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
  // 光の当たる位置調整
  directionalLight.position.set(1, 1, 1);
  // シーンに追加
  scene.add(directionalLight);

  // ポイント光源を追加
  const pointLight = new THREE.PointLight(0xffffff, 1);
  pointLight.position.set(-200, -200, -200);
  scene.add(pointLight);

  // マウス操作
  const Controls = new OrbitControls(camera, renderer.domElement);

  // ポイント光源のアニメーション
  function animate() {
    // ポジションを動的に指定
    pointLight.position.set(
      200 * Math.sin(Date.now() / 500),
      200 * Math.sin(Date.now() / 1000),
      200 * Math.cos(Date.now() / 500)
    );
    // フレーム単位で関数実行
    requestAnimationFrame(animate);

    // レンダリング関数
    renderer.render(scene, camera);
  }

  // ブラウザリサイズ対応
  function onWindowResize() {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
  }

  window.addEventListener("resize", onWindowResize);
  animate();
}

簡単に触れてみましたがマテリアルやシーン、ジオメトリなど、様々な機能の上で初めて、3Dオブジェクトがレンダリングされることがわかりました!

参考

https://www.udemy.com/course/threejs-beginner/

Discussion