5
0

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.

[Three.js] R3FがOffScreenに対応したらしいので使ってみる

Last updated at Posted at 2023-05-09

はじめに

Paul氏とR3Fコミュニティに感謝します。
R3F(ReactThreeFiber)はThree.jsをReactで簡単に使えるようにしたライブラリです。
R3Fのコミュニティは、毎日のように面白いプロジェクトやリソースが公開されます。

その中で、今回は先月OffscreenCanvasに対応されたということなので早速使ってみます。
Offscreenとは、WebGLでのレンダリングをメインスレッド外、つまりはWebWorkerで実行し、
メインスレッドの処理に依存せずに描画できるため、パフォーマンスの向上が狙えます。
このライブラリのすごいところは、WebWorkerを意識することなく、実装できることです。

サンプルの実装

# Create: ReactApp
npx create-react-app oscreen --template typescript
# install
npm install three @types/three @react-three/fiber @react-three/drei @react-three/offscreen
# 実行
npm start

ディレクトリ構成

oscreen
└ src
   ┝ App.tsx
   ┝ Scene.tsx
   └ worker.jsx

ソースコード

App.tsx

App.tsx
import React, { lazy } from 'react';
import { Canvas } from "@react-three/offscreen"

const Scene = lazy(() => import("./Scene"))

const worker = new Worker(new URL("./worker.jsx", import.meta.url), { type: "module" })

function App() {
  return (
    <div style={{ height: "100vh" }}>
      <Canvas 
        dpr={[1, 1.5]}
        camera={{ position: [0, 0, 10], fov: 25 }} 
        worker={worker} 
        fallback={<Scene />}
      />
    </div>
  );
}

export default App;

Scene.tsx

Scene.tsx
import React, { useRef, useState } from 'react'
import { useFrame } from '@react-three/fiber'
import { useGLTF, Center, ContactShadows, Environment, CameraControls } from '@react-three/drei'

const Scene = () => {
    const mesh = useRef<any>()
    const { nodes, materials } = useGLTF('/pmndrs.glb') as any;
    const [hovered, setHover] = useState(false)
    const [active, setActive] = useState(false)
    const color = hovered ? 'hotpink' : 'orange'
    useFrame((state, delta) => {
      mesh.current.rotation.x += delta / 2
      mesh.current.rotation.y += delta / 2
    })
    return (
      <>
        <Center ref={mesh}>
          <mesh
            geometry={nodes.cube.geometry}
            material={materials.base}
            material-color={color}
            scale={active ? 0.3 : 0.25}
            onClick={(e) => (e.stopPropagation(), setActive(!active))}
            onPointerOver={(e) => (e.stopPropagation(), setHover(true))}
            onPointerOut={(e) => setHover(false)}
          />
        </Center>
        <ContactShadows color={color} position={[0, -1.5, 0]} blur={3} opacity={0.75} />
        <ambientLight />
        <pointLight position={[10, 10, 5]} />
        <Environment preset="city">

        </Environment>
        <CameraControls minPolarAngle={Math.PI / 2} maxPolarAngle={Math.PI / 2} />
      </>
    )
}

export default Scene;

worker.jsx

worker.jsx
import React from 'react'
import { render } from '@react-three/offscreen'
import Scene from './Scene'

render(<Scene />)

実行結果

oscreen1.gif

ReactのStateやそのほかのdreiリソース、
fiberのuseFrameなどを利用できることを確認しました。

その他のpmndrsのリソースが使えるかどうか

  • PostProcessing/Shaderは使えるか?
  • WebXRは使えるか?
    これらを確認していきます

PostProcessing/Shaderは使えるか?

Scene.tsx

Scene.tsx
import React, { useRef, useState, useMemo, useEffect } from 'react'
import { useFrame, applyProps, useLoader } from '@react-three/fiber'
import { useGLTF, Center, ContactShadows, Environment, Float, OrbitControls, Lightformer, MeshReflectorMaterial } from '@react-three/drei'
import { EffectComposer, SSR, Bloom, LUT } from "@react-three/postprocessing";
import { Mesh, MeshPhysicalMaterial, Texture } from 'three';
import { LUTCubeLoader } from 'postprocessing'

const Scene = () => {
  return (
    <>
      <color attach="background" args={['#15151a']} />
      <Lambo rotation={[0, Math.PI / 1.5, 0]} scale={0.015} />
      <hemisphereLight intensity={0.5} />
      <ContactShadows resolution={1024} frames={1} position={[0, -1.16, 0]} scale={15} blur={0.5} opacity={1} far={20} />
      <mesh scale={4} position={[3, -1.161, -1.5]} rotation={[-Math.PI / 2, 0, Math.PI / 2.5]}>
        <ringGeometry args={[0.9, 1, 4, 1]} />
        <meshStandardMaterial color="red" roughness={0.75} />
      </mesh>
      <mesh scale={4} position={[-3, -1.161, -1]} rotation={[-Math.PI / 2, 0, Math.PI / 2.5]}>
        <ringGeometry args={[0.9, 1, 3, 1]} />
        <meshStandardMaterial color="gold" roughness={0.75} />
      </mesh>
      <mesh rotation={[-Math.PI/2, 0, 0]} position={[0, -1.162, 0]} >
        <planeBufferGeometry args={[32, 32]}/>
        <MeshReflectorMaterial mirror={1} resolution={1024} />
      </mesh>
      <Environment resolution={512} preset='city' frames={Infinity}>
        {/* Ceiling */}
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, -9]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, -6]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, -3]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 0]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 3]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 6]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 9]} scale={[10, 1, 1]} />
        {/* Sides */}
        <Lightformer intensity={2} rotation-y={Math.PI / 2} position={[-50, 2, 0]} scale={[100, 2, 1]} />
        <Lightformer intensity={2} rotation-y={-Math.PI / 2} position={[50, 2, 0]} scale={[100, 2, 1]} />
        <Lightformer rotation-y={-Math.PI / 2} position={[10, 1, 0]} scale={[20, 1, 1]} />
        {/* Accent (gold) */}
        <Float speed={5} floatIntensity={2} rotationIntensity={2}>
          <Lightformer form="ring" color="gold" intensity={1} scale={10} position={[-15, 4, -18]} target={[0, 0, 0]} />
        </Float>
        {/* Key */}
        <Lightformer form="ring" color="gold" intensity={10} scale={2} position={[10, 5, 10]} onUpdate={(self) => self.lookAt(0, 0, 0)} />
      </Environment>
      <OrbitControls enablePan={false} minPolarAngle={Math.PI / 2.2} maxPolarAngle={Math.PI / 2.2} autoRotate autoRotateSpeed={0.4} />
      <PostProcess />
    </>
  )
}

const Lambo = (props: any) => {
  const { scene, nodes, materials } = useGLTF('/lambo.glb') as any;
  useMemo(() => {
    // ⬇⬇⬇ All this is probably better fixed in Blender ...
    Object.values(nodes).forEach((node) => {
      if (node instanceof Mesh && node.isMesh) {
        // Fix glas, normals look messed up in the original, most likely deformed meshes bc of compression :/
        if (node.name.startsWith('glass')) node.geometry.computeVertexNormals()
        // Fix logo, too dark
        if (node.name === 'silver_001_BreakDiscs_0') node.material = applyProps(materials.BreakDiscs.clone(), { color: '#ddd' })
      }
    })
    // Fix windows, they have to be inset some more
    nodes['glass_003'].scale.setScalar(2.7)
    // Fix inner frame, too light
    applyProps(materials.FrameBlack, { metalness: 0.75, roughness: 0, color: 'black' })
    // Wheels, change color from chrome to black matte
    applyProps(materials.Chrome, { metalness: 1, roughness: 0, color: '#333' })
    applyProps(materials.BreakDiscs, { metalness: 0.2, roughness: 0.2, color: '#555' })
    applyProps(materials.TiresGum, { metalness: 0, roughness: 0.4, color: '#181818' })
    applyProps(materials.GreyElements, { metalness: 0, color: '#292929' })
    // Make front and tail LEDs emit light
    applyProps(materials.emitbrake, { emissiveIntensity: 3, toneMapped: false })
    applyProps(materials.LightsFrontLed, { emissiveIntensity: 3, toneMapped: false })
    // Paint, from yellow to black
    nodes.yellow_WhiteCar_0.material = new MeshPhysicalMaterial({
      roughness: 0.3,
      metalness: 0.05,
      color: '#820000',
      envMapIntensity: 0.75,
      clearcoatRoughness: 0,
      clearcoat: 1
    })
  }, [nodes, materials])
  return <primitive object={scene} {...props} />
}

const PostProcess = () => {
  const [texture, setTexture] = useState<Texture>();
  useEffect(() => {
    const loader = new LUTCubeLoader();
    loader.load('/F-6800-STD.cube', (loadedTexture: Texture) => {
      setTexture(loadedTexture);
    });
  }, []);
  let lut: JSX.Element = (<></>);
  if (texture){
    lut = <LUT lut={texture} />
  }
  return (
    <EffectComposer disableNormalPass>
      <Bloom luminanceThreshold={0.2} mipmapBlur luminanceSmoothing={0} intensity={1.45} />
      {lut}
    </EffectComposer>
  )
}

export default Scene;

実行結果

oscreen2.gif

ちゃんと使えそうでした。

WebXRは使えるか

Scene.tsx
import React, { useRef, useState, useMemo, useEffect } from 'react'
import { useFrame, applyProps, useLoader } from '@react-three/fiber'
import { useGLTF, useFont, Text3D, ContactShadows, Environment, Float, OrbitControls, Lightformer, MeshReflectorMaterial } from '@react-three/drei'
import { EffectComposer, Bloom, LUT } from "@react-three/postprocessing";
import { Mesh, MeshPhysicalMaterial, Texture } from 'three';
import { LUTCubeLoader } from 'postprocessing';
import { XR, startSession } from "@react-three/xr";


const Scene = () => {
  const font = useFont("/MPLUS.json");
  const fontdata = font.data;
  const startXR = () => {
    startSession("immersive-vr", undefined);
  }
  return (
    <XR>
      <Text3D font={fontdata} rotation={[0, Math.PI / 1.5, 0]} position={[-1.25, 3, 3.5]} onClick={startXR}>
        XRで実行
      </Text3D>
      <color attach="background" args={['#15151a']} />
      <Lambo rotation={[0, Math.PI / 1.5, 0]} scale={0.015} />
      <hemisphereLight intensity={0.5} />
      <ContactShadows resolution={1024} frames={1} position={[0, -1.16, 0]} scale={15} blur={0.5} opacity={1} far={20} />
      <mesh scale={4} position={[3, -1.161, -1.5]} rotation={[-Math.PI / 2, 0, Math.PI / 2.5]}>
        <ringGeometry args={[0.9, 1, 4, 1]} />
        <meshStandardMaterial color="red" roughness={0.75} />
      </mesh>
      <mesh scale={4} position={[-3, -1.161, -1]} rotation={[-Math.PI / 2, 0, Math.PI / 2.5]}>
        <ringGeometry args={[0.9, 1, 3, 1]} />
        <meshStandardMaterial color="gold" roughness={0.75} />
      </mesh>
      <mesh rotation={[-Math.PI/2, 0, 0]} position={[0, -1.162, 0]} >
        <planeBufferGeometry args={[32, 32]}/>
        <MeshReflectorMaterial mirror={1} resolution={1024} />
      </mesh>
      <Environment resolution={512} preset='city' frames={Infinity}>
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, -9]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, -6]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, -3]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 0]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 3]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 6]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 9]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-y={Math.PI / 2} position={[-50, 2, 0]} scale={[100, 2, 1]} />
        <Lightformer intensity={2} rotation-y={-Math.PI / 2} position={[50, 2, 0]} scale={[100, 2, 1]} />
        <Lightformer rotation-y={-Math.PI / 2} position={[10, 1, 0]} scale={[20, 1, 1]} />
        <Float speed={5} floatIntensity={2} rotationIntensity={2}>
          <Lightformer form="ring" color="gold" intensity={1} scale={10} position={[-15, 4, -18]} target={[0, 0, 0]} />
        </Float>
        <Lightformer form="ring" color="gold" intensity={10} scale={2} position={[10, 5, 10]} onUpdate={(self) => self.lookAt(0, 0, 0)} />
      </Environment>
      <OrbitControls enablePan={false} minPolarAngle={Math.PI / 2.2} maxPolarAngle={Math.PI / 2.2} autoRotate autoRotateSpeed={0.4} />
      <PostProcess />
    </XR>
  )
}

const Lambo = (props: any) => {
  const { scene, nodes, materials } = useGLTF('/lambo.glb') as any;
  useMemo(() => {
    // ⬇⬇⬇ All this is probably better fixed in Blender ...
    Object.values(nodes).forEach((node) => {
      if (node instanceof Mesh && node.isMesh) {
        // Fix glas, normals look messed up in the original, most likely deformed meshes bc of compression :/
        if (node.name.startsWith('glass')) node.geometry.computeVertexNormals()
        // Fix logo, too dark
        if (node.name === 'silver_001_BreakDiscs_0') node.material = applyProps(materials.BreakDiscs.clone(), { color: '#ddd' })
      }
    })
    // Fix windows, they have to be inset some more
    nodes['glass_003'].scale.setScalar(2.7)
    // Fix inner frame, too light
    applyProps(materials.FrameBlack, { metalness: 0.75, roughness: 0, color: 'black' })
    // Wheels, change color from chrome to black matte
    applyProps(materials.Chrome, { metalness: 1, roughness: 0, color: '#333' })
    applyProps(materials.BreakDiscs, { metalness: 0.2, roughness: 0.2, color: '#555' })
    applyProps(materials.TiresGum, { metalness: 0, roughness: 0.4, color: '#181818' })
    applyProps(materials.GreyElements, { metalness: 0, color: '#292929' })
    // Make front and tail LEDs emit light
    applyProps(materials.emitbrake, { emissiveIntensity: 3, toneMapped: false })
    applyProps(materials.LightsFrontLed, { emissiveIntensity: 3, toneMapped: false })
    // Paint, from yellow to black
    nodes.yellow_WhiteCar_0.material = new MeshPhysicalMaterial({
      roughness: 0.3,
      metalness: 0.05,
      color: '#820000',
      envMapIntensity: 0.75,
      clearcoatRoughness: 0,
      clearcoat: 1
    })
  }, [nodes, materials])
  return <primitive object={scene} {...props} />
}

const PostProcess = () => {
  const [texture, setTexture] = useState<Texture>();
  useEffect(() => {
    const loader = new LUTCubeLoader();
    loader.load('/F-6800-STD.cube', (loadedTexture: Texture) => {
      setTexture(loadedTexture);
    });
  }, []);
  let lut: JSX.Element = (<></>);
  if (texture){
    lut = <LUT lut={texture} />
  }
  return (
    <EffectComposer disableNormalPass>
      <Bloom luminanceThreshold={0.2} mipmapBlur luminanceSmoothing={0} intensity={1.45} />
      {lut}
    </EffectComposer>
  )
}

export default Scene;

重くて表示はできないようですが、XRも実行可能のようです。

![画像1.png](https://qiita-image-store.s3.ap-northeast-
1.amazonaws.com/0/493888/2844b8f4-3c0d-8f98-cecb-852a544b74e2.png)

最後にパフォーマンス比較

動くBoxをさらに500個おいて比較しました。
通常:30~45FPS
OScreen:40~50FPS

単純な比較のみでも効果があった。
この上に通常のReactのHTMLレンダリング等でプロジェクトを作りこむと、この差は大きくなるんだと思います。

oscreen4.gif

比較ソースコード

App.tsx(Normal)
import React, { lazy } from 'react';
// import { Canvas } from "@react-three/offscreen"
import { Canvas } from "@react-three/fiber";

const Scene = lazy(() => import("./Scene"))

const worker = new Worker(new URL("./worker.jsx", import.meta.url), { type: "module" })

function App() {
  return (
    <div style={{ height: "100vh" }}>
      {/* <Canvas 
        gl={{ logarithmicDepthBuffer: true, antialias: false }}
        dpr={[1, 1.5]}
        camera={{ position: [0, 0, -16], fov: 25 }} 
        worker={worker} 
        fallback={<Scene />}
      /> */}
      <Canvas
        gl={{ logarithmicDepthBuffer: true, antialias: false }}
        dpr={[1, 1.5]}
        camera={{ position: [0, 0, -16], fov: 25 }} 
      >
        <Scene/>
      </Canvas>
    </div>
  );
}
App.tsx(OffScreen)
import React, { lazy } from 'react';
import { Canvas } from "@react-three/offscreen"
// import { Canvas } from "@react-three/fiber";

const Scene = lazy(() => import("./Scene"))

const worker = new Worker(new URL("./worker.jsx", import.meta.url), { type: "module" })

function App() {
  return (
    <div style={{ height: "100vh" }}>
      <Canvas 
        gl={{ logarithmicDepthBuffer: true, antialias: false }}
        dpr={[1, 1.5]}
        camera={{ position: [0, 0, -16], fov: 25 }} 
        worker={worker} 
        fallback={<Scene />}
      />
      {/* <Canvas
        gl={{ logarithmicDepthBuffer: true, antialias: false }}
        dpr={[1, 1.5]}
        camera={{ position: [0, 0, -16], fov: 25 }} 
      >
        <Scene/>
      </Canvas> */}
    </div>
  );
}

export default App;

Scene.tsx
import React, { useRef, useState, useMemo, useEffect, Suspense } from 'react'
import { useFrame, applyProps, useLoader } from '@react-three/fiber'
import { useGLTF, useFont, Text3D, ContactShadows, Environment, Float, OrbitControls, Lightformer, MeshReflectorMaterial } from '@react-three/drei'
import { EffectComposer, Bloom, LUT } from "@react-three/postprocessing";
import { Color, Mesh, MeshPhysicalMaterial, Texture, Vector3 } from 'three';
import { LUTCubeLoader } from 'postprocessing';
import { XR, startSession } from "@react-three/xr";
import { Perf } from "r3f-perf";

const Scene = () => {
  const font = useFont("/MPLUS.json");
  const fontdata = font.data;
  const startXR = () => {
    startSession("immersive-vr", undefined);
  }
  return (
    <XR>
      <Text3D font={fontdata} rotation={[0, Math.PI / 1.5, 0]} position={[0, 2.5, 3.5]} onClick={startXR}>
        ピッカピカ
      </Text3D>
      <Text3D font={fontdata} size={0.45} rotation={[0, Math.PI / 1.5, 0]} position={[0, 1.5, 1.75]}>
        R3Fは楽しい
      </Text3D>
      <color attach="background" args={['#15151a']} />
      <Lambo rotation={[0, Math.PI / 1.5, 0]} scale={0.015} />
      <hemisphereLight intensity={0.5} />
      <ContactShadows resolution={1024} frames={1} position={[0, -1.16, 0]} scale={15} blur={0.5} opacity={1} far={20} />
      <mesh scale={4} position={[3, -1.161, -1.5]} rotation={[-Math.PI / 2, 0, Math.PI / 2.5]}>
        <ringGeometry args={[0.9, 1, 4, 1]} />
        <meshStandardMaterial color="red" roughness={0.75} />
      </mesh>
      <mesh scale={4} position={[-3, -1.161, -1]} rotation={[-Math.PI / 2, 0, Math.PI / 2.5]}>
        <ringGeometry args={[0.9, 1, 3, 1]} />
        <meshStandardMaterial color="gold" roughness={0.75} />
      </mesh>
      <mesh rotation={[-Math.PI/2, 0, 0]} position={[0, -1.162, 0]} >
        <planeBufferGeometry args={[32, 32]}/>
        <MeshReflectorMaterial mirror={1} resolution={1024} />
      </mesh>
      <Environment resolution={512} preset='city' frames={Infinity}>
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, -9]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, -6]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, -3]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 0]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 3]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 6]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-x={Math.PI / 2} position={[0, 4, 9]} scale={[10, 1, 1]} />
        <Lightformer intensity={2} rotation-y={Math.PI / 2} position={[-50, 2, 0]} scale={[100, 2, 1]} />
        <Lightformer intensity={2} rotation-y={-Math.PI / 2} position={[50, 2, 0]} scale={[100, 2, 1]} />
        <Lightformer rotation-y={-Math.PI / 2} position={[10, 1, 0]} scale={[20, 1, 1]} />
        <Float speed={5} floatIntensity={2} rotationIntensity={2}>
          <Lightformer form="ring" color="gold" intensity={1} scale={10} position={[-15, 4, -18]} target={[0, 0, 0]} />
        </Float>
        <Lightformer form="ring" color="gold" intensity={10} scale={2} position={[10, 5, 10]} onUpdate={(self) => self.lookAt(0, 0, 0)} />
      </Environment>
      <OrbitControls enablePan={false} minPolarAngle={Math.PI / 2.2} maxPolarAngle={Math.PI / 2.2} autoRotate autoRotateSpeed={0.4} />
      <PostProcess />
      {[...Array(1000)].map((_, i) => (
        <AnimationBox key={i} />
      ))}
      <Perf position={"bottom-right"} />
    </XR>
  )
}

const Lambo = (props: any) => {
  const { scene, nodes, materials } = useGLTF('/lambo.glb') as any;
  useMemo(() => {
    // ⬇⬇⬇ All this is probably better fixed in Blender ...
    Object.values(nodes).forEach((node) => {
      if (node instanceof Mesh && node.isMesh) {
        // Fix glas, normals look messed up in the original, most likely deformed meshes bc of compression :/
        if (node.name.startsWith('glass')) node.geometry.computeVertexNormals()
        // Fix logo, too dark
        if (node.name === 'silver_001_BreakDiscs_0') node.material = applyProps(materials.BreakDiscs.clone(), { color: '#ddd' })
      }
    })
    // Fix windows, they have to be inset some more
    nodes['glass_003'].scale.setScalar(2.7)
    // Fix inner frame, too light
    applyProps(materials.FrameBlack, { metalness: 0.75, roughness: 0, color: 'black' })
    // Wheels, change color from chrome to black matte
    applyProps(materials.Chrome, { metalness: 1, roughness: 0, color: '#333' })
    applyProps(materials.BreakDiscs, { metalness: 0.2, roughness: 0.2, color: '#555' })
    applyProps(materials.TiresGum, { metalness: 0, roughness: 0.4, color: '#181818' })
    applyProps(materials.GreyElements, { metalness: 0, color: '#292929' })
    // Make front and tail LEDs emit light
    applyProps(materials.emitbrake, { emissiveIntensity: 3, toneMapped: false })
    applyProps(materials.LightsFrontLed, { emissiveIntensity: 3, toneMapped: false })
    // Paint, from yellow to black
    nodes.yellow_WhiteCar_0.material = new MeshPhysicalMaterial({
      roughness: 0.3,
      metalness: 0.05,
      color: '#820000',
      envMapIntensity: 0.75,
      clearcoatRoughness: 0,
      clearcoat: 1
    })
  }, [nodes, materials])
  return <primitive object={scene} {...props} />
}

const AnimationBox = () => {
  const ref = useRef<any>();
  const [speed] = useState(Math.random() * 0.05 + 0.01);

  const p = new Vector3(
    Math.random() * 30 - 15,
    Math.random() * 3,
    Math.random() * 30 - 15
  );
  
  const size = 0.25;
  const reverse = Math.random() > 0.5? true: false;
  const color = useMemo(() => {
    return new Color().setHSL(Math.random(), 1.0, 0.5);
  }, []);

  useFrame((state) => {
    if (ref.current) {
      if (reverse){
        ref.current.position.y -= Math.sin(state.clock.getElapsedTime()) * speed;  
      }
      else{
        ref.current.position.y += Math.sin(state.clock.getElapsedTime()) * speed;
      }
    }
  });

  return (
    <mesh ref={ref} position={p} castShadow receiveShadow>
      <boxBufferGeometry attach="geometry" args={[size, size, size]} />
      <meshStandardMaterial attach="material" color={color} />
    </mesh>
  );
}

const PostProcess = () => {
  const [texture, setTexture] = useState<Texture>();
  useEffect(() => {
    const loader = new LUTCubeLoader();
    loader.load('/F-6800-STD.cube', (loadedTexture: Texture) => {
      setTexture(loadedTexture);
    });
  }, []);
  let lut: JSX.Element = (<></>);
  if (texture){
    lut = <LUT lut={texture} />
  }
  return (
    <EffectComposer disableNormalPass>
      <Bloom luminanceThreshold={0.2} mipmapBlur luminanceSmoothing={0} intensity={1.45} />
      {lut}
    </EffectComposer>
  )
}

export default Scene;

最後に

R3Fの海外コミュニティでは毎日のように効率化リソースが公開され、Web3などの新しいプロジェクトがでてきて毎日楽しいです。
次はWebGPUが実装されるんでしょうか。楽しみです。

おわり。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?