8
5

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.

React-Three-Fiverを用いてReactで3D表現する

Last updated at Posted at 2023-07-02

React-three-fiber(この記事では、r3fと呼称する。)とは、ReactでThree.jsを扱うためのラッパーライブラリで、Reactの宣言的な記述に合わせてWebGLシーンを構築することができます。

対象者

  • r3fを使ったことがない方
  • three.jsの知識をお持ちの方
  • Reactで3Dのサイトを作りたい方

インストール

$ npm install three @react-three/fiber

また、TypeScriptをお使いの場合は下記に記載しているモジュールもインストールします。

$ npm install @types/three

Canvas

r3fを使う時に必ずラップする。

import React from 'react'
import { Canvas } from '@react-three/fiber'

const App = () => (
  <Canvas>
    ...
  </Canvas>
)

export default App;

Canvasコンポーネントではcamerarenderersceneを指定した事になります。

cameraの位置はデフォルトでは{ fov: 75, near: 0.1, far: 1000, position: [0, 0, 5] }
もし、camera位置を変更したい時には下記のようにpropsで指定します。

import React from 'react'
import { Canvas } from '@react-three/fiber'

const App = () => (
  <Canvas camera={ { fov: カメラの錐台の垂直視野, near: 平面近くのカメラ錐台, far: カメラの錐台の遠方平面, position: [x座標, y座標, z座標] } }>
    ...
  </Canvas>
)

export default App;

Mesh

いよいよ物体を表示するのですが、物体を表示させる為にはメッシュと呼ばれるものが必要でした。
r3fでは、meshコンポーネントがあります。<mesh />

一方で、メッシュを作成には形状(ジオメトリ)材質(マテリアル) が必要でした。
なので、最終的には下記のようなコードになるはずです。

import React from 'react'
import { Canvas } from '@react-three/fiber'

const App = () => (
  <Canvas>
    <mesh>
      <Geometry />
      <Material />
    </mesh>
  </Canvas>
)

export default App;

形状(ジオメトリ)

基本はthree.jsのインスタンスメソッドがコンポーネントになったと思ってください。
例えば、直方体を指定したい時には下記のインスタンスメソッドをメソッドを作成していました。

new THREE.BoxGeometry(1, 1, 1)

これが、r3fだと下記のようになります。

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

このように、r3fではインスタンスメソッドがそのままコンポーネントになっています。

材質(マテリアル)

Materialの場合も同様に、three.jsのインスタンスメソッドがコンポーネントになっています。
例えば、MeshBasicMaterialを使いたい場合、three.jsでは下記のように指定していました。

new THREE.MeshBasicMaterial({ color: "hotpink" })

これが、r3fだと下記のようになります。

<meshBasicMaterial color={"hotpink"} />

光源(ライト)

使用するMaterialによっては光源(ライト)を照らしてあげないといけない場合があります。 ここでは、光源(ライト)の照らし方を紹介します。
因みに余談なのですが、光源(ライト)を照らさなくても大丈夫なMaterialを紹介します。

  • MeshBasicMaterial
  • MeshNormalMaterial

ここでは、平行光源(DirectionalLight)の当て方を紹介します。

<directionalLight color={"white"} intensity={0.5} position={[0, 0, 0]} />
// color : 光の色
// intensity : 光の強さ
// position : 平行光源のポジショニング

アニメーション

JavaScriptでアニメーションをさせるには、時間経過で関数を呼び続ける必要があります。そのためには、requestAnimationFrame()というグローバルメソッドを使用します。

r3fではuseFrame()というメソッドが用意されています。
では、サンプルコードを交えてuseFrame()を使っていきます。

App.tsx
import { Canvas, useFrame } from "@react-three/fiber";
import React, { useRef } from "react";
import { Mesh } from "three";

function App() {
  const ref = useRef({} as Mesh);
  useFrame(() => (ref.current.rotation.x += 0.01));
  return (
    <>
      <Canvas
        camera={{ fov: 70, near: 0.1, far: 2000 }}
        style={{ width: "100vw", height: "100vh" }}
      >
        <color args={["#5bbee5"]} attach={"background"} />
        <ambientLight intensity={0.5} />
        <directionalLight position={[10, 10, 20]} />
        <mesh ref={ref} position={[-2, 0, 0]}>
          <boxGeometry args={[1, 1, 1]} />
          <meshPhongMaterial color={"hotpink"} />
        </mesh>
        <mesh ref={ref} position={[0, 0, 0]}>
          <boxGeometry args={[1, 1, 1]} />
          <meshPhongMaterial color={"hotpink"} />
        </mesh>
        <mesh ref={ref} position={[2, 0, 0]}>
          <boxGeometry args={[1, 1, 1]} />
          <meshPhongMaterial color={"hotpink"} />
        </mesh>
      </Canvas>
    </>
  );
}

export default App;

3つのBoxGeometryが縦方向に少しずつ回転していくアニメーションを行なっていく処理を記述していきましたが、下記のようなエラーが出ました。

R3F: hooks can only be used within the Canvas component

useFrame()<Canvas />の中でしか使えないよ〜と言ってきます。
なので、以下のようにメッシュを子コンポーネントで管理します。

App.tsx
import { Canvas } from "@react-three/fiber";
import Box from "./compornents/Box";

function App() {
  return (
    <>
      <Canvas
        camera={{ fov: 70, near: 0.1, far: 2000 }}
        style={{ width: "100vw", height: "100vh" }}
      >
        <color args={["#5bbee5"]} attach={"background"} />
        <ambientLight intensity={0.5} />
        <directionalLight position={[10, 10, 20]} />
        <Box position={[-2.4, 0, 0]} />
        <Box position={[0, 0, 0]} />
        <Box position={[2.4, 0, 0]} />
      </Canvas>
    </>
  );
}

export default App;
Box.tsx
import { useFrame } from "@react-three/fiber";
import React, { useRef } from "react";
import { Mesh } from "three";

const Box = (props: JSX.IntrinsicElements["mesh"]) => {
  const ref = useRef({} as Mesh);
  useFrame(() => (ref.current.rotation.x += 0.01));
  return (
    <mesh ref={ref} {...props}>
      <boxGeometry args={[1, 1, 1]} />
      <meshPhongMaterial color={"hotpink"} />
    </mesh>
  );
};

export default Box;

チュートリアルっぽく仕上げる

最後に公式にあるチュートリアルっぽく、BoxGeometryをクリックすると拡大・縮小したり、hoverしたら色が変わる処理を追加し今回のブログを締めようと思います。

拡大・縮小

まず最初にクリックしたら拡大・縮小する処理を実装します。実装するには拡大・縮小を管理するstateが必要になるので用意します。

 const [clicked, setClicked] = useState<boolean>(false);

useStateの値がfalseなら縮小・trueなら拡大してあげます。
また、大きさを変化させる時に用いられるプロパティはscaleです。

<mesh
  onClick={() => setClicked(!clicked)}
  scale={clicked ? 2 : 1}
>

hoverしたら色が変わる

クリックしたら拡大・縮小する処理と同様に色を管理するstateを用意します。

const [hovered, setHovered] = useState<boolean>(false);

useStateの値がfalseならhotpink・trueならorangeに変わるという処理を記述していきます。

以下のプロパティを付けることでマウスがhoverした時の処理、マウスが離れた時の処理を実装することができます。

<mesh
  onPointerOver={() => setHovered(true)} // マウスがhoverした時の処理
  onPointerOut={() => setHovered(false)} // マウスが離れた時の処理
>
  ...
  <meshPhongMaterial color={hovered ? "orange" : "hotpink"} />
</mesh>

おわりに

いかがだったでしょうか?僕自身も今r3fにハマっていてこれからどんどんキャッチアップしていきたいなと思っていて、次回まだ未定なのですが@react-three/dreiを用いて色々やる記事を書こうかな?と思っています。

今回使用した、@react-three/fiber@react-three/dreiを組み合わせる事により、@react-three/fiberだけではできなかった3D表現ができるようになるのでお楽しみに。ということで最後まで目を通していただきありがとうございました。

8
5
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
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?