LoginSignup
1
2

More than 1 year has passed since last update.

reactを使った場合に3Dの表現ってどうやるんだろ?って気になり調べてみたらreact-three-fiberというThree.jsreact版みたいな良さげなライブラリがあったので触ってみたいと思います。

ドキュメントのExamplesを初めて見た時は結構テンションが上がりました。笑

また、エコシステムもあるのでそれらを組み合わせつつ、react-three-fiberに慣れればThree.jsで書くより開発速度があがるかもしれません。(Three.jsやWebGLの知識は必要なので自分のスキルに合わせて勉強していければと思います。。。)

開発環境を準備する

ビルドツールに関してはお好みのものをご使用いただければと思いますので割愛させていただきます。
※Next.jsの場合はreact-three-fiberの公式ドキュメントも参考にするといいと思います。

react-three-fiberをインストール

本題のreact-three-fiberThree.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でカラーを変更できるように試してみる。

App.tsx
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のマテリアルをシェダーにてカラー変更を行えるよう切り替えてみます。

App.tsx
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;
vertex.glsl
void main() {
  vec4 worldPosition = modelMatrix * vec4(position, 1.0);
  vec4 mvPosition = viewMatrix * worldPosition;
  gl_Position = projectionMatrix * mvPosition;
}
fragment.glsl
uniform vec3 uColor;

void main() {
  gl_FragColor = vec4(uColor, 1.0);
}

問題なさそう。。。
ざっくりとした感じではありましたが以上となります。

まとめ

もう少し触ってみないとわからないことも多いですが、なんと言っても楽しいにつきます。

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