# xr-frame里的GLTF


# GLTF资源

GLTF模型需要先通过Loader加载进小程序中,才可以渲染。

<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离线预处理模型可以解决这个问题。

# Loader选项

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

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

# GLTF组件

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

使用xr-gltf标签可以为标签自动创建GLTF组件

<xr-gltf id="myGLTF" model="gltfModel"></xr-gltf>

🚫 xr-gltf标签对应的元素为Shadow元素,所以请不要xml里为xr-gltf添加子标签。点击这里阅读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 根据GLTFNode节点的name字段来获取其下的所有Mesh组件 2.28.1版本加入;详见API文档
getPrimitivesByMeshName 根据GLTFMesh节点的name字段来获取所有相关的Mesh组件 2.28.1版本加入;详见API文档

# 例:使用TS脚本来修改GLTF贴图

以下代码在GLTF模型渲染完毕后修改其中某个mesh的贴图:

// gltf
{
    ...
    "nodes": [{
      "mesh": 0,
      "name": "Banana"
    }],
    ...
}
// xml
<xr-gltf id="myGLTF" model="gltfModel" bind:gltf-loaded="handleGLTFLoaded"></xr-gltf>
// 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内的所有动画片段:

<xr-gltf id="myGLTF" model="gltfModel" anim-autoplay></xr-gltf>

# 例:使用TS脚本来控制GLTF动画

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

例如以下代码在GLTF模型渲染完毕后播放名为idle的动画:

// xml
<xr-gltf id="myGLTF" model="gltfModel" bind:gltf-loaded="handleGLTFLoaded"></xr-gltf>
// ts
function handleGLTFLoaded({ detail }) {
    const el = detail.value.target;
    const animator = el.getComponent("animator");
    animator.play("idle");
}

动画的名字idle,对应的是.gltf文件中animations数组节点的每一个元素的name属性(参考官方文档)。

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

如果.gltf文件内有多个name相同的动画,也会使用这个规则,在名称后面添加#x作为动画名称,以此来区别。此时如果用不加#x的原始名称来索引动画,会返回#0的动画。

# Morph Target

xr-frame支持Morph Target动画,但是对target的数量有限制,同一个模型最多使用8target

这里的target概念有别于.gltf文件内的target节点:.gltf文件内的一个同时拥有POSITIONNORMAL属性的target节点,在xr-frame中算作2个target(不影响渲染效果)。

# GLTF扩展

GLTF模型可以使用扩展,所使用的扩展会在.gltf文件内extensionsUsedextensionsRequired节点下列出。

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

# xr-frame已支持扩展

# GLTF模型的限制

GLTF模型需要满足以下条件才能正常渲染:

  • 纹理使用的顶点UV不超过2个;
  • 使用的顶点JOINTS不超过1个;
  • 使用的顶点WEIGHTS不超过1个;
  • 不使用sparse accessor
  • accessornormalized属性不为true
  • morph targets数量小于等于8个;
  • morph的属性为POSITION,NORMALTANGENT
  • 图元类型不为LINE_LOOPTRIANGLE_FAN;

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

# 渲染超出限制的模型

(基础库2.31.0及以后)
如果模型超出上方限制,使用GLTF组件的时候会无法渲染,并在console中报错:(例)
[xr-frame] Error: GLTF validation failed at [AccessorNode]: [10602] Normalized accessors are not supported.
无论如何也想要渲染这个模型的话,可以在<xr-asset-load>标签中填写ignoreError属性:

<xr-asset-load type="gltf" asset-id="..." src="..." options="ignoreError: 10602"/>

ignoreError属性可以接受一系列由空格分隔的数字,数字对应着console中模型报错信息里的编号。

可以用ignoreError: -1来忽略所有错误(不推荐)。