# xr-frame里的GLTF

* GLTF资源
* GLTF组件
* 动画
* Morph
* GLTF扩展
* 限制

***

## GLTF资源
GLTF模型需要先通过[Loader](/miniprogram/dev/reference/xr-frame/component/assets/index)加载进小程序中，才可以渲染。
```xml
<xr-asset-load type="gltf" asset-id="gltfModel" src="/assets/xxx.gltf" />
```
在xr-asset-load标签中：  
+ type需为"gltf"
+ src可以填写.gltf文件也可以填写.glb文件
+ 使用.gltf文件的时候，需要确保将附属资源与.gltf文件放在同一路径下（保持原有相对结构）

该Loader加载完成后会返回一个`GLTFModel`资源，可以用来传递给`GLTF组件`来实例化成场景元素和Mesh组件等。

Loader加载过程中会进行：
1. .gltf和.glb文件的数据解析
2. 所有附属资源的下载
3. 将模型数据重新拼接成xr-frame渲染数据

所有步骤执行完毕后才算是加载完成。由于`步骤3`的存在，所以比较复杂的GLTF模型就算*有缓存*，也可能加载得比较缓慢。使用[xr-frame-toolkit](../tools/toolkit#gltf优化)离线预处理模型可以解决这个问题。

### Loader选项
> 添加选项的方法：`<xr-asset-load ... options="preserveRaw: true" />`

| 选项名 | 用途 | 备注 |
| --- | --- | --- |
| preserveRaw | 加载完后，在`GLTFModel`资源中保留原始json，保存在`jsonRaw`字段中。 | 可通过`scene.assets.getAsset("gltf", "xxx")`来获取`GLTFModel`资源。 |
| ignoreError | 可用于渲染超出限制的模型。 | 使用方法参考渲染超出限制的模型。 |

## GLTF组件
`GLTF组件`可以渲染一个已经加载完成的`GLTFModel`资源。

使用`xr-gltf`标签可以为标签自动创建`GLTF组件`：
```xml
<xr-gltf id="myGLTF" model="gltfModel"></xr-gltf>
```

>  🚫 `xr-gltf`标签对应的元素为*Shadow元素*，所以请**不要**在`xml`里为`xr-gltf`添加子标签。点击[这里](/miniprogram/dev/reference/xr-frame/component/core/shadow)阅读*Shadow元素*的详细说明。

标签属性映射：
| 属性名 | 组件.属性 | 备注 |
| --- | --- | --- |
| model | GLTF.model | 使用的GLTFModel资源，对应`xr-asset-load`标签中的asset-id属性 |
| cast-shadow | GLTF.castShadow | GLTF模型是否投射阴影 |
| receive-shadow | GLTF.receiveShadow | GLTF模型是否接受阴影 |

`GLTF组件`事件：
| 事件名 | 描述 | 事件回调参数 | 备注 |
| --- | --- | --- | --- |
| gltf-loaded | GLTF组件将GLTFModel渲染完毕后触发。 | { target: Element } | - |

### 使用TS脚本来修改GLTF
GLTF组件提供了一系列接口来让用户操作xr-frame为GLTF模型生成的内部子树，开发者也可以用这些接口来**动态修改glTF模型的颜色、纹理等材质属性**。
>  🚫 不可以手动添加或删除节点，只能操作节点上已有的组件。

| 接口名 | 描述 | 备注 |
| --- | --- | --- |
| getInternalNodeByName | 根据GLTFNode节点的`name`字段来获取xr-frame对应的`Element` | |
| get meshes | 获取生成的所有`Mesh组件` | |
| [getPrimitivesByNodeName](../../api/classes/GLTF#getPrimitivesByNodeName) | 根据GLTFNode节点的`name`字段来获取其下的所有`Mesh组件` | 2.28.1版本加入；详见API文档 |
| [getPrimitivesByMeshName](../../api/classes/GLTF#getPrimitivesByMeshName) | 根据GLTFMesh节点的`name`字段来获取所有相关的`Mesh组件` | 2.28.1版本加入；详见API文档 |

### 例：使用TS脚本来修改GLTF贴图
以下代码在GLTF模型渲染完毕后修改其中某个mesh的贴图：
```gltf
// gltf
{
    ...
    "nodes": [{
      "mesh": 0,
      "name": "Banana"
    }],
    ...
}
```
```xml
// xml
<xr-gltf id="myGLTF" model="gltfModel" bind:gltf-loaded="handleGLTFLoaded"></xr-gltf>
```
```typescript
// ts
function handleGLTFLoaded({ detail }) {
    const el = detail.value.target;
    const gltf = el.getComponent("gltf");
    const newMat = this.scene.assets.getAsset("texture", "...texture name...");
    for (const mesh of gltf.getPrimitivesByNodeName("Banana")) {
        mesh.material.setTexture("u_baseColorMap", newMat);
    }
}
```

## GLTF动画
如果GLTF模型有自带的动画，就会在当前元素下自动创建一个`Animator组件`，并将GLTF模型内的动画片段加入到这个`Animator组件`中。

>  如果`GLTF组件`所在的元素本来已经拥有`Animator组件`，就不会新建，而是直接使用这个`Animator组件`。
在标签上添加`anim-autoplay`属性可以自动播放GLTF模型内的动画，会播放GLTF内的**所有**动画片段：
```xml
<xr-gltf id="myGLTF" model="gltfModel" anim-autoplay></xr-gltf>
```

### 例：使用TS脚本来控制GLTF动画
如果你只想播放GLTF模型里的**某一个**动画片段，或者想切换播放的动画片段，可以使用TS脚本来控制。

例如以下代码在GLTF模型渲染完毕后播放名为`idle`的动画：
```xml
// xml
<xr-gltf id="myGLTF" model="gltfModel" bind:gltf-loaded="handleGLTFLoaded"></xr-gltf>
```
```typescript
// ts
function handleGLTFLoaded({ detail }) {
    const el = detail.value.target;
    const animator = el.getComponent("animator");
    animator.play("idle");
}
```
动画的名字`idle`，对应的是.gltf文件中`animations`数组节点的每一个元素的`name`属性（参考[官方文档](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#animations)）。

如果.gltf文件内的动画节点没有`name`属性，则会自动给动画分配名字`gltfAnimation#x`，其中x是数字编号，从0开始：`gltfAnimation#0`, `gltfAnimation#1`, 以此类推。

>  如果.gltf文件内有多个`name`相同的动画，也会使用这个规则，在名称后面添加`#x`作为动画名称，以此来区别。此时如果用不加`#x`的原始名称来索引动画，会返回`#0`的动画。
## Morph Target
`xr-frame`支持Morph Target动画，但是对`target`的数量有限制，同一个模型最多使用**8**个`target`。

这里的`target`概念有别于.gltf文件内的target节点：.gltf文件内的一个同时拥有`POSITION`和`NORMAL`属性的`target节点`，在`xr-frame`中算作2个`target`（不影响渲染效果）。

## GLTF扩展
GLTF模型可以使用[*扩展*](https://github.com/KhronosGroup/glTF/blob/main/extensions/README.md)，所使用的扩展会在.gltf文件内`extensionsUsed`和`extensionsRequired`节点下列出。

`extensionsUsed`中声明的扩展**非**必需。如果xr-frame目前不支持该扩展，`GLTF模型`会以基础样式渲染，并在console中生成相关warning。  
`extensionsRequired`中声明的扩展为必需。如果xr-frame目前不支持该扩展，`GLTF模型`将**不**会渲染，并在console中生成相关error。

### xr-frame已支持扩展
+ [KHR_materials_unlit](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_unlit/README.md)
+ [KHR_materials_clearcoat](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md)
+ [KHR_materials_ior](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_ior/README.md)
+ [KHR_materials_specular](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md)
+ [KHR_materials_pbrSpecularGlossiness](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness/README.md)
+ [SEIN_processed](https://seinjs.com/cn/overview)
+ *WX_processed_model
+ [KHR_draco_mesh_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_draco_mesh_compression/README.md)*(2.32.1及以后,仅限真机，工具上暂时无法加载)*  
+ *WX_compress_textures：支持压缩纹理，需要配合[xr-frame-toolkit](/miniprogram/dev/reference/xr-frame/component/tools/toolkit)一起使用，在基础库版本`3.0.1`以上支持。  
**号标注的扩展为xr-frame内部扩展，主要被xr-frame-cli使用。*  

## GLTF模型的限制
`GLTF模型`需要满足以下条件才能正常渲染：
+ 纹理使用的顶点`UV`不超过2个；
+ 使用的顶点`JOINTS`不超过1个；
+ 使用的顶点`WEIGHTS`不超过1个；
+ 不使用`sparse accessor`；
+ `accessor`的`normalized`属性不为`true`；
+ `morph targets`数量小于等于8个；
+ `morph`的属性为`POSITION`,`NORMAL`或`TANGENT`；
+ `图元类型`不为`LINE_LOOP`或`TRIANGLE_FAN`;

经测试，只有少数模型会超出限制，大多数模型都可以正常渲染。并且随着项目迭代，未来将会解除或者放宽一些条件。

### 渲染超出限制的模型
*(基础库2.31.0及以后)*  
如果模型超出上方限制，使用`GLTF组件`的时候会无法渲染，并在console中报错：（例）  
![[xr-frame] Error: GLTF validation failed at [AccessorNode]: [10602] Normalized accessors are not supported.](./images/2023-04-06-16-53-15.png)  
无论如何也想要渲染这个模型的话，可以在`<xr-asset-load>`标签中填写`ignoreError`属性：
```
<xr-asset-load type="gltf" asset-id="..." src="..." options="ignoreError: 10602"/>
```  
`ignoreError`属性可以接受一系列由空格分隔的数字，数字对应着console中模型报错信息里的编号。
> 可以用`ignoreError: -1`来忽略所有错误（不推荐）。
