React Three Fiber で 3D の地球をブラウザに表示させる
こんにちは、じゅんじゅんです。先日社内の勉強会で前々から興味があった Three.js を使用して 3D の地球をブラウザに表示させる方法を発表しました。
Three.js について調べていたとき、 Three.js を React で記述できる React Three Fiber というライブラリーがあることを知りました。
今回は 3D の地球を React Three Fiber で表示させる方法を Three.js での書き方と比較しながら紹介します。
環境
- react: 18.1.0
- @react-three/fiber: 8.0.19
- three: 0.141.0
対象読者
- 3D コンテンツの制作に興味がある方
- React で Three.js を扱いたい方
Three.js で 3D の地球を表示
まずは Three.js で 3D の地球を表示させるコードを紹介します。以下を参考に作成しました。
<html>
<head>
<meta charset="utf-8" />
<script src="https://unpkg.com/three@0.137.4/build/three.min.js"></script>
<script>
window.onload = () => {
// シーンを作成
const scene = new THREE.Scene();
// カメラを作成
const camera = new THREE.PerspectiveCamera(45, 960 / 540);
camera.position.set(0, 0, 1000);
// ライトを作成
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1);
// ライトをシーンに追加
scene.add(directionalLight);
// ジオメトリを作成
const geometry = new THREE.SphereGeometry(300);
// 画像を読み込む
const loader = new THREE.TextureLoader();
const texture = loader.load('./earthmap1k.jpg');
// マテリアルに画像を設定
const material = new THREE.MeshStandardMaterial({
map: texture,
});
// メッシュを作成
const mesh = new THREE.Mesh(geometry, material);
// 3D空間にメッシュを追加
scene.add(mesh);
// レンダラーを作成
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#myCanvas'),
});
renderer.setSize(width, height);
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()
}
</script>
</head>
<body>
<canvas id="myCanvas"></canvas>
</body>
</html>
地球の画像はこちらのサイトからお借りしました。
このコードによりブラウザに地球が表示されます。 これを React Three Fiber に書き換えていきます。
React Three Fiber で 3D の地球を表示
React Three Fiber の導入
作業ディレクトリを作成したら、以下のコマンドを実行して React Three Fiber をインストールします。
npm install three @react-three/fiber
Canvas コンポーネントを配置
まずは App.jsx を以下のように記述し、Canvas
コンポーネントを配置します。
import { Canvas } from '@react-three/fiber';
import './App.css';
const App = () => {
return (
<div id="canvas-container">
<Canvas
camera={{
position: [0, 0, 1000],
aspect: 960 / 540,
}
}>
</Canvas>
</div >
);
}
export default App;
Canvas
コンポーネントは、レンダリングに必要な基本要素であるシーンとカメラを裏側で設定してくれています。
また、 Canvas
コンポーネントに対して camera
プロパティーを記述してカメラの設定ができます。
Three.js では以下のように記述していた部分です。
// シーンを作成
const scene = new THREE.Scene();
// カメラを作成
const camera = new THREE.PerspectiveCamera(45, 960 / 540);
camera.position.set(0, 0, 1000);
さらに、Canvas コンポーネントはフレームごとにシーンをレンダリングする役割も持っています。レンダラーを用意したり、render
関数を使用する必要はありません。Three.js では以下の部分です。
// レンダラーを作成
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#myCanvas'),
});
renderer.setSize(width, height);
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate();
これらの処理は Canvas
コンポーネントが舞台裏で行ってくれます。
ここで、 Canvas
のサイズを設定しておきます。 Canvas
は親ノードの大きさに合わせて変更されるため、 App.css で canvas-container
属性に対して width
と height
を以下のように設定して全画面にします。
#canvas-container {
width: 100vw;
height: 100vh;
}
ライトを作成
次はライトを作成します。App.jsx を以下のように修正します。
import { Canvas } from '@react-three/fiber';
import './App.css';
const App = () => {
return (
<div id="canvas-container">
<Canvas
camera={{
position: [0, 0, 1000],
aspect: 960 / 540,
}}>
<directionalLight color="white" position={[1, 1, 1]} /> </Canvas>
</div>
);
}
export default App;
Canvas
コンポーネント下にライトの要素を置くだけでシーンに設置できます。今回は平行光源である directionalLight
を置いていますが、 AmbientLight
など他のライトでも同様です。
Three.js ではライトに対して set()
を使って color
や position
を設定していましたが、 react Three Fiber では directionalLight
要素の属性として設定できます。
紹介のため color="white"
と記載していますが、 color
はデフォルトで白ですので記述は不要です。
Three.js では以下のようにライトを作成していました。
// ライトを作成
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1);
// ライトをシーンに追加
scene.add(directionalLight);
メッシュを作成
次はメッシュを作成します。メッシュはオブジェクトの形状を 3D 空間で表現するためのジオメトリ (オブジェクトの種類) やマテリアル (オブジェクトの質感) を保持します。
mesh
コンポーネントを Canvas
コンポーネント下に置くことでメッシュを配置できます。App.jsx を以下のように修正します。
import { Canvas } from '@react-three/fiber';
import './App.css';
import { useLoader } from "@react-three/fiber";import * as THREE from 'three';import img from "./earthmap1k.jpg";
const App = () => {
const texture = useLoader(THREE.TextureLoader, img); return (
<div id="canvas-container">
<Canvas
camera={{
position: [0, 0, 1000],
aspect: 960 / 540,
}
}>
<mesh> <sphereGeometry args={[300]} /> <meshStandardMaterial map={texture} /> </mesh> <directionalLight color="white" position={[1, 1, 1]} />
</Canvas>
</div>
);
}
export default App;
mesh
コンポーネントの子要素としてジオメトリとマテリアルを置くだけで、自動的に親の mesh
コンポーネントに割り当てられます。
今回は地球を表示させるのでジオメトリは sphereGeometry
、マテリアルはスタンダードな meshStandardMaterial
にしています。
Three.js では sphereGeometry
の半径をコンストラクターで指定していました。
const geometry = new THREE.SphereGeometry(300);
React Three Fiber では args
という属性に配列を渡すことで指定できます。
<sphereGeometry args={[300]} />
これで npm run start
を実行するとブラウザに地球が表示されているのが確認できます。
メッシュの作成とシーンへの追加は Three.js では以下のように記述していました。
// ジオメトリを作成
const geometry = new THREE.SphereGeometry(300);
// 画像を読み込む
const loader = new THREE.TextureLoader();
const texture = loader.load('./earthmap1k.jpg');
// マテリアルに画像を設定
const material = new THREE.MeshStandardMaterial({
map: texture,
});
// メッシュを作成
const mesh = new THREE.Mesh(geometry, material);
// 3D空間にメッシュを追加
scene.add(mesh);
完成したコード
最終的に App.jsx は以下のようになりました。
import { Canvas } from '@react-three/fiber';
import './App.css';
import { useLoader } from "@react-three/fiber";
import * as THREE from 'three';
import img from "./earthmap1k.jpg";
const App = () => {
const texture = useLoader(THREE.TextureLoader, img);
return (
<div id="canvas-container">
<Canvas
camera={{
position: [0, 0, 1000],
aspect: 960 / 540,
}
}>
<mesh>
<sphereGeometry args={[300]} />
<meshStandardMaterial map={texture} />
</mesh>
<directionalLight color="white" position={[1, 1, 1]} />
</Canvas>
</div>
);
}
export default App;
Three.js よりもとても少ない行数で書けています。
また、基本的にコンポーネントを配置していくだけで必要な要素が追加されていくので直感的に書くことができ、コードを読んだときの理解もしやすいと思います。
まとめ
とりあえず 3D オブジェクトを表示するという基本的な部分をやってみました。今後はアニメーションやイベントなどを扱えるようになりたいと思います。