【Three.js入門】ビルドツール不要、CDNだけで始める基本の使い方

ブラウザで3Dを扱えるライブラリといえばThree.jsですが、「まずnpmでプロジェクトを作って、Viteを入れて…」という記事が多くて、ちょっと試したいだけなのに腰が重くなりがちです。

実はThree.jsはHTMLファイル1枚だけで始められます。私も勉強を始めた頃は、デモ用のHTMLを何十枚も作っては壊すというやり方で覚えました。ビルドが要らないのでファイルをブラウザで開くだけで動き、失敗しても捨てるだけ。この気軽さが最初の学習には一番効くと思います。

まずは完成形のデモから。マウス操作は何もできない、ただ回るキューブです。

デモを別タブで開く

CDNからThree.jsを読み込む

npmを使わずに、importmapでCDNのモジュールを読み込みます。<body> の中にこれを書くだけです。

<script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.172.0/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.172.0/examples/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from 'three';
  // ここに3Dのコードを書く
</script>

importmapは「three という名前でimportしたらこのURLを見に行け」というブラウザへの指示です。これを書いておくと、npmでインストールした場合と同じ import * as THREE from 'three' という書き方がそのまま使えます。後でViteなどに移行するときも、importmapを消すだけでコードは無修正で済むのがポイントです。

three/addons/ も登録していますが、これは後の回で使うOrbitControls(カメラ操作)やGLTFLoader(モデル読み込み)などの公式アドオン用です。最初に書いておいて損はありません。

3D表示に最低限必要な3点セット

Three.jsの画面は、どんなに複雑なものでも必ずこの3つでできています。

  • シーン — 3Dオブジェクトを置いていく空間
  • カメラ — その空間をどこから見るか
  • レンダラー — シーンとカメラを受け取って実際に描画する装置
// シーン
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x222233);

// カメラ(視野角, アスペクト比, 描画する最短距離, 最長距離)
const camera = new THREE.PerspectiveCamera(
  25,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.set(0, 0, 15);

// レンダラー
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

renderer.domElement は canvas 要素です。自分で <canvas> を書かなくても、レンダラーが作ったものをbodyに追加すればそれで表示されます。

カメラは原点(0, 0, 0)を向いた状態で作られるので、position.set(0, 0, 15) で少し後ろに下げています。カメラを動かさないと、オブジェクトの中にカメラがめり込んで何も見えない、というのが最初にやりがちな失敗です。

キューブを置く

オブジェクトはジオメトリ(形)とマテリアル(見た目)を合わせてメッシュにするという手順で作ります。

const geometry = new THREE.BoxGeometry(2, 2, 2);
const materials = [
  new THREE.MeshBasicMaterial({ color: 0xff4444 }),
  new THREE.MeshBasicMaterial({ color: 0x44ff44 }),
  new THREE.MeshBasicMaterial({ color: 0x4444ff }),
  new THREE.MeshBasicMaterial({ color: 0xffff44 }),
  new THREE.MeshBasicMaterial({ color: 0x44ffff }),
  new THREE.MeshBasicMaterial({ color: 0xffffff }),
];
const cube = new THREE.Mesh(geometry, materials);
scene.add(cube);

マテリアルを配列で6個渡すと、ボックスの各面に順番に割り当てられます。回転したときに面の区別がつくので、動作確認用のキューブはこの作り方がおすすめです。

MeshBasicMaterial はライトを無視して色がそのまま出るマテリアルです。ライトの設定をしなくても表示されるので最初の一歩に向いています。ライトに反応するマテリアルは次回で扱います。

アニメーションループ

1回描画して終わりではなく、毎フレーム描画し続けるループを作ります。

const clock = new THREE.Clock();

function animate() {
  const elapsedTime = clock.getElapsedTime();

  cube.rotation.x = Math.sin(elapsedTime);
  cube.rotation.y = elapsedTime * 0.5;

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

requestAnimationFrame はブラウザの描画タイミング(通常60fps)に合わせて関数を呼んでくれます。ここで大事なのが THREE.Clock で、getElapsedTime() は開始からの経過秒数を返します。「フレーム数」ではなく「経過時間」を基準に動かすことで、リフレッシュレートが違うモニターでも同じ速度で回ります。144Hzのゲーミングモニターで見ると2倍速で回る、みたいな事故を防げます。

ウィンドウリサイズへの対応

ウィンドウサイズが変わったときにカメラとレンダラーを更新しないと、表示が引き伸ばされてしまいます。定型文としてこれを毎回入れておきます。

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

camera.aspect を変えたあとの updateProjectionMatrix() を忘れると反映されないので注意してください。

まとめ

  • importmapを使えばHTML1枚でThree.jsを始められる
  • 画面の構成要素はシーン・カメラ・レンダラーの3点セット
  • オブジェクトはジオメトリ+マテリアル=メッシュ
  • アニメーションは経過時間ベースで動かす

デモの全ソースはこのHTMLファイルをそのまま「ページのソースを表示」で見られます。コピーしてローカルで開けばすぐ改造できるので、まずは色や回転速度をいじってみてください。


Three.js解説シリーズ(全7回)

  1. 【Three.js入門】ビルドツール不要、CDNだけで始める基本の使い方(この記事)
  2. レンダラー・ライト・カメラの使い方と設定まとめ
  3. キーボードとNipplejsでプレイヤーを動かす
  4. GLTFLoaderで3Dモデルをインポートする
  5. ファイル分割はDIパターンがおすすめ
  6. CloudflareへのデプロイとWebSocketでの位置同期
  7. WebGLレンダラーとCSS3Dレンダラーを共存させる