6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Three.jsとreact-three-fiberの実装を比較する

Posted at

はじめに

React ベースの Web アプリで Three.js を実装する方法として、ピュアな Three.js を使う方法とreact-three-fiberというライブラリを使う方法が挙げられます。
今回はこのreact-three-fiberと Three.js で実装した時を比べてみます。

実装する内容

3D モデルを読み込んで表示します。
アニメーションで上下に動かします。
デバッグ UI も用意します。

Three実装.gif

Three.js

純粋な Three.js の実装を見たいので Vanilla で実装していきます。

初期構築

vite で構築します

yarn create vite

✔ Select a framework: › Vanilla
✔ Select a variant: › JavaScript

package install

yarn add three lil-gui

css の修正

画面幅いっぱいまで描画されるようにします。

style.css
* {
  margin: 0;
  padding: 0;
}

Three の実装

デバッグ UI にはlil-guiを使います。
3D モデルはプロジェクト直下に modelsというディレクトリを作って置いています。
3D モデルは fbx ファイルを使います。

main.js
import * as THREE from "three";
import * as dat from "lil-gui";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

const gui = new dat.GUI();
const animationFolder = gui.addFolder("animation");
const ambientLightFolder = gui.addFolder("ambientLight");
const directionalLightFolder = gui.addFolder("directionalLight");

// canvasの設定
const canvas = document.getElementById("canvas");

// sceneの設定
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);

// cameraの設定
const camera = new THREE.PerspectiveCamera(
  35,
  innerWidth / innerHeight,
  0.1,
  2000
);
camera.position.z = 80;
camera.position.y = 7.2;
scene.add(camera);

// ライト
const ambientLight = new THREE.AmbientLight();
ambientLight.color.set(0xffffff);
ambientLight.intensity = 0.5;
scene.add(ambientLight);
ambientLightFolder
  .add(ambientLight, "intensity")
  .min(0)
  .max(1)
  .step(0.1)
  .name("ambIntensity");
ambientLightFolder.addColor(ambientLight, "color");

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.4);
directionalLight.position.set(1, 0.55, 5);
scene.add(directionalLight);

directionalLightFolder
  .add(directionalLight, "intensity")
  .min(0)
  .max(1)
  .step(0.1)
  .name("dirIntensity");
directionalLightFolder.addColor(directionalLight, "color");

// オブジェクト
let drone;
const fbxLoader = new FBXLoader();
fbxLoader.setResourcePath("./models/Drone_Costum/Teturizer/");
fbxLoader.load("./models/Drone_Costum/Material/drone_costum.fbx", (obj) => {
  obj.scale.set(0.025, 0.025, 0.025);
  scene.add(obj);
  drone = obj;
});

// レンダリング設定
const renderer = new THREE.WebGLRenderer({
  canvas,
});
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio);

//コントロール
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// ブラウザのリサイズの操作
window.addEventListener("resize", () => {
  // サイズのアップデート
  sizes.width = window.innerWidth;
  sizes.height = window.innerHeight;

  // カメラのアップデート
  camera.aspect = sizes.width / sizes.height;
  camera.updateProjectionMatrix();

  // レンダラーのアップデート
  renderer.setSize(sizes.width, sizes.height);
  renderer.setPixelRatio(window.devicePixelRatio);
});

const clock = new THREE.Clock();

const animation = {
  isAnimation: true,
  frequency: 5,
  depth: 0.5,
};
animationFolder.add(animation, "isAnimation").name("animate");
animationFolder.add(animation, "frequency").min(0).max(10).step(0.1);
animationFolder.add(animation, "depth").min(0).max(10).step(0.1);

// アニメーション設定
const animate = () => {
  if (drone) {
    if (animation.isAnimation)
      drone.position.y =
        Math.sin(clock.getElapsedTime() * animation.frequency) *
        animation.depth;
  }
  renderer.render(scene, camera);
  window.requestAnimationFrame(animate);
};

animate();

実装結果

Three実装.gif

react-three-fiber

React ベースで実装します。

初期構築

vite で構築します

yarn create vite

✔ Select a framework: › React
✔ Select a variant: › TypeScript

package install

yarn add three leva @react-three/drei @react-three/fiber
yarn add -D @types/three

css の修正

画面幅いっぱいまで描画されるようにします。
index.cssの中身は全部削除します。

App.css
body {
  margin: 0;
  padding: 0;
}

react-three-fiber の実装

デバッグ UI にはlevaを使います。
3D モデルはpublicmodelsというディレクトリを作って置いています。
3D モデルは fbx ファイルを使います。

App.tsx
import {
  Html,
  OrbitControls,
  PerspectiveCamera,
  useProgress,
} from "@react-three/drei";
import { Canvas, useFrame, useLoader } from "@react-three/fiber";
import { Suspense, useRef } from "react";
import "./App.css";
import * as THREE from "three";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { OrbitControls as OrbitControlImpl } from "three-stdlib";
import { Mesh } from "three";
import { useControls } from "leva";

const App = () => {
  const fbx = useLoader(
    FBXLoader,
    "/models/Drone_Costum/Material/drone_costum.fbx",
    (loader) => {
      loader.setResourcePath("/models/Drone_Costum/Teturizer/");
    }
  );
  const controlRef = useRef<OrbitControlImpl>({} as OrbitControlImpl);
  const model = useRef({} as Mesh);

  const { animate, frequency, depth } = useControls("animation", {
    animate: true,
    frequency: { value: 5, min: 0, max: 10, step: 0.1 },
    depth: { value: 0.5, min: 0, max: 10, step: 0.1 },
  });
  const { ambIntensity, ambColor } = useControls("ambientLight", {
    ambIntensity: { value: 0.5, min: 0, max: 1, step: 0.1 },
    ambColor: { value: "#fff", label: "color" },
  });
  const { dirIntensity, dirColor } = useControls("dirientLight", {
    dirIntensity: { value: 0.4, min: 0, max: 1, step: 0.1 },
    dirColor: { value: "#fff", label: "color" },
  });

  // アニメーション設定
  useFrame(({ clock }) => {
    if (animate)
      model.current.position.y =
        Math.sin(clock.getElapsedTime() * frequency) * depth;
  });

  return (
    <>
      {/* cameraの設定 */}
      <PerspectiveCamera
        makeDefault
        args={[35, innerWidth / innerHeight, 0.1, 2000]}
        position={[0, 7.2, 80]}
      />
      {/* ライト */}
      <ambientLight
        color={new THREE.Color(ambColor)}
        intensity={ambIntensity}
      />
      <directionalLight
        color={new THREE.Color(dirColor)}
        intensity={dirIntensity}
        position={[1, 0.55, 5]}
      />
      {/* オブジェクト */}
      <Suspense fallback={<Loading />}>
        <primitive object={fbx} scale={[0.025, 0.025, 0.025]} ref={model} />
      </Suspense>
      {/* コントロール */}
      <OrbitControls makeDefault ref={controlRef} />
    </>
  );
};

export default () => (
  // canvasの設定 scene・レンダリング設定込み
  <Canvas
    onCreated={({ gl, scene }) => {
      scene.background = new THREE.Color(0xffffff);
    }}
    flat
    style={{
      height: innerHeight,
      width: innerWidth,
    }}
  >
    <App />
  </Canvas>
);

const Loading = () => {
  const { progress } = useProgress();
  return <Html center>{progress} % loaded</Html>;
};

実装結果

r3f実装.gif

まとめ

react-three-fiber ではシーン設定やカメラ設定など、Three.js の実装の中で面倒な部分をライブラリの内部でやってくれます。
よって、そのまま Three.js で実装するよりコード量が少なくなります。
また、React ライクな記述方法になるので React で 3D 描画を実装する際は積極的に取り入れていきたいですね。

比較項目 Three.js react-three-fiber
コード量 - (Three.jsと比較して)少なくなる
デバッグ UI lil-gui leva
アニメーション requestAnimationFrame の中で実装 useFrame の中で実装
3D モデルのロード FBXLoader + 引数内の関数で scene にモデルを追加 useLoader + suspense で遅延ロード

参考

Three.js 公式
react-three-fiber 公式 Doc

6
1
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
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?