🐕

React+TypeScriptなWebアプリで、R3Fのtutorial15。(useLoaderでテクスチャ読込み)

2023/12/26に公開

Abstract

今回の参考はここ(useLoader Hook)

  • useLoader()は、画像をTextureとして読み込んだり、モデルのデータを読み込んだりするのに使える。
  • useLoader()で読み込んだデータはキャッシュされる。なので、パス名で再利用可能。

結論

今回の成果物はココ↓
https://github.com/aaaa1597/react-r3f-tutorial015

読込み方法の説明 :

useLoader(loader, url: string | string[], extensions?)

  • loader ... ロードを実行するクラスor関数。読込みたい対象によっていろいろ設定できる。
    • TextureLoader ← 今回はコレを使う。
    • GLTFLoader
    • FBXLoader
    • MMDLoader
  • url ... 絶対パスか相対パスを設定。相対パスの時は、public配下に配置する必要がある。
  • extensions ... 知らん。

使い方の説明 :

画像が読み込まれたら、mapプロパティに割当てる。
下記3つの方法がある。

使い方1
<meshBasicMaterial map="{texture}"></meshBasicMaterial>
使い方2
material={new THREE.MeshBasicMaterial({ map: texture })}
使い方3
const material = new THREE.MeshBasicMaterial()
material.map = texture

前提

手順

1.プロジェクト生成 -> VSCodeで開く

めんどいから、このスケルトンコードから始める。react-r3f-tutorial012
で、下記コマンドでフォルダ名とか整備する。

フォルダリネームとか
$ BaseProject=react-r3f-tutorial012
$ NewProject=react-r3f-tutorial015
$ cd ~
$ rm -rf ${BaseProject}
$ git clone https://github.com/aaaa1597/${BaseProject}.git
$ rm -rf ${BaseProject}/.git
$ mv ${BaseProject} ${NewProject}
$ cd ${NewProject}

準備

$ npm install --save three
$ npm install --save @types/three
$ npm install --save @react-three/fiber
$ npm install --save @react-three/drei
$ npm install --save-dev leva
$ mkdir -p public/imgs
$ wget https://github.com/aaaa1597/react-r3f-tutorial015/blob/main/public/imgs/grid.png public/imgs

.eslintrc.js

ビルドエラー回避の設定

.eslintrc.js
     "rules": {
-        "react/no-unknown-property": ['error', { ignore: ['css', "args", 'wireframe', 'rotation-x', 'rotation'] }],
+        "react/no-unknown-property": ['error', { ignore: ['css', "args", 'rotation-x', "castShadow", 'receiveShadow'] }],
     }

App.tsx

まず全体。

App.tsx
 import React, {useRef} from 'react';
 import './App.css';
-import { Canvas, useFrame, MeshProps } from '@react-three/fiber'
+import { Canvas, useFrame, MeshProps, useLoader } from '@react-three/fiber'
 import * as THREE from 'three'
 import { Stats, OrbitControls } from '@react-three/drei'
 import { useControls } from 'leva'


+const Lights = () => {
+  const directionalRef = useRef<THREE.DirectionalLight>(null!)
+
+  useControls('Directional Light', {
+    intensity: {
+      value: 1, min: 0, max: 5, step: 0.1,
+      onChange: (v) => {
+        directionalRef.current.intensity = v
+      },
+    },
+    position: {
+      value: [3.3,1.0,4.4],
+      onChange: (v) => {
+        directionalRef.current.position.x = v[0]
+        directionalRef.current.position.y = v[1]
+        directionalRef.current.position.z = v[2]
+      },
+    },
+  })
+
+  return (
+    <directionalLight ref={directionalRef} castShadow />
+  )
+}

type BoxProps = {
  props: MeshProps;
  wireframe?: boolean;
}

const Box = (boxprops: BoxProps) => {
  const ref = useRef<THREE.Mesh>(null!)

  useFrame((_, delta) => {
    if( !ref.current) return
    ref.current.rotation.x += 1 * delta
    ref.current.rotation.y += 0.5 * delta
  })

-  useControls(boxprops.props.name!, {
-    wireframe: {
-      value: false,
-      // onChange: (v: boolean) => {
-      //   console.log( (ref.current.material as THREE.MeshBasicMaterial) );
-      //   (ref.current.material as THREE.MeshBasicMaterial).wireframe = v
-      // }
-    },
-    flatShading: {
-      value: true,
-      // onChange: (v) => {
-      //   // ref.current.material.flatShading = v
-      //   // ref.current.material.needsUpdate = true
-      // },
-    },
-    color: {
-      value: 'lime',
-      // onChange: (v) => {
-      //   // ref.current.material.color = new THREE.Color(v)
-      // },
-    },
-  })

  return (
-   <mesh {...boxprops} ref={ref}>
+   <mesh {...boxprops.props} ref={ref}>
      <boxGeometry />
-     <meshBasicMaterial color={0x00ff00} wireframe />      
    </mesh>
  )
}

+const Floor = () => {
+  return (
+    <mesh rotation-x={-Math.PI / 2} receiveShadow={true}>
+      <circleGeometry args={[10]} />
+      <meshStandardMaterial />
+    </mesh>
+  )
+}

const App = () => {
+ const texture = useLoader(THREE.TextureLoader, './imgs/grid.png')

  return (
    <div style={{ width: "75vw", height: "75vh" }}>
-      <Canvas camera={{ position: [3, 1, 2] }}>
+      <Canvas camera={{ position: [4, 4, 3] }} shadows>
-        <ambientLight />
-        <directionalLight />
        <Lights />
-       <Box props={{position:[-0.75, 0, 0], name:"A", material: new THREE.MeshBasicMaterial()}}/>
-       <Box props={{position:[ 0.75, 0, 0], name:"B", material: new THREE.MeshNormalMaterial()}}/>
-       <Box props={{position:[-0.75, 2, 0], name:"C", material: new THREE.MeshPhongMaterial()}}/>
-       <Box props={{position:[ 0.75, 2, 0], name:"D", material: new THREE.MeshStandardMaterial()}}/>
+       <Box props={{position:[3, 1, 0], name:"meshBasicMaterial",    material: new THREE.MeshBasicMaterial({ map: texture })}}/>
+       <Box props={{position:[1, 1, 0], name:"meshNormalMaterial",   material: new THREE.MeshNormalMaterial({flatShading: true,})}}/>
+       <Box props={{position:[1, 3, 0], name:"meshPhongMaterial",    material: new THREE.MeshPhongMaterial({flatShading: true, map: texture,})}}/>
+       <Box props={{position:[3, 3, 0], name:"MeshStandardMaterial", material: new THREE.MeshStandardMaterial({flatShading: true, map: texture,})}}/>
        <Floor />
        <OrbitControls />
        <axesHelper args={[5]} />
        <gridHelper />
        <Stats />
      </Canvas>
    </div>
  );
}

export default App;

で、実行。


出来た!!

ポイント

useLoader()で指定する画像の位置は、public配下で。


React+TypeScriptなWebアプリで、R3Fのtutorial14。(影(Shadow)を設定する)


React+TypeScriptなWebアプリで、R3Fのtutorial16。(GLTFloaderで3Dモデル表示)

Discussion