はじめに
React ベースの Web アプリで Three.js を実装する方法として、ピュアな Three.js を使う方法とreact-three-fiber
というライブラリを使う方法が挙げられます。
今回はこのreact-three-fiber
と Three.js で実装した時を比べてみます。
実装する内容
3D モデルを読み込んで表示します。
アニメーションで上下に動かします。
デバッグ UI も用意します。
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 の修正
画面幅いっぱいまで描画されるようにします。
* {
margin: 0;
padding: 0;
}
Three の実装
デバッグ UI にはlil-gui
を使います。
3D モデルはプロジェクト直下に models
というディレクトリを作って置いています。
3D モデルは fbx ファイルを使います。
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();
実装結果
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
の中身は全部削除します。
body {
margin: 0;
padding: 0;
}
react-three-fiber の実装
デバッグ UI にはleva
を使います。
3D モデルはpublic
に models
というディレクトリを作って置いています。
3D モデルは fbx ファイルを使います。
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>;
};
実装結果
まとめ
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 で遅延ロード |