株式会社シベスピ 従業員ブログ

シベスピの社員ブログ。技術・想い・経験沢山書いていきます!

【Three.js】Webページで3Dモデルを表示してみた

気になるホームページがありました

こんにちは。
最近見かけた「CHILL OUT」という飲み物のホームページで、飲み物の缶がふわふわ動いていたので、どうやっているのだろうと気になって基本的な部分だけ実践してみました。
https://butfirstchillout.com/

WebGL (Web Graphics Library)

このような技術は「WebGL」と呼ばれます。
Webブラウザで2D/3Dグラフィックスを表示することができるJavaScript APIです。

Three.js

WebGLを実現するためのJavaScript ライブラリに「Three.js」というものがあります。
比較的分かりやすく、多様なツールやサービスと連携しても安定して動作するとのことです。CHILL OUTのページでも使われていそうでしたので、今回はこちらを使用します。

実装してみた

あんなに綺麗なモデルは作れるはずもなく、Blenderで作成したこの缶wの描画を目指します。

環境は以下の通りです。

実行環境
Node.js:v14.17.6
ライブラリ
Three.js:v0.154.0
サーバー
Node.jsのhttp-server:v14.1.1
まずはhtml

ここで特徴的なのはcanvasタグです。WebGLと組み合わせることで3Dグラフィックスを描画することができます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <script type="importmap">
        {
          "imports": {
            "three" : "/node_modules/three/build/three.module.js"
          }
        }
      </script>
    <script type="module" src="script.js"></script>
    <title>3D Model Viewer</title>
  </head>
  <body>
    <div class="container">
      <h1>3D Model</h1>
    </div>
    <canvas class="cylinder-canvas"></canvas>
  </body>
</html>
続いてJavascript。最初のインポート

いきなり苦戦しました。1行目が全然うまくいかず。
2,3行目と同じように書いても、「見つかりません」や「パスの書き方が正しくありません」など。。。
最終的にはhtml側のimportmapと組み合わせて動いてくれました。

import * as THREE from "three";
import { GLTFLoader } from "/node_modules/three/examples/jsm/loaders/GLTFLoader.js";
import { OrbitControls } from "/node_modules/three/examples/jsm/controls/OrbitControls.js";
3Dモデルをcanvasに描画するための準備の部分

Sceneは描画対象の3D空間です。3Dモデルは.gltf形式です。

// Canvas取得
const canvas = document.querySelector("canvas.cylinder-canvas");
// Scene作成
const scene = new THREE.Scene();
// 3Dモデル取得
const loader = new GLTFLoader();
const url = "/3DModel/cylinder.gltf";
let model = null;
3Dモデルを描画する部分

描画するためにsceneに3Dモデルを追加します。
アニメーションで3Dモデルを動かします。

// 3Dモデル配置、アニメーション描画開始
loader.load(
  url,
  function (gltf) {
    model = gltf.scene;
    model.position.set(0, 0, 0);
    scene.add(model);
    // アニメーション
    mixer = new THREE.AnimationMixer(model);
    gltf.animations.forEach((animation) => {
      actions.push(mixer.clipAction(animation).play());
    });
    tick();
  },
  function (error) {console.log(error);}
);
3Dモデルに対する光源や視点の設定

光源が無ければ、真っ黒な塊が描画されてしまいます。また、1方向からだけ光を当てると反対側は真っ黒のままです。そんな所まで設定しなければいけないのかと新鮮でした。
Cameraによる視点はマウスに連動する設定です。

/**
 * 光源
 */
// 平行光
const light = new THREE.DirectionalLight(0xFFFFFF);
light.intensity = 1;
light.position.set(3, 10, 1);
scene.add(light);
// 周辺光
const ambientLight = new THREE.AmbientLight(0XFFFFE0, 0.7);
scene.add(ambientLight);
/**
 * サイズ
 */
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};
/**
 * カメラ
 */
const camera = new THREE.PerspectiveCamera(90, sizes.width / sizes.height, 0.5, 150);
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 2;
scene.add(camera);
// カメラ制御
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
3Dモデルを動かす部分

レンダラーは、3D空間の描画そのものを行っています。
アニメーションでは、短い時間で描画を更新し動きを実現します。

/**
 * レンダラー
 */
const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  alpha: true,
  antialias: true,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio));
/**
 * アニメーション
 */
const tick = () => {
  // 時間差
  const delta = clock.getDelta();
  mixer.update(delta);
  // カメラ制御
  controls.update();
  // レンダリング
  renderer.render(scene, camera);
  window.requestAnimationFrame(tick);
};

結果

紹介したホームページとはほど遠いですが、動いたー!とても軽くて驚きました。
今回、3Dモデルを扱うことも動かすことも初めてだったので、とても楽しく良い経験になりました。知らない技術にはどんどん挑戦していきたいですね。

CHILL OUTってどんな飲み物なんでしょう。