react
を使った場合に3Dの表現ってどうやるんだろ?って気になり調べてみたらreact-three-fiberというThree.js
のreact
版みたいな良さげなライブラリがあったので触ってみたいと思います。
ドキュメントのExamplesを初めて見た時は結構テンションが上がりました。笑
また、エコシステムもあるのでそれらを組み合わせつつ、react-three-fiber
に慣れればThree.js
で書くより開発速度があがるかもしれません。(Three.jsやWebGLの知識は必要なので自分のスキルに合わせて勉強していければと思います。。。)
開発環境を準備する
ビルドツールに関してはお好みのものをご使用いただければと思いますので割愛させていただきます。
※Next.jsの場合はreact-three-fiberの公式ドキュメントも参考にするといいと思います。
react-three-fiberをインストール
本題のreact-three-fiber
とThree.js
をインストールします。
npm i @react-three/fiber three
TypeScript
ご使用の場合はThree.js
の型定義ファイルもインストールします。
npm i @react-three/fiber three @types/three
react-three-fiberのエコシステムをインストール
- r3f-perf - パフォーマンスモニタ FPSなどの確認
- leva - dat.gui的なツール
- @react-three/drei - react-three-fiberのヘルパー
npm i r3f-perf leva @react-three/drei
動かしてみる
Three.jsに用意されているBoxGeometryの形状とlevaでカラーを変更できるように試してみる。
import { OrbitControls } from '@react-three/drei';
import { Canvas, useFrame } from '@react-three/fiber';
import { useControls } from 'leva';
import { Perf } from 'r3f-perf';
import { useEffect, useRef, useState, VFC } from 'react';
import type { Mesh } from 'three';
type BoxProps = {
color?: ColorRepresentation;
};
const Box: VFC<BoxProps> = ({ color = '#0066ff' }) => {
const mesh = useRef<Mesh>();
useFrame(() => {
if (mesh.current) {
mesh.current.rotation.x = mesh.current.rotation.y += 0.01;
}
});
return (
<mesh ref={mesh}>
<boxBufferGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={color} />
</mesh>
);
};
function App() {
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const [dpr, setDpr] = useState(0);
const { color } = useControls({ color: '#ff0000' });
useEffect(() => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
setDpr(Math.min(window.devicePixelRatio, 2));
window.addEventListener('resize', () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
setDpr(Math.min(window.devicePixelRatio, 2));
});
}, []);
const aspect = width / height;
return (
<Canvas
dpr={dpr}
camera={{ aspect, near: 0.1, far: 100, position: [0, 0, 4] }}>
<ambientLight />
<pointLight position={[10, 10, 0]} />
<Box color={color} />
<OrbitControls />
<Perf position={'bottom-right'} showGraph={false} />
</Canvas>
);
}
export default App;
シェーダーも動くか試してみる
シェーダーも問題なく動くか試してみます。
先ほどのApp.tsx
のBoxのマテリアルをシェダーにてカラー変更を行えるよう切り替えてみます。
import { OrbitControls } from '@react-three/drei';
import { Canvas, useFrame } from '@react-three/fiber';
import { useControls } from 'leva';
import { Perf } from 'r3f-perf';
import { useEffect, useMemo, useRef, useState, VFC } from 'react';
import fragmentShader from 'shaders/fragment.glsl?raw';
import vertexShader from 'shaders/vertex.glsl?raw';
import { Color, ColorRepresentation, Mesh, ShaderMaterial } from 'three';
type BoxProps = {
color?: ColorRepresentation;
};
const Box: VFC<BoxProps> = ({ color = '#0066ff' }) => {
const mesh = useRef<Mesh>();
const params = useMemo(
() => ({
uniforms: {
uColor: { value: new Color(color) },
},
fragmentShader,
vertexShader,
}),
[]
);
useFrame(() => {
if (mesh.current) {
mesh.current.rotation.x = mesh.current.rotation.y += 0.01;
(mesh.current.material as ShaderMaterial).uniforms.uColor.value =
new Color(color);
}
});
return (
<mesh ref={mesh}>
<boxBufferGeometry args={[1, 1, 1]} />
<shaderMaterial {...params} />
</mesh>
);
};
function App() {
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const [dpr, setDpr] = useState(0);
const { color } = useControls({ color: '#ff0000' });
useEffect(() => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
setDpr(Math.min(window.devicePixelRatio, 2));
window.addEventListener('resize', () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
setDpr(Math.min(window.devicePixelRatio, 2));
});
}, []);
const aspect = width / height;
return (
<Canvas
dpr={dpr}
camera={{ aspect, near: 0.1, far: 100, position: [0, 0, 4] }}>
<ambientLight />
<pointLight position={[10, 10, 0]} />
<Box color={color} />
<OrbitControls />
<Perf position={'bottom-right'} showGraph={false} />
</Canvas>
);
}
export default App;
void main() {
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
vec4 mvPosition = viewMatrix * worldPosition;
gl_Position = projectionMatrix * mvPosition;
}
uniform vec3 uColor;
void main() {
gl_FragColor = vec4(uColor, 1.0);
}
問題なさそう。。。
ざっくりとした感じではありましたが以上となります。
まとめ
もう少し触ってみないとわからないことも多いですが、なんと言っても楽しいにつきます。