Svelte で 3D 表示を行うライブラリ Threlte #3: glTF シーンを扱う

# シリーズ

### Main

* [Svelte で 3D 表示を行うライブラリ Threlte #1: パッケージの作成とライブラリ概要](https://qiita.com/takavfx/items/0d4c6b831628d628b468)
* [Svelte で 3D 表示を行うライブラリ Threlte #2: エクストラパッケージを使用する](https://qiita.com/takavfx/items/e71db089198bba1b122b)
* Svelte で 3D 表示を行うライブラリ Threlte #3: glTF シーンを扱う
* [Svelte で 3D 表示を行うライブラリ Threlte #4: シーンコントロールツールセット Theatre.js for Threlte](https://qiita.com/takavfx/items/b91ba3c47e5137723a5f)
* [Svelte で3D表示を行うライブラリ Threlte #5: XR への対応](https://qiita.com/takavfx/items/6a499286c8a125c7b65d)

### Extra

* [Svelte で 3D 表示を行うライブラリ Threlte Ex1: Svelte5 対応を試す](https://qiita.com/takavfx/items/13feec35d60fe629eb26)

# glTF について

glTF は Khronos Group Inc. が提唱しているファイルフォーマット、シーンディスクリプタ。
最初からストリーミング機構などをサポートしているため、Web 利用などと相性が良く、近年普及が進んできている。

:::note info
昨年の SIGGRAPH 2023 では glTF の今後のフォーマットに関してのセッションやフォーラムも数多くあった。
参加者には、Khronos Group の人に加え、Meta、Adobe、NVIDIA、Autodesk、Epic Games などが参加しており、今後は、XR や 3D 広告での利用をもっと広めていく狙いもあるとみられる。

* [The State of 3D Asset Interoperability using USD and glTF](https://s2023.siggraph.org/?post_type=page&p=1842&id=bof_153&sess=sess454)
* [Geometry, Textures, and Workflow - Optimizing glTF](https://s2023.siggraph.org/?post_type=page&p=1842&id=bof_175&sess=sess462)
* [glTF Complex Scenes & Interactivity](https://s2023.siggraph.org/?post_type=page&p=1842&id=bof_174&sess=sess484)
* [Exploring the Artistic Frontier: Unleashing Creativity in 3D Models with glTF and PBR](https://s2023.siggraph.org/?post_type=page&p=1842&id=bof_173&sess=sess485)
* [Let's Get Moving: Adding Physics to glTF](https://s2023.siggraph.org/?post_type=page&p=1842&id=bof_177&sess=sess475)
* [glTF: Transforming 3D Asset Delivery for Real-Time Graphics](https://s2023.siggraph.org/?post_type=page&p=1842&id=bof_149&sess=sess519)
* [Khronos is holding our fourth invite-only glTF Ecosystem Forum at this year’s SIGGRAPH in Los Angeles, California.](https://www.khronos.org/events/gltf-ecosystem-forum-at-siggraph-2023)
:::

## glTF のデータ構造

データ構造を理解していると、コード上でのアクセスもしやすい。
なので、一応のデータ構造に関してもここで扱っておく。

```svelte:.gltf/.glb
{
    animations: AnimationClip[],
    asset: {generator: string, version: number},
    cameras: Camera[],
    materials: 'Materials'[],
    nodes: (Group | Mesh | SkkinedMesh)[],
    parser: GLTFParser,
    scenes: Group[]
    userData: {}
}
```

* animations
    * アニメーション（クリップ）情報が保存されている
* asset
    * glTF ファイル自体の情報
* cameras
    * カメラ情報が保存されている
* materials
    * マテリアル情報が保存されている
* nodes
    * メッシュやスキンメッシュ（アニメーションする際に必要なメッシュタイプ）、内包シーン情報が保存される
* parser
    * GLTF のパース情報。JSON 形式もこちら経由で取得できる。
* scenes
    * シーン情報を保存
* userData
    * ユーザー固有のデータの保存領域

## glTF の種類

glTF には、大きく分けて、

* Ascii タイプ
    * 拡張子は **.gltf**
    * テキストで構成されているファイルタイプ。一般的なテキストエディタで中身を見ることができる。
    * 文字列のパースを含むので、ファイルサイズが大きければ大きいほどロードに時間がかかる。
* Binary タイプ
    * 拡張子は **.glb**
    * 内容がバイナリで構成されていてファイルの中身を通常のテキストエディタ等では閲覧できない。
    * ファイルサイズが抑えられるのと、ロードの速度が速い。

が存在する。

ファイルによってはテクスチャ等が分かれているので、それら不随ファイルが足りないと、以下の機構で利用するさいはロードエラーを起こすので注意する。

## Threlte における glTF

Threlte では、他のファイルフォーマットに比べて最初からサポートがある。

* [`<GLTF>`](https://threlte.xyz/docs/reference/extras/gltf)
* [`useGltf`]()
* [`useGltfAnimations`](https://threlte.xyz/docs/reference/extras/use-gltf-animations
)

通常のファイルロードに加えて、アニメーションやシーン周りのサポートもあるため、諸々を説明するより先に Threlte での glTF の扱い方を紹介した方がモノにしやすいというのもあり、この段階での glTF の扱い方についてまとめるに至った。

では、まずは簡易ロードの仕方から。

# glTF 関連モジュールの使い方

## `<GLTF>` - glTF ファイルの簡易ロード

`<GLTF>` を使用すると、それ単体での glTF ファイルの簡易ロードが可能。

```diff_svelte:Scene.svelte
<script lang="ts">
	import { T } from '@threlte/core';
+	import { ContactShadows, GLTF, Grid, OrbitControls, Environment } from '@threlte/extras';
</script>

<T.PerspectiveCamera makeDefault position={[5, 2, 13]} fov={15}>
	<OrbitControls enableZoom={true} enableDamping target.y={1.5} />
</T.PerspectiveCamera>

<Environment
	files={'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/equirectangular/venice_sunset_1k.hdr'}
	isBackground={true}
/>

<Grid
	position.y={-0.001}
	cellColor="#ffffff"
	sectionColor="#ffffff"
	sectionThickness={0}
	fadeDistance={30.0}
	cellSize={2}
/>

<ContactShadows scale={10} blur={2} far={2.5} opacity={0.5} />

<!-- 今回は glTF のサンプルモデルを直接 static 以下に配置して使用 -->
<T.Mesh position={[0, 1.3, 0]}>
+	<GLTF
+		url="https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/main/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf"
+	/>
</T.Mesh>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/50bdf4de-a700-68f0-832a-a0b3a06309bd.png)


## `useGltf` - glTF ファイルの操作利用

glTF シーンをロードして、そのシーンをコントロールしながら描画する場合は `useGltf` フックでロードして使用する。
Web アプリとして実装する場合はこちらでの利用が多くなると思われる。

useGltf を使用すると、glTF を取り回しができるインスタンスオブジェクトとして扱えるようになる。

```diff_svelte:Scene.svelte
<script lang="ts">
  import { T } from '@threlte/core'
+  import { Grid, OrbitControls, useGltf, Environment } from '@threlte/extras'

  const gltf = useGltf("./models/DamagedHelmet.glb", { useDraco: true })

</script>

{#if $gltf}
+  <T is={$gltf.nodes['node_damagedHelmet_-6514']} />
{/if}
```

glTF オブジェクトにすると、シーン内の詳細にもアクセスできる様になる。
なので、例えばマテリアルを差し替えるなども可能。

```diff_svelte:Scene.svelte
<script lang="ts">
  import { T } from '@threlte/core'
  import { Grid, OrbitControls, useGltf, Environment } from '@threlte/extras'
+  import { MeshStandardMaterial, Color } from 'three'

  const gltf = useGltf("./models/DamagedHelmet.glb", { useDraco: true })
</script>

{#if $gltf}
  <T.Mesh
+    geometry={$gltf.nodes['node_damagedHelmet_-6514'].geometry}
+    material={new MeshStandardMaterial({roughness: 0.0,
+                                        color: new Color("rgb(255, 0, 0)")})}
    rotation={[1.5, 0, 0]}
  />
{/if}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/65d229d7-067f-73c5-590e-cf2a6a627869.png)



## `useGltfAnimations` - glTF でのアニメーションを再生する

Three.js の場合でもそうだが、大体の 3D フレームワーク同様、メッシュとアニメーションの処理は柔軟に扱えるよう、別々に取り扱うように設計されている。Mesh : Aimation = 1:1 ではないのである。
なので、これらを取り扱う場合には、ワンステップ踏む必要がある。

Threlte では、これを `@threlte/extras` の `useGltfAnimations` などを使って実現する。

```diff_svelte:Scene.svelte
<script lang="ts">
  import { T } from '@threlte/core'
+  import { useGltf, useGltfAnimations, GLTF } from '@threlte/extras'

+  const { gltf, actions } = useGltfAnimations()

+  $: $actions['Take 001']?.play()
</script>

<!-- その他シーンセットアップは省略 -->

+<GLTF
+  bind:gltf={$gltf}
+  url="/models/LittlestTokyo.glb"
+  useDraco={true}
+/>
```

![output2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/f6edfa06-71c7-ac1a-f226-d0b4ca2438bc.gif)


:::note info
glTf のアニメーションは、**一つの glTF ファイルに複数内包されいてる**可能性がある。
そのため、アニメーションを再生する際には、アニメーションのトラック名を指定して、再生してあげる必要がある。
こういったところが、アニメーションを扱う上で意図手間かかる所以。
:::

# `@threlte/gltf` - glTF コンポーネント出力ツール

glTF の内部構造にアクセスする場合は名前解決が必要だったりする。
それを上手く解析して表示するには、3D エディタ的な機能まで必要だったりするが、Threlte では、glTF ファイルを読み込むためのコンパクトな svelte コンポーネントを出力しくれるツールが提供されている。
それが `@threlte/gltf` である。

例えば、[DamagedHelmet](https://github.com/KhronosGroup/glTF-Sample-Models/tree/main/2.0/DamagedHelmet) の場合、

```bash
npx @threlte/gltf@latest DamagedHelmet.glb
```

といったコマンドを実行することによって、以下のような Svelte コンポーネントを出力してくれる。

```svelte:DamagedHelmet.svelte
<script>
  import { Group } from 'three'
  import { T, forwardEventHandlers } from '@threlte/core'
  import { useGltf } from '@threlte/extras'

  export const ref = new Group()

  const gltf = useGltf('/DamagedHelmet.glb')

  const component = forwardEventHandlers()
</script>

<T is={ref} dispose={false} {...$$restProps} bind:this={$component}>
  {#await gltf}
    <slot name="fallback" />
  {:then gltf}
    <T.Mesh
      geometry={gltf.nodes['node_damagedHelmet_-6514'].geometry}
      material={gltf.materials.Material_MR}
      rotation={[Math.PI / 2, 0, 0]}
    />
  {:catch error}
    <slot name="error" {error} />
  {/await}

  <slot {ref} />
</T>

```

あとは、これをコンポーネントとして再利用するだけ。

```svelte:Scene.svelte
<script lang="ts">
  import Model from '$lib/models/DamagedHelmet.svelte';
</script>

<!-- シーン設定は省略 -->

<Model />
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/cee6762d-ed0a-f182-05cc-1a76e407b69f.png)


読み込むファイルが決まっていたり、コンパクトなローダーで収めたり、対象ハックする参考としてこれを利用するという手がある。

# glTF を扱う上での付随知識

## DRACO ローダーの使用

glTF は、際してコンパクトに取り回しが行える様に圧縮がかけられている事がままある。
その圧縮形式は今のところ **[DRACO](https://google.github.io/draco/)** が頻繁に使われている（現状は、実質一択）。

その DRACO 解凍 を行いつつロードしてくれる仕組みが **DRACO ローダー** として[用意されている](https://threlte.xyz/docs/reference/extras/gltf#draco)。

この Draco 圧縮がかかったものを、ローダーを通さずにロードしようとするとエラーを起こす。

```
Error in asyncWritable: THREE.GLTFLoader: No DRACOLoader instance provided.
```

そのため、エラー回避も含めてローダーを使用してロードしておく。
以下の様に複数やり方があるので、状況に応じて選択する。

```svelte:Scene.svelte
<script lang="ts">
  import { userGltf } from "@threlte/extras"

  const gltf = useGltf('/model.glb', { useDraco: true })
</script>
```

```svelte:Scene.svelte
<GLTF
  url="/models/LittlestTokyo.glb"
  useDraco={true}
/>
```

また、`true` を渡すとライブラリ標準のものがロードされてくるが、ローダーを直接指定することも可能。
特別、ローダーを変更したい場合に指定すると良い。

```svelte:Scene.svelte
<GLTF
  url="/models/LittlestTokyo.glb"
  useDraco="https://www.gstatic.com/draco/v1/decoders/"
/>
```

:::note info

もう一つとしては [Meshpot]() が存在する。
ただ今のところ僕は使っているところを見たことがないので、要調査。

使い方としては、DRACO と同じで、`useMeshpot` を指定する。

```svelte
<GLTF
  url="/models/LittlestTokyo.glb"
  useMeshpot={true}
/>
```
:::

---

:point_right:次のシリーズを読む
[Svelte で 3D 表示を行うライブラリ Threlte #4: シーンコントロールツールセット Theatre.js for Threlte](https://qiita.com/takavfx/items/b91ba3c47e5137723a5f)