React Three Fiberは、Reactのコンポーネントでthree.jsの3次元空間を組み立てて、3Dオブジェクトが操作できるライブラリです。Reactの宣言的な記述で、WebGLによる3D表現が簡単にできます。採り上げるのは、3次元空間で立方体を回してみるというつぎのような作例です。カメラはマウスでインタラクティブに操作できます。
本稿の作例
https://codesandbox.io/s/react-three-fiber-typescript-basic-example-dnzllu?file=/src/App.tsx:579-605
マウスによるカメラ操作
- ドラッグ: 立方体の中心から一定の距離で旋回する。
- 右ボタンドラッグ: カメラの方向は保ったまま、フレーミングを水平・垂直に移動させる。
- ホイール: カメラの方向に、映像を拡大・縮小させる。
ひな形プロジェクトの作成とインストール
まず、作業の準備です。プロジェクトのひな形は、Create React Appでつくります(プロジェクト名はreact-three-fiber-typescript
としました)。テンプレート(--template
)のオプションに加えたのはTypeScriptです。もっとも、型推論が働くので、明示的な型づけはほとんどしていません。
npx create-react-app react-three-fiber-typescript --template typescript
そして、three.js(three
)とReact Three Fiber(@react-three/fiber
)のインストールです。@types/three
も加えてください。
npm install three @react-three/fiber
npm install --save-dev @types/three
3次元空間に立方体を加える
素の(標準)JavaScriptでthree.jsによる3次元空間の舞台を整えるには、まずつぎの3つのオブジェクトがつくられなければなりません。
- シーン(scene)
- 3次元空間を表し、3Dオブジェクトやライトが加えられる。
- カメラ(camera)
- 3次元空間を映し、レンダラーに送って画面に投影する。
- レンダラー(renderer)
- カメラから受け取った画像を、画面の再描画のたびに更新する。
それが、React Three Fiberでは、Canvas
コンポーネントひとつで済むのです。作例のつぎのコード(src/App.tsx
)には、camera
プロパティでカメラの定めを加えました。けれど、デフォルトの設定はされているので、除いても3次元空間の準備はできているのです。Canvas
の子として加える3Dオブジェクトの立方体(Cube
)は、このあとコンポーネントとして定めます。
import React from 'react';
import { Canvas } from '@react-three/fiber';
import { Cube } from './Cube';
function App() {
return (
<div className="App">
<Canvas
camera={{
fov: 45,
near: 0.1,
far: 1000,
position: [0, 0, 5]
}}
>
<Cube />
</Canvas>
</div>
);
}
export default App;
camera
プロパティに定めた項目の内容はつぎのとおりです。こうした設定をするには、three.jsのドキュメントに当たらなければなりません。標準JavaScriptで使うthree.jsの基本については、「three.js + TypeScript: 3次元空間で立方体を回してみる」をご参照ください。
-
fov
: 視野(field of view)またはカメラの用語で画角。写される光景の範囲を角度(度数)で示す。数字が大きいと広角で、写る範囲は広がるかわり、対象物は相対的に小さくなる。小さい角度は望遠に近づき、対象物が相対的に大きく写る。 -
aspect
: アスペクト比で、描画する矩形領域の $ \frac{幅}{高さ} $ 。矩形領域の比と合わなければ、画像の水平・垂直比が歪む。 -
near
: オブジェクトを描画するもっとも近い距離。これより近くのオブジェクトは画面に描かれない。 -
far
: オブジェクトを描画するもっとも遠い距離。これより遠くのオブジェクトは画面に描かれない。
3Dオブジェクトを定めるのはmesh
コンポーネントです。その中には形状を決めるジオメトリと、表面素材のマテリアルが加えられなければなりません。立方体はboxBufferGeometry
でつくれます。マテリアルに選んだのはmeshPhongMaterial
です。3Dオブジェクトはデフォルトでは、3次元空間の原点(0, 0, 0)を中心に配置されます。
つぎのコード例でboxBufferGeometry
には、プロパティとしてargs
に配列を与えました。これは、three.jsのBoxBufferGeometry()
コンストラクタに渡す引数(arguments)の意味です。立方体の幅と高さ、および奥行きを定めます。なお、three.jsでは、距離や長さは1を基本とした数値です。カメラの設定でオブジェクトは大きくも小さくも描かれるので、単位は気にせず比率と捉えて構いません。
マテリアルに用いたのは、three.jsのMeshPhongMaterial
です。表面は鏡面反射する光沢があります。color
プロパティには、CSSと同じかたちでカラーが定められ、16進数の場合は数値でも構いません。
import { FC } from 'react';
export const Cube: FC = () => {
return (
<mesh>
<boxBufferGeometry args={[1, 1, 1]} />
<meshPhongMaterial color="aqua" />
</mesh>
);
};
アプリケーションを実行してみると、立方体が真っ黒に描かれるでしょう。明かりがないからです。デフォルトの背景色が白であることに、ごまかされてはいけません。3次元空間に光を与えないと、3Dオブジェクトの正しい姿は見えないのです。
平行光源を加える
まず光源として加えるのは、directionalLight
(DirectionalLight
)です。「平行光源」とも呼ばれ、太陽光のようにひとつの方向から、光が平行に差し込みます。光が差してくる方向を定めるのがposition
プロパティです。原点に向かって差し込むので、座標(1, 1, 1)は、原点(0, 0, 0)と立方体の初期位置の前面右上角の座標(0.5, 0.5, 0.5)を結んだ直線の方向になります(右斜め上手前)。intensity
プロパティの数値は、1を100%とした光の明るさです。
function App() {
return (
<div className="App">
<Canvas
>
<directionalLight position={[1, 1, 1]} intensity={0.8} />
</Canvas>
</div>
);
}
これで、指定したカラー(aqua
)の3Dオブジェクトが表示されたでしょう。もっとも、カメラに正面向きなので、立方体か正方形平面か区別がつきません。
立方体に回転のアニメーションを加える
立方体をアニメーションで回転してみましょう。そのとき用いるフックが、React Three FiberのuseFrame
です。立方体のmesh
コンポーネントにはref
プロパティを与えておきます。x
およびy
軸周りの回転を加えるのはrotation
プロパティです(単位ラジアン)。フックに定めたコールバックは、再描画のたびに呼び出されます。立方体が回っていることを確かめられるでしょう。
import { useFrame } from '@react-three/fiber';
// import { FC } from 'react';
import { FC, useRef } from 'react';
import type { Mesh } from 'three';
export const Cube: FC = () => {
const cubeRef = useRef<Mesh>(null);
useFrame(() => {
const cube = cubeRef.current;
if (!cube) return;
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
});
return (
// <mesh>
<mesh ref={cubeRef}>
</mesh>
);
};
環境光を加える
まだ、少し気になるかもしれません。光の当たっていない面が漆黒であることです。物理的に考えれば、仕方ありません。でも、現実と照らし合わせると違和感があるでしょう。そこで用いる光源がコンポーネントambientLight
です。args
プロパティの配列は、AmbientLight()
コンストラクタの引数で、光のカラーを定めます。明るさのintensity
プロパティの値は控えめにしました。
function App() {
return (
<div className="App">
<Canvas
>
<ambientLight args={[0xffffff]} intensity={0.2} />
</Canvas>
</div>
);
}
立方体の陰になった面にも明るさが加わったでしょう。
OrbitControls
でカメラをマウス制御する
OrbitControls
を用いると、カメラが簡単にマウスで制御できます。@react-three/fiber
とは別モジュールとして、インストールに加えてください。
npm install @react-three/drei
使い方は簡単で、Canvas
にOrbitControls
コンポーネントを加えるだけです。マウス制御の基本機能は、すでにデフォルトで備わっています。
import { OrbitControls } from '@react-three/drei';
function App() {
return (
<div className="App">
<Canvas
>
<OrbitControls />
</Canvas>
</div>
);
}
これで、冒頭にご紹介した作例はでき上がりです。ふたつのモジュールの記述全体を、つぎのコード001にまとめました。JSXを除いたJavaScriptコードはほとんどないのが驚きです。
コード001■3次元空間の立方体を回転させてカメラはマウス制御する
import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
import { Cube } from './Cube';
function App() {
return (
<div className="App">
<Canvas
camera={{
fov: 45,
near: 0.1,
far: 1000,
position: [0, 0, 5]
}}
>
<Cube />
<ambientLight args={[0xffffff]} intensity={0.2} />
<directionalLight position={[1, 1, 1]} intensity={0.8} />
<OrbitControls />
</Canvas>
</div>
);
}
export default App;
import { useFrame } from '@react-three/fiber';
import { FC, useRef } from 'react';
import type { Mesh } from 'three';
export const Cube: FC = () => {
const cubeRef = useRef<Mesh>(null);
useFrame(() => {
const cube = cubeRef.current;
if (!cube) return;
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
});
return (
<mesh ref={cubeRef}>
<boxBufferGeometry args={[1, 1, 1]} />
<meshPhongMaterial color="aqua" />
</mesh>
);
};