【React Three Fiber】Textureの適用方法

<!-- ======================================================== -->
# 概要
<!-- ======================================================== -->
React Three Fiberで、Materialに**テクスチャを適用する方法**をまとめました。
現在わかっているだけで、5パターンの適用方法があります。

<!-- ======================================================== -->
# ベースとなるモデル
<!-- ======================================================== -->
この記事で扱うsceneです。
モデルにはテクスチャを適用していません。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/e8dbf6a0-10ae-6264-b6f4-4720952af0d0.png" width="600">
<font color='#22a7f2'>Cube</font>は床面に影を落としています。また、<font color='#ea7600'>Sphere</font>からの影を受けています。

### コード
object以外のコードは、以降で紹介するコードと同じなので、ここでまとめて記載しておきます。

```.tsx:NoTexture.tsx
import React, { useRef, VFC } from 'react';
import * as THREE from 'three';
import { OrbitControls, Plane, Stats } from '@react-three/drei';
import { Canvas, useFrame } from '@react-three/fiber';

export const NoTexture: VFC = () => {
	return (
		<Canvas
			camera={{
				position: [0, 5, 8],
				fov: 50,
				aspect: window.innerWidth / window.innerHeight,
				near: 0.1,
				far: 2000
			}}
			dpr={window.devicePixelRatio}
			shadows>
			{/* canvas color */}
			<color attach="background" args={['#1e1e1e']} />
			{/* fps */}
			<Stats />
			{/* camera controller */}
			<OrbitControls />
			{/* lights */}
			<ambientLight intensity={0.1} />
			<directionalLight
				position={[5, 5, 5]}
				intensity={1}
				shadow-mapSize-width={2048}
				shadow-mapSize-height={2048}
				castShadow
			/>
			{/* object */}
			<Objects />
			{/* floor */}
			<Plane rotation={[-Math.PI / 2, 0, 0]} args={[10, 10]} receiveShadow>
				<meshStandardMaterial color="#fff" side={THREE.DoubleSide} />
			</Plane>
		</Canvas>
	)
}

// ==============================================
const Objects: VFC = () => {
	const boxRef = useRef<THREE.Mesh>(null)

	useFrame(() => {
		boxRef.current!.rotation.y += 0.01
	})

	return (
		<>
			<mesh ref={boxRef} position={[-1, 1, 0]} castShadow receiveShadow>
				<boxGeometry args={[1, 1, 1]} />
				<meshStandardMaterial color="#22a7f2" />
			</mesh>
			<mesh position={[1, 3, 2]} castShadow receiveShadow>
				<sphereGeometry args={[0.6, 32, 32]} />
				<meshStandardMaterial color="#ea7600" />
			</mesh>
		</>
	)
}
```
### 影の設定
影の設定については、以下の記事を参考にしてください。

https://qiita.com/nemutas/items/60587fb3c67450ecff6d#%E5%BD%B1

<!-- ======================================================== -->
# Matcaps
<!-- ======================================================== -->
**Matcaps**を使用したテクスチャの適用方法を紹介します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/d148207e-4943-b2c9-2269-d7e9ae2bb8d4.png" width="600">
Matcapsを使用すると、メッシュ自体に落ちる影が適用されなくなります。
床面に、<font color='#22a7f2'>Cube</font>と<font color='#ea7600'>Sphere</font>の影は落ちています。しかし、テクスチャを適用した<font color='#22a7f2'>Cube</font>には、<font color='#ea7600'>Sphere</font>の影が落ちません。

### 関連リンク
* ドキュメント

https://docs.pmnd.rs/drei/prototyping/use-matcap-texture

* テクスチャの仕様

https://github.com/nidorx/matcaps

### コード
```.tsx:MatcapsTexture.tsx
import React, { Suspense, useRef, VFC } from 'react';
import * as THREE from 'three';
import { OrbitControls, Plane, Stats, useMatcapTexture } from '@react-three/drei';
import { Canvas, useFrame } from '@react-three/fiber';

export const MatcapsTexture: VFC = () => {
	return (
		<Canvas {/* props */}>
			// ・・・
			{/* object */}
			<Suspense fallback={null}>
				<Objects />
			</Suspense>
			// ・・・
		</Canvas>
	)
}

// ==============================================
const Objects: VFC = () => {
	const boxRef = useRef<THREE.Mesh>(null)

	useFrame(() => {
		boxRef.current!.rotation.y += 0.01
	})

	const [matcap1] = useMatcapTexture('161B1F_C7E0EC_90A5B3_7B8C9B', 512)
	const [matcap2] = useMatcapTexture('36220C_C6C391_8C844A_8B7B4C', 512)

	return (
		<>
			<mesh ref={boxRef} position={[-1, 1, 0]} castShadow receiveShadow>
				<boxGeometry args={[1, 1, 1]} />
				<meshMatcapMaterial matcap={matcap1} />
			</mesh>
			<mesh position={[1, 3, 2]} castShadow receiveShadow>
				<sphereGeometry args={[0.6, 32, 32]} />
				<meshMatcapMaterial matcap={matcap2} />
			</mesh>
		</>
	)
}
```
* テクスチャを読み込む場合は、`Suspense`タグを使用して非同期処理にします。

```.tsx
<Suspense fallback={null}>
	<Objects />
</Suspense>
```

* **Matcaps**は、`useMatcapTexture`を使用して読み込みます。引数には、テクスチャコードと解像度を指定します。

```.tsx
const [matcap1] = useMatcapTexture('161B1F_C7E0EC_90A5B3_7B8C9B', 512)
const [matcap2] = useMatcapTexture('36220C_C6C391_8C844A_8B7B4C', 512)
```

Matcapsテクスチャは4色からなるテクスチャで、テクスチャコードはその色のHexカラーコードをつなげたものです。

><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/718b57f8-5fbc-7841-6844-e003d1f61417.png" width="600">
>https://github.com/nidorx/matcaps#textures

* 生成したテクスチャは、`meshMatcapMaterial`に適用します。

```.tsx
<mesh ref={boxRef} position={[-1, 1, 0]} castShadow receiveShadow>
	<boxGeometry args={[1, 1, 1]} />
	<meshMatcapMaterial matcap={matcap1} />
</mesh>
```

<!-- ======================================================== -->
# Environment
<!-- ======================================================== -->
**Environment**タグを使用したテクスチャの適用方法を紹介します。
Environmentは、メッシュではなく周囲にテクスチャを適用します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/24041729-fbb2-3f80-c13a-57f18e195d3a.png" width="600">
<font color='#ea7600'>Sphere</font>は、`metalness`値を上げて、周辺環境を反射させています。

### 関連リンク
* ドキュメント

https://docs.pmnd.rs/drei/abstractions/enviroment

* テクスチャのダウンロードサイト

https://polyhaven.com/

### コード
```.tsx:EnvTexture.tsx
import React, { Suspense, useRef, VFC } from 'react';
import * as THREE from 'three';
import { Environment, OrbitControls, Plane, Stats } from '@react-three/drei';
import { Canvas, useFrame } from '@react-three/fiber';

export const MatcapsTexture: VFC = () => {
	return (
		<Canvas {/* props */}>
			// ・・・
			{/* object */}
			<Suspense fallback={null}>
				<Environment preset="city" background />
			</Suspense>
			<Objects />
			// ・・・
		</Canvas>
	)
}

// ==============================================
const Objects: VFC = () => {
	const boxRef = useRef<THREE.Mesh>(null)

	useFrame(() => {
		boxRef.current!.rotation.y += 0.01
	})

	return (
		<>
			<mesh ref={boxRef} position={[-1, 1, 0]} castShadow receiveShadow>
				<boxGeometry args={[1, 1, 1]} />
				<meshStandardMaterial color="#22a7f2" />
			</mesh>
			<mesh position={[1, 3, 2]} castShadow receiveShadow>
				<sphereGeometry args={[0.6, 32, 32]} />
				<meshStandardMaterial color="#ea7600" metalness={1} roughness={0} />
			</mesh>
		</>
	)
}
```
**Environment**タグでは、読み込むテクスチャを指定します。

```.tsx
<Suspense fallback={null}>
	<Environment preset="city" background />
</Suspense>
```
`preset`は、あらかじめ用意されているテクスチャです。
これ以外のテクスチャを使うには、`files`を使用してテクスチャファイルをロードします。

```.tsx
<Suspense fallback={null}>
	<Environment files="/assets/comfy_cafe_2k.hdr" background />
</Suspense>
```
また、`background`を指定することで周囲にテクスチャを表示します。
これを指定しない場合、周囲にテクスチャは適用されますが、表示自体はされません。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/3b2ac457-c607-8c5a-666a-43b437dbd954.png" width="600">

<!-- ======================================================== -->
# useCubeTexture
<!-- ======================================================== -->
**useCubeTexture**フックを使用したテクスチャの適用方法を紹介します。
Environmentを使用したテクスチャと似ていますが、useCubeTextureではメッシュ自身にテクスチャを適用させます。
つまり、テクスチャを適用するメッシュを個別に指定することができます。
また、周辺にもテクスチャを適用することができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/3a13ec58-463e-bc98-ab4b-5cb6f3f65db3.png" width="600">
<font color='#22a7f2'>Cube</font>と<font color='#ea7600'>Sphere</font>、周辺環境にテクスチャを適用しています。床には適用していません。

### 関連リンク
* ドキュメント

https://docs.pmnd.rs/drei/loaders/use-cube-texture

### コード
```.tsx:CubeTexture.tsx
import React, { Suspense, useRef, VFC } from 'react';
import * as THREE from 'three';
import { OrbitControls, Plane, Stats, useCubeTexture } from '@react-three/drei';
import { Canvas, useFrame, useThree } from '@react-three/fiber';

export const MatcapsTexture: VFC = () => {
	return (
		<Canvas {/* props */}>
			// ・・・
			{/* object */}
			<Suspense fallback={null}>
				<Objects />
			</Suspense>
			// ・・・
		</Canvas>
	)
}

// ==============================================
const Objects: VFC = () => {
	const boxRef = useRef<THREE.Mesh>(null)

	useFrame(() => {
		boxRef.current!.rotation.y += 0.01
	})

	const { scene } = useThree()

	const texture = useCubeTexture(['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'], {
		path: '/assets/textures/cube/'
	})

	scene.background = texture

	return (
		<>
			<mesh ref={boxRef} position={[-1, 1, 0]} castShadow receiveShadow>
				<boxGeometry args={[1, 1, 1]} />
				<meshStandardMaterial color="#22a7f2" envMap={texture} />
			</mesh>
			<mesh position={[1, 3, 2]} castShadow receiveShadow>
				<sphereGeometry args={[0.6, 32, 32]} />
				<meshStandardMaterial color="#ea7600" envMap={texture} metalness={1} roughness={0} />
			</mesh>
		</>
	)
}
```
**useCubeTexture**は、6つのテクスチャを読み込みます。これはメッシュの周囲を取り巻く6面のテクスチャになります。

```.tsx
const texture = useCubeTexture(['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'], {
	path: '/assets/textures/cube/'
})
```
それを、マテリアルの`envMap`に割り当てます。

```.tsx
<mesh position={[1, 3, 2]} castShadow receiveShadow>
	<sphereGeometry args={[0.6, 32, 32]} />
	<meshStandardMaterial color="#ea7600" envMap={texture} metalness={1} roughness={0} />
</mesh>
```
また、`scene.background `に割り当てることで、周囲にもテクスチャが適用されます。

```.tsx
scene.background = texture
```

### CubeTextureを使用した屈折表現
`texture.mapping`に**CubeRefractionMapping**を指定することで、屈折表現をすることができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/e347e126-6f42-18d5-1fc7-60cad22a7a87.png" width="600">

```.tsx
const { scene } = useThree()
const texture = useCubeTexture(['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'], {
	path: '/assets/textures/cube/'
})
texture.mapping = THREE.CubeRefractionMapping
scene.background = texture

//・・・

<mesh position={[1, 3, 2]} castShadow receiveShadow>
	<sphereGeometry args={[0.6, 32, 32]} />
	<meshBasicMaterial envMap={texture} refractionRatio={0.9} color="#fff" />
</mesh>
```

ただし、`refractionRatio`を指定できるmaterialは一部に限られているようです。わかっているもので以下のものがあります。

* meshPhongMaterial
* meshLambertMaterial
* meshBasicMaterial

**公式サンプル**
https://threejs.org/examples/#webgl_materials_envmaps
https://threejs.org/examples/#webgl_materials_cubemap_refraction
https://threejs.org/examples/#webgl_materials_cubemap

<!-- ======================================================== -->
# useTexture
<!-- ======================================================== -->
**useTexture**フックを使用したテクスチャの適用方法を紹介します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/55b8713c-cafe-4b9b-8579-3ee874396133.png" width="600">

:::note warn
ただ、この方法はあまり実用的ではないと思います。
:::
図を見ると<font color='#22a7f2'>Cube</font>の面が分離しています。
これは、DisplacementMapとdisplacementScaleを割り当てたことが原因ですが、もっと言えばUVマッピングが正確にできていないからだと思います。（この辺は正確に調査できていないです）
コード上でUVマッピングすることもできますが、複雑なメッシュに対してUVマッピングするのは難しくコードも煩雑になります。
それよりもいい方法があります。次で紹介するBlenderを用いたテクスチャの適用です。

### 関連リンク
* ドキュメント

https://docs.pmnd.rs/drei/loaders/use-texture

* テクスチャのダウンロードサイト

https://ambientcg.com/

### コード
```.tsx
import React, { Suspense, useRef, VFC } from 'react';
import * as THREE from 'three';
import { OrbitControls, Plane, Stats, useTexture } from '@react-three/drei';
import { Canvas, useFrame } from '@react-three/fiber';

export const Texture: VFC = () => {
	return (
		<Canvas {/* props */}>
			// ・・・
			{/* object */}
			<Suspense fallback={null}>
				<Objects />
			</Suspense>
			// ・・・
		</Canvas>
	)
}

// ==============================================
const Objects: VFC = () => {
	const boxRef = useRef<THREE.Mesh>(null)

	useFrame(() => {
		boxRef.current!.rotation.y += 0.01
	})

	const name = (type: string) => `/assets/textures/Rocks024S/Rocks024S_1K_${type}.jpg`

	const textureProps = useTexture({
		map: name('Color'),
		displacementMap: name('Displacement'),
		normalMap: name('NormalGL'),
		roughnessMap: name('Roughness'),
		aoMap: name('AmbientOcclusion')
	})

	return (
		<>
			<mesh ref={boxRef} position={[-1, 1, 0]} castShadow receiveShadow>
				<boxGeometry args={[1, 1, 1, 32, 32, 32]} />
				<meshStandardMaterial {...textureProps} displacementScale={0.1} />
			</mesh>
			<mesh position={[1, 3, 2]} castShadow receiveShadow>
				<sphereGeometry args={[0.6, 32, 32]} />
				<meshStandardMaterial {...textureProps} displacementScale={0.2} />
			</mesh>
		</>
	)
}
```
* displacementを適用する場合、geometryのセグメントを細かくする（頂点数を増やす）必要があります。<br>
その頂点が`displacementMap`に従って凹凸の形状を取り、そのスケールを`displacementScale`で決めます。

```.tsx
<mesh ref={boxRef} position={[-1, 1, 0]} castShadow receiveShadow>
	<boxGeometry args={[1, 1, 1, 32, 32, 32]} />
	<meshStandardMaterial color="#22a7f2" {...textureProps} displacementScale={0.1} />
</mesh>
```

<!-- ======================================================== -->
# Blender
<!-- ======================================================== -->
**Blender**を使用したテクスチャの適用方法を紹介します。
この方法では、Blender側でモデルを作成し、更にテクスチャも適用します。そして、そのモデルをglbエクスポートして、React Three Fiberに読み込ませます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/0bf67d08-c2e1-e67d-8c55-394c424089d6.png" width="600">

使用したテクスチャは以下のものです。

https://ambientcg.com/view?id=SheetMetal002

### 関連リンク
* glbファイルの仕様

https://docs.blender.org/manual/en/2.80/addons/io_scene_gltf2.html

* テクスチャのダウンロードサイト

https://ambientcg.com/

### モデリング（Blender）
UV球を追加します。
また、スムーズシェードもかけておきます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/8a26b01e-5f39-6680-4df7-f574c8dc0845.png" width="600">

Shadingタグで、ノードを以下のように設定します。
![Inkedスクリーンショット 2021-11-21 231847_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/ac1690a2-ccea-86b0-be35-850594280c8b.jpeg)
画像テクスチャノードに、ダウンロードしたテクスチャのマップをそれぞれ指定します。
ここで、Color Mapは、`色空間`に**sRGB**を指定します。その他のMapは、**Non-Color**を指定します。

また、今回使用したテクスチャでは**Opacity Map（Alpha Map）**を指定しています。
このMapを指定する場合は、ノードの追加の他にマテリアルの設定から、**ブレンドモードをアルファクリップ**にする必要があります。
![スクリーンショット 2021-11-21 232907.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/b36fdd43-0a44-089a-edad-d6ff3c4f1a2f.png)

Blenderでテクスチャを指定する利点として、UV展開の設定の他に、**マッピングノード**でテクスチャの`位置・角度・スケール`を簡単に調整することができます。
![aaa.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/3574be20-d395-0985-bfe2-39d615b4337a.jpeg)

#### Displacementについて
useTextureの例でDisplacementをやり玉にあげましたが、Displacement Mapを使用したBlenderでの設定については、ちゃんとした設定方法がわかっていません。

そもそもテクスチャを使用する箇所を、凹凸がわかるレベルで物凄くよって見ることはほぼないと思うので、いまのところ調査はしていません。

また、モディファイアを使うことで一応設定することが可能ですが、ちゃんと反映されていないっぽいので使用しませんでした。
![スクリーンショット 2021-11-21 235906.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/4742f70c-fcb5-1b02-5d59-964ad3bfb023.png)
![スクリーンショット 2021-11-22 000128.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/06f0088f-dafb-594b-1646-3fd1fa235a04.png)

### エクスポート
以下の手順で、作成したモデルをglbファイルでエクスポートします。
![スクリーンショット 2021-11-21 234019.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/196fd89d-c097-08d6-0337-d9b22249308e.png)

エクスポートの設定
![スクリーンショット 2021-11-21 233939.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/7ee52000-d5e8-4c13-7824-8d601ec58186.png)

### コードの雛形の生成
次に、エクスポートしたglbファイルをReact Three Fiberで読み込むわけですが、コードの雛形を生成してくれる便利なツールがあります。

https://gltf.pmnd.rs/

このサイトに、エクスポートしたglbファイルを投げ込むと、モデルを表示してくれます。また、コードの雛形も同時に生成してくれます。

![スクリーンショット 2021-11-21 234536.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/406fa053-da79-aea6-0e6c-14601a543935.png)

雛形コードの生成は、コマンドラインからも行えます。詳しくは以下を参照してください。

https://github.com/pmndrs/gltfjsx

### コード
```.tsx
import React, { Suspense, useRef, VFC } from 'react';
import * as THREE from 'three';
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
import { Environment, OrbitControls, Plane, Stats, useGLTF } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';

export const GlbTexture: VFC = () => {
	return (
		<Canvas {/* props */}>
			// ・・・
			{/* object */}
			<Suspense fallback={null}>
				<Environment preset="city" />
				<Model />
			</Suspense>
			// ・・・
		</Canvas>
	)
}

// ==============================================
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/

type GLTFResult = GLTF & {
	nodes: {
		Sphere: THREE.Mesh
	}
	materials: {
		SheetMetal002: THREE.MeshStandardMaterial
	}
}

const ModelPath = '/assets/SheetMetal002.glb'

const Model: VFC = (props: JSX.IntrinsicElements['group']) => {
	const group = useRef<THREE.Group>()
	const { nodes, materials } = useGLTF(ModelPath) as GLTFResult

	return (
		<group ref={group} position={[0, 2, 0]} {...props} dispose={null}>
			<mesh
				castShadow
				receiveShadow
				geometry={nodes.Sphere.geometry}
				material={materials.SheetMetal002}
			/>
		</group>
	)
}

useGLTF.preload(ModelPath)
```

<!-- ======================================================== -->
# まとめ
<!-- ======================================================== -->
手探りで調べたので間違った解釈をしているところがあるかもしれません。

「こういうテクスチャの割り当て方もあるよ」とか、「テクスチャをこういう風に使うと、こういう表現ができるよ」など、知っているかたがいれば是非コメントしてください。