14
7

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.

ReactAdvent Calendar 2022

Day 21

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

Last updated at Posted at 2022-12-20

はじめに

前回はReactで3Dコンテンツを実装する - three.js入門という記事でthree.jsの概要を解説しました。
今回はreact-three-fiberの概要を解説していきます。
three.jsの時と同様、公式リポジトリのサンプルコードを実装し、各要素の解説をしていこうと思います。

React-Three-Fiberとは(再掲)

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

早速使ってみる

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

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

npx create-react-app react_three_fiber_sample --template typescript

src/下にpages/threeFiberSample.tsxを作成し、App.tsxから呼び出します。

src/App.tsx
import React from 'react';
import './App.css';
import { ThreeFiberSample } from './pages/threeFiberSample';

function App() {
  return <ThreeFiberSample />
}

export default App;
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>を配置しています。

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

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

  • htmlにheight: 100%を追記
  • bodyにheight: 100%を追記
  • dev#rootにheight: 100%を追記
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

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に置き換えると、下記のようになります。

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

使用できるプロパティの詳細はドキュメントをご参照ください。

また、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と同じです。
引数で、Geometory, Materialを受け取る代わりに、meshタグで、Geometory要素とMeterial要素を囲ってオブジェクトを生成します。

<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に置き換えると下記のようになります。

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と同様です。
サンプルでは<boxGeometory/>が使用されています。

  <boxGeometry args={[1, 1, 1]} />

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

const geometry = new THREE.BoxGeometry( 1, 1, 1 );

<xxxMaterial />

three.jsのMaterialと同様です。
サンプルでは<meshStandarMaterial />が使用されています。

  <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />

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

const material = new THREE.MeshStandardMaterial( { color: hovered ? 'hotpink' : 'orange' } );

<xxxLight />

three.jsのLightと同様です。
サンプルでは<ambientLight/>, <pointLight/>が使用されています。

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

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

const ambientLight = new THREE.AmbientLight()
const pointLight = new THREE.PointLight()
pointLight.position.set(10, 10, 10)

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

react-three-fiberの公式ドキュメントを見ていただくとわかる通り、
これらの要素はドキュメントにほとんど載っていません。
が、原則three.jsで使用されている名前をCamelケースで置き換えたものをjsxコンポーネントとして、使えるという由の記載があります。
(公式ドキュメント該当箇所)

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

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

useFrame

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

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

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

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

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

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

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

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

14
7
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
14
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?