Reactで3Dコンテンツを実装する - react-three-fiber入門

# はじめに

前回は[Reactで3Dコンテンツを実装する - three.js入門](https://qiita.com/sho-19202325/items/027e4c8899053d39be5f)という記事で[three.js](https://github.com/mrdoob/three.js/blob/dev/README.md)の概要を解説しました。
今回は[react-three-fiber](https://github.com/pmndrs/react-three-fiber)の概要を解説していきます。
three.jsの時と同様、公式リポジトリのサンプルコードを実装し、各要素の解説をしていこうと思います。

# React-Three-Fiberとは(再掲)

Reactでthree.jsを扱うためのライブラリで、three.jsの諸要素をJSXコンポーネントとして扱うことができるようになります。
このライブラリを使用するためには、three.jsの知識が必要なので、
three.jsがよくわからないという方は[前回の記事](https://qiita.com/sho-19202325/items/027e4c8899053d39be5f)をご一読いただけると幸いです。

https://docs.pmnd.rs/react-three-fiber/getting-started/introduction

https://github.com/pmndrs/react-three-fiber

# 早速使ってみる

## Reactアプリケーションの準備

例のごとく、create-react-appで、Reactアプリケーションを作成します。

```
npx create-react-app react_three_fiber_sample --template typescript
```

src/下に```pages/threeFiberSample.tsx```を作成し、App.tsxから呼び出します。
```tsx:src/App.tsx
import React from 'react';
import './App.css';
import { ThreeFiberSample } from './pages/threeFiberSample';

function App() {
  return <ThreeFiberSample />
}

export default App;
```

```tsx:src/pages/threeFiberSample.tsx
export const ThreeFiberSample = () => {
  return <div>hoge</div>
}
```

サーバーを立ち上げて、http://localhost:3000 にアクセスし、hogeと表示されれば完了です。

### three.jsのインストール

```
npm install three @types/three @react-three/fiber
```

### react-three-fiberのインストール

```
npm install @react-three/fiber
```

## 公式リポジトリのサンプルを実装する

React側の準備が整ったので、公式リポジトリのサンプルを実装して見ます。
Typescriptのサンプルがあるのでそちらを使用します。
元のサンプルではcreateRoot()を使用していますが、
今回はページを分けているのでそちらは使用せず、ThreeFiberSampleコンポーネントの中に```<Canvas>```を配置しています。

```tsx:src/pages/threeFiberSample
import * as THREE from 'three'
import React, { useRef, useState } from 'react'
import { Canvas, useFrame, ThreeElements } from '@react-three/fiber'

function Box(props: ThreeElements['mesh']) {
  const ref = useRef<THREE.Mesh>(null!)
  const [hovered, hover] = useState(false)
  const [clicked, click] = useState(false)
  useFrame((state, delta) => (ref.current.rotation.x += delta))
  return (
    <mesh
      {...props}
      ref={ref}
      scale={clicked ? 1.5 : 1}
      onClick={(event) => click(!clicked)}
      onPointerOver={(event) => hover(true)}
      onPointerOut={(event) => hover(false)}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
    </mesh>
  )
}

export const ThreeFiberSample = () => {
  return (
    <Canvas>
      <ambientLight />
      <pointLight position={[10, 10, 10]} />
      <Box position={[-1.2, 0, 0]} />
      <Box position={[1.2, 0, 0]} />
    </Canvas>
  )
}

```

サーバーを立ち上げて、http://localhost:3000 にアクセスします。
```
npm run start
```

下記のように表示されたら成功です。

![スクリーンショット 2022-12-20 16.12.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/890fe0da-98a9-aebe-4c23-11927a1e5256.png)

...が、あまりにも小さいですね、```src/index.css```に下記3点追記します。

- htmlに```height: 100%```を追記
- bodyに```height: 100%```を追記
- dev#rootに```height: 100%```を追記

```css:src/index.css
/* 1つ目 */
html {
  height: 100%;
}

body {
  margin: 0;
  height: 100%; /* 二つ目 */
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* 三つ目 */
div#root{
  height: 100%;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}
```

するといい感じの大きさで表示されるはずです。

![react_three_fiber_sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/b769e6c9-32a6-6c03-73df-39be234bcbd1.gif)

hoverをすると色が黄色からピンクに変わり、クリックするとBoxのサイズが変わります。

# React-Three-Fiberの諸概念解説

three.js同様、上記のサンプルを踏まえつつ、React-Three-Fiberの諸概念を解説します。

## Canvas

three.jsのシーン, カメラ, Rendererを一つにまとめたものです。
three.jsで上記が必須であるように、react-three-fiberにおいて、Canvas要素は必須です。

Canvas要素で囲むと、内部にthree.js由来のreact-three-fiberオブジェクトを描画することができます。
カメラに関する設定は、propsで渡すことができます。
サンプルの例では、何もpropsを渡していないので、default値である
```{ fov: 75, near: 0.1, far: 1000, position: [0, 0, 5] }```が設定されています。
※ アスペクト比はCanvas要素の縦横比そのものなので、今回の場合は画面の縦横比と同等になります。

デフォルトのCanvasをthree.jsに置き換えると、下記のようになります。

```ts
const scene = new Scene()
const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 5
```

:::note info
使用できるプロパティの詳細は[ドキュメント](https://docs.pmnd.rs/react-three-fiber/api/canvas#render-props)をご参照ください。
:::

また、Canvas要素は1フレームごとにレンダリングをおこなっています。
なので、three.jsのようにsetAnimationLoop()を使って再帰的にrendererを呼び出す必要はありません。
react-three-fiberでアニメーションを実装するには、後述のuseFrame hookを使用します。

## three.js由来のjsxコンポーネント

サンプルを見ていただくと
- \<mesh>\</mesh>
- \<xxxGeometry />
- \<xxxMaterial />
- \<xxxLight />

と、ちらほら見覚えのあるような名前が連なっています。
これらは全てthree.jsの対応するクラスをCamelケースにしたものです。
一つずつ見ていきましょう。

### \<mesh>\</mesh>

three.jsの[Mesh](https://qiita.com/sho-19202325/items/027e4c8899053d39be5f#mesh)と同じです。
引数で、Geometory, Materialを受け取る代わりに、meshタグで、Geometory要素とMeterial要素を囲ってオブジェクトを生成します。

```tsx
<mesh
  {...props}
  ref={ref}
  scale={clicked ? 1.5 : 1}
  onClick={(event) => click(!clicked)}
  onPointerOver={(event) => hover(true)}
  onPointerOut={(event) => hover(false)}>
  <boxGeometry args={[1, 1, 1]} />
  <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
```
また、onClickやonPointerOverなどを指定して、クリックイベントやマウスホバー時のイベントを扱うことができます。

three.jsに置き換えると下記のようになります。

```tsx
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshStandardMaterial( { color: hovered ? 'hotpink' : 'orange' } );
const mesh = new THREE.Mesh( geometory, material )

scene.add(mesh)
```

### \<xxxGeometory/>

three.jsの[Geometory](https://qiita.com/sho-19202325/items/027e4c8899053d39be5f#geometory%E5%BD%A2%E7%8A%B6)と同様です。
サンプルでは\<boxGeometory/>が使用されています。

```tsx
  <boxGeometry args={[1, 1, 1]} />
```
three.jsに書き換えると下記のようになります。
```tsx
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
```

### \<xxxMaterial />

three.jsの[Material](https://qiita.com/sho-19202325/items/027e4c8899053d39be5f#material%E6%9D%90%E8%B3%AA)と同様です。
サンプルでは\<meshStandarMaterial />が使用されています。

```tsx
  <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
```
three.jsに書き換えると下記のようになります。
```tsx
const material = new THREE.MeshStandardMaterial( { color: hovered ? 'hotpink' : 'orange' } );
```

### \<xxxLight />

three.jsの[Light](https://qiita.com/sho-19202325/items/027e4c8899053d39be5f#light%E3%83%A9%E3%82%A4%E3%83%88)と同様です。
サンプルでは\<ambientLight/>, \<pointLight/>が使用されています。

```tsx
<ambientLight />
<pointLight position={[10, 10, 10]} />
```

three.jsに書き換えると下記のようになります。
```tsx
const ambientLight = new THREE.AmbientLight()
const pointLight = new THREE.PointLight()
pointLight.position.set(10, 10, 10)

scene.add(ambientLight)
scene.add(pointLight)
```

:::note warn
react-three-fiberの公式ドキュメントを見ていただくとわかる通り、
これらの要素はドキュメントにほとんど載っていません。
が、原則three.jsで使用されている名前をCamelケースで置き換えたものをjsxコンポーネントとして、使えるという由の記載があります。
([公式ドキュメント該当箇所](https://docs.pmnd.rs/react-three-fiber/getting-started/your-first-scene#:~:text=The%20general%20rule%20is%20that%20Fiber%20components%20are%20available%20under%20the%20camel%2Dcase%20version%20of%20their%20name%20in%20three.js.))

使いたいコンポーネントがある場合は、three.jsのドキュメントからクラスを探し、
Camelケースに変更して、jsxコンポーネントを作って見ましょう。
すると、問題なく使えるはずです。

また、jsxコンポーネントに指定できるpropsも同様で、
three.jsのドキュメントから引数やプロパティを参照すれば、同じ引数やプロパティが指定できます。
:::

## useFrame

1フレームごとに実行される副作用を実装できるhookです。
Canvasは1フレームごとにレンダリングされる、ことを上記で書きましたが、
そのレンダリングの直前で毎回useFremeで指定しているコールバック関数を呼び出します。

```tsx
useFrame((state, delta) => {
  // 1フレームごとに実行したい処理
})
```

stateはSceneなどを含めた、Canvas内部のthree.jsオブジェクトの状態で、
deltaは各フレームごとの実行時間です。

refはuseRefを使って、mesh要素を参照しています。
こうすることで、three.jsでMeshインスタンスを扱うのと同様に、mesh要素に対して操作を行うことができます

```tsx
const ref = useRef<THREE.Mesh>(null!)

return (
<mesh
  {...props}
  ref={ref}
>
  ...
</mesh>
)
```

サンプルのuseFrameの中では下記のようになっています。

```tsx
useFrame((state, delta) => (ref.current.rotation.x += delta))
```

これは、1フレームごとにrefを経由して参照しているmesh要素のrotation.xをdeltaずつ足しています。
こうすることで、1フレームごとにrotation.xが少しずつずれていき、
結果としてオブジェクトが回転しているようなアニメーションが実装できます。
もちろん、three.js同様postionを操作して、meshを移動させることも可能です。

# おわりに

本記事では、three.jsに引き続き、公式リポジトリのサンプルをもとにreact-three-fiberの諸概念を解説していきました。
react-three-fiber固有の要素というよりかは、three.jsに関連した要素がほとんどであることがわかっていただけたと思います。
react-three-fiberを使えば、three.jsの各要素をコンポーネントとして扱うことができるので、
一度作ったmeshやアニメーションを再利用することができるので、Reactでもいい感じに3Dコンテンツの実装を行うことができます。

明日からはreact-three-fiberを使って、ブラウザ上で動作するチェスゲームを実装していく記事を投稿予定です。
ボリュームが多いので何回かに分けて投稿する予定ですが、そちらも読んでいただけると幸いです!