概要
Three.jsのReact用ライブラリ react-three-fibar を使用して、簡単な3D表現を実装します。
公式ドキュメント
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
でプロジェクトを作成します。
npx create-react-app . --template typescript --use-npm
必要なパッケージをインストールします。
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>
</>
)
}
影を入れるためには、まずCanvasにshadows
プロパティを追加します。
次に、lightやオブジェクトに、castShadow
・receiveShadow
を追加します。
castShadow
は、それが影を他に落とすかの設定です。receiveShadow
は、それが影を受けるかの設定です。
例えば、directionalLightは、影を他に落とすけど自分自身は影を受けないので、castShadow
だけ設定します。boxオブジェクトは、影を他に落とすし自分自身も影を受けるので、castShadow
・receiveShadow
どちらも設定しています。
このため、Box1にはBox2の影が落ちていて、Box1自身も床に影を落としています。
影の解像度が低いときは、lightのshadow-mapSize-width
・shadow-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
lightRef
をdirectionalLight
のプロパティに追加することで、ライトを可視化します。
ヘルパーの生成には、useHelperを使用します。
useHelperは、Canvas内のコンポーネントでのみ使用できます。
gridHelper
args
では、[大きさ, 分割数, 真ん中の線の色, 全体の線の色]
を指定しています。
まとめ
react-three-fiberを使うと、簡単に動く3D表現ができてとても楽しいです。
ですが、Three.jsのラッパーパッケージなので、Three.jsでの情報はあってもreact-three-fiberの情報がなかなかないです...
実装例は、CodeSandBoxを参照するといいと思います。