2
1

More than 3 years have passed since last update.

【React】react-three-fibarで3D表現をする

Last updated at Posted at 2021-09-17

概要

Three.jsのReact用ライブラリ react-three-fibar を使用して、簡単な3D表現を実装します。

output(video-cutter-js.com) (1).gif

公式ドキュメント

react-three-fibarは、Three.jsのReact用パッケージで、ほぼ100%の互換性があります。 Three.jsのドキュメントはかなり充実しているので、react-three-fibarのドキュメントに載っていないことがあれば、そちらを参照しましょう。

環境

  • react - 17.0.2
  • typescript - 4.4.3
  • three - 0.132.2
  • react-three/fiber - 7.0.7
  • react-three/drei - 7.8.2

インストール

いつものように、プロジェクトフォルダを用意してcreate-react-appでプロジェクトを作成します。

cmd
npx create-react-app . --template typescript --use-npm

必要なパッケージをインストールします。

cmd
npm i three @react-three/fiber @react-three/drei
npm i -D @types/three

react-three/dreiは、react-three-fiberに対して便利なヘルパー関数を提供します。

実装

キャンバスの作成・オブジェクトの配置

import React, { VFC } from 'react';
import { DoubleSide } from 'three';
import { OrbitControls } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';

export const ThreeDemo: VFC = () => {
    return (
        <Canvas camera={{ fov: 50, position: [0, 3, 10] }}>
            <Contents />
        </Canvas>
    )
}

const Contents: VFC = () => {
    return (
        <>
            {/* control */}
            <OrbitControls />

            {/* light */}
            <directionalLight position={[5, 5, 5]} />

            {/* box 1 */}
            <mesh position={[0, 2, 0]}>
                <boxGeometry args={[1, 1, 1]} />
                <meshPhongMaterial color="blue" />
            </mesh>

            {/* box 2 */}
            <mesh position={[1, 3, 2]} scale={0.5}>
                <boxGeometry args={[1, 1, 1]} />
                <meshPhongMaterial color="red" />
            </mesh>

            {/* floor */}
            <mesh position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]}>
                <planeGeometry args={[10, 10]} />
                <meshStandardMaterial color="#E5E5E5" side={DoubleSide} />
            </mesh>
        </>
    )
}

Canvas

視野角(fov)やカメラ位置(position)を設定します。ほかに背景色やDPRなどを設定できます。

OrbitControls

このコンポーネントを追加すると、マウスでカメラの回転、移動、ズームを行うことができます。
どれかしらの機能だけを使う/使わないの設定もできます。

directionalLight

ライトです。位置や強さ、色などを設定することができます。

オブジェクト

geometryではオブジェクトの形状を、materialでは色や材質を定義します。
それをmeshタグで囲います。meshでは、オブジェクトの位置や回転を定義します。

<mesh position={[0, 2, 0]}>
    <boxGeometry args={[1, 1, 1]} />
    <meshPhongMaterial color="blue" />
</mesh>

実装例では、Contentsコンポーネントを作成してCanvasタグの中で参照しています。 これは意図的にやっていて、Canvas内のコンポーネントでしか使用できないカスタムフックがあるためです。(後ほど紹介しています)


オブジェクト間に落ちる影も簡単に実装することができます。

import React, { VFC } from 'react';
import { DoubleSide } from 'three';
import { OrbitControls } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';

export const ThreeDemo: VFC = () => {
    return (
        <Canvas camera={{ fov: 50, position: [0, 3, 10] }} shadows>
            <Contents />
        </Canvas>
    )
}

const Contents: VFC = () => {
    return (
        <>
            {/* control */}
            <OrbitControls />

            {/* light */}
            <directionalLight
                position={[5, 5, 5]}
                intensity={1} // 光の強さ
                shadow-mapSize-width={2048} // 描画精度
                shadow-mapSize-height={2048}
                castShadow
            />

            {/* box 1 */}
            <mesh position={[0, 2, 0]} castShadow receiveShadow>
                <boxGeometry args={[1, 1, 1]} />
                <meshPhongMaterial color="blue" />
            </mesh>

            {/* box 2 */}
            <mesh position={[1, 3, 2]} scale={0.5} castShadow receiveShadow>
                <boxGeometry args={[1, 1, 1]} />
                <meshPhongMaterial color="red" />
            </mesh>

            {/* floor */}
            <mesh position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
                <planeGeometry args={[10, 10]} />
                <meshStandardMaterial color="#E5E5E5" side={DoubleSide} />
            </mesh>
        </>
    )
}

影を入れるためには、まずCanvasshadowsプロパティを追加します。

次に、lightやオブジェクトに、castShadowreceiveShadowを追加します。
castShadowは、それが影を他に落とすかの設定です。receiveShadowは、それが影を受けるかの設定です。
例えば、directionalLightは、影を他に落とすけど自分自身は影を受けないので、castShadowだけ設定します。boxオブジェクトは、影を他に落とすし自分自身も影を受けるので、castShadowreceiveShadowどちらも設定しています。

このため、Box1にはBox2の影が落ちていて、Box1自身も床に影を落としています。

影の解像度が低いときは、lightのshadow-mapSize-widthshadow-mapSize-heightで調整します。


オブジェクトの回転

import React, { useRef, VFC } from 'react';
import { DoubleSide } from 'three';
import { OrbitControls } from '@react-three/drei';
import { Canvas, useFrame } from '@react-three/fiber';

export const ThreeDemo: VFC = () => {
    return (
        <Canvas camera={{ fov: 50, position: [0, 3, 10] }} shadows>
            <Contents />
        </Canvas>
    )
}

const Contents: VFC = () => {
    const boxRef = useRef<any>(null)

    useFrame(({ clock }) => {
        const a = clock.getElapsedTime()
        boxRef.current.rotation.x = a * 1
        boxRef.current.rotation.y = a * 1
        boxRef.current.rotation.z = a * 0
    })

    return (
        <>
            {/* control */}
            ・・・

            {/* light */}
            ・・・

            {/* box 1 */}
            <mesh ref={boxRef} position={[0, 2, 0]} castShadow receiveShadow>
                <boxGeometry args={[1, 1, 1]} />
                <meshPhongMaterial color="blue" />
            </mesh>

            {/* box 2 */}
            <mesh position={[1, 3, 2]} scale={0.5} castShadow receiveShadow>
                <boxGeometry args={[1, 1, 1]} />
                <meshPhongMaterial color="red" />
            </mesh>

            {/* floor */}
            ・・・
        </>
    )
}

フレームアニメーションは、useFrameを使うことで実装できます。
Box1のmeshにboxRefを指定することで、Box1が回転します。

useFrameは、Canvas内のコンポーネントでのみ使用できます。


ヘルパーオブジェクト

Three.jsには、グリッドやライトを視覚化するためのヘルパーオブジェクトが用意されています。

import React, { useRef, VFC } from 'react';
import { DirectionalLightHelper, DoubleSide } from 'three';
import { OrbitControls, useHelper } from '@react-three/drei';
import { Canvas, useFrame } from '@react-three/fiber';

export const ThreeDemo: VFC = () => {
    return (
        <Canvas camera={{ fov: 50, position: [0, 3, 10] }} shadows>
            <Contents />
        </Canvas>
    )
}

const Contents: VFC = () => {
    const lightRef = useRef()
    useHelper(lightRef, DirectionalLightHelper)

    ・・・

    return (
        <>
            {/* control */}
            ・・・

            {/* light */}
            <directionalLight
                ref={lightRef}
                position={[5, 5, 5]}
                intensity={1} // 光の強さ
                shadow-mapSize-width={2048} // 描画精度
                shadow-mapSize-height={2048}
                castShadow
            />

            {/* box 1 */}
            ・・・

            {/* box 2 */}
            ・・・

            {/* floor */}
            ・・・

            {/* grid */}
            <gridHelper position={[0, 0.01, 0]} args={[10, 10, 'red', '#4C4C4C']} />
        </>
    )
}

DirectionalLightHelper

lightRefdirectionalLightのプロパティに追加することで、ライトを可視化します。
ヘルパーの生成には、useHelperを使用します。

useHelperは、Canvas内のコンポーネントでのみ使用できます。

gridHelper

argsでは、[大きさ, 分割数, 真ん中の線の色, 全体の線の色]を指定しています。

まとめ

react-three-fiberを使うと、簡単に動く3D表現ができてとても楽しいです。
ですが、Three.jsのラッパーパッケージなので、Three.jsでの情報はあってもreact-three-fiberの情報がなかなかないです...:rolling_eyes:

実装例は、CodeSandBoxを参照するといいと思います。

2
1
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
2
1