图集

图集Atlas是一种资源,可以优化渲染流程和资源,在业界中用于精灵动画和UI比较多。它将一些散碎的小图拼接为一张大图,加之偏移数据uvMatrix或者uvST,能有效减少渲染时的纹理数量和切换次数。

目前图集在微信小程序框架中主要配合粒子系统使用。

创建图集

xr-frame使用的图集是 标准,提供了以下几种方式来创建:

工具离线生成,加载器创建

最推荐的方式是使用工具去离线生成图集,比如Shoebox,输出后将描述文件js后缀改为json即可。生成后的图像和描述文件大致为:

之后可以通过加载器加载,我们可以用标签来加载:

<xr-asset-load type="atlas" asset-id="numbers" src="/assets/numbers.json" />

也可以使用代码加载:

scene.assets.loadAsset({type: 'atlas', assetId: 'numbers', src: '/assets/numbers.png'});

代码创建

代码创建

除了加载器,还可以通过代码创建的方式,这里的options详见IAtlasCreationOptions:

// 通过配置创建
const atlas = xrFrameSystem.Atlas.CREATE_FROM_TEXTURE(scene, texture, options);

当然也有完全通过格子创建空图集的方式,详见CREATE_FROM_TEXTURE。

使用图集

加载完的图集提供了几个方法来供开发者在不同场景使用:

// 获取纹理
const texture = atlas.texture;

// 获取UV矩阵,matrix3
const uvMatrix = atlas.getUVMatrix(frameName);

// 获取UV缩放和偏移,[sx, sy, tx, ty]。
const uvST = atlas.getUVST(frameName);

渲染纹理

相机一章中提到了渲染目标renderTarget可以指定为渲染纹理,纹理也提到了渲染纹理可以作为纹理使用。可见,渲染纹理RenderTexture就是连接多个相机渲染之间的桥梁。

一个典型场景就是用渲染纹理来做镜子之类的效果。

创建渲染纹理

创建渲染纹理有两种方式,一般我们会优先在wxml中进行创建。

在wxml中创建

xml中创建需要用到xr-asset-render-texture标签:

<xr-asset-render-texture asset-id="rt" width="2048" height="1024" />

其中rt是资源id,widthheight是渲染纹理的宽高。

手动代码创建

手动代码创建直接使用scene.createRenderTexture即可:

const rt = scene.createRenderTexture({width: 2048, height: 2048});

// 当然,也可以添加到资源系统中
scene.assets.addAsset('render-texture', 'rt', rt);

使用渲染纹理

创建完渲染纹理后便可以使用它,下面是一个在xml中的简单使用实例,用代码使用也是类似的:

<xr-node layer="1">
  <xr-mesh id="cube" node-id="mesh-cube" position="-1 0.5 1.5" scale="1 1 1" rotation="0 45 0" geometry="cube" uniforms="u_baseColorFactor:0.298 0.764 0.85 1" />
  <xr-mesh node-id="mesh-sphere" position="0 1.25 0" scale="1.25 1.25 1.25" geometry="sphere" uniforms="u_baseColorFactor:0.937 0.176 0.368 1" />
  <xr-mesh node-id="mesh-cylinder" position="1 0.7 1.5" scale="1 0.7 1" geometry="cylinder" uniforms="u_baseColorFactor:1 0.776 0.364 1" />
</xr-node>

<xr-node layer="2">
  <xr-mesh node-id="mesh-plane" position="0 0 -6" rotation="-90 0 0" scale="16 1 12" geometry="plane" uniforms="u_baseColorMap:render-rt" />
</xr-node>

<xr-node node-id="rt-camera-target" position="0 1 0"></xr-node>
<xr-camera
  position="0 1 4" clear-color="0.925 0.925 0.925 1" target="rt-camera-target" render-target="rt" cull-mask="0b011"
/>

<xr-camera
 position="0 4 6" clear-color="0.925 0.925 0.925 1" target="mesh-plane" cull-mask="0b111" camera-orbit-control=""
></xr-camera>

第一个相机的render-target设置为了rt,然后mesh-planeuniforms中的一张纹理也设置为了rt。在绘制中就会用第一个相机先绘制到渲染纹理上,然后第二个相机绘制的物体中的那个平面在使用渲染纹理渲染。

注意这里我们用到了layercullMask,这个在前面的章节提到过。因为我们不想让第一个相机绘制那个平面,第二个相机则会绘制所有东西。

相机

相机Camera是渲染系统最核心的组件之一,和几乎所有的渲染引擎一样,它真正驱动着整个渲染管线的运作。相机组件一般被代理到相机元素XRCamera中使用,其派生自XRNode,对应在xml中的标签为xr-camera

相机组件也承载了一部分AR系统相关的能力,同时我们还提供了配套的相机控制器组件CameraOrbitControl来方便开发者进行相机控制。

相机元素

相机元素的一个典型使用方式如下:

<xr-node node-id="target" position="1 1 1" />
<xr-camera
  position="0 1 4" clear-color="0.4 0.6 0.7 1"
  background="skybox" target="target"
  camera-orbit-control=""
/>

这里面只列出了部分属性,实际上属性要多得多,下面就给大家一一举例。

以下属性均已组件数据驼峰命名给出,在元素中按照框架约定均转成**小写加-**的形式。

投影方式

首先是投影方式以及其相关的参数,投影方式由isPerspective决定,声明是否使用透视投影,目前默认即为透视投影,关于这两者的区分开发者可以自行查阅。

无论是对于哪种投影类型,以下参数都是通用的:

  1. near:近裁剪平面,决定相机最近能看到多远的物体。
  2. far:远裁剪平面,决定相机最远能看到多远的物体。

在选择了透视投影的模式下,有以下参数可供调整:

  1. fov:视场角,角度,默认为60。

在选择了正交投影的情况下则有以下参数:

  1. orthSize:相机可视范围大小,是横向的尺寸,纵向会根据aspect换算。

自定义矩阵

本质上来讲,投影方式和参数方式决定了投影矩阵,而相机所在元素挂载的transform组件则决定了视图矩阵,这里不再赘述这种基本概念,一般来讲都是框架自动计算的。但在某些高级需求,比如水面折射投影的渲染需求中,确实有可能要自己定制投影方式、甚至是定制视图矩阵的计算。为了解决这种需求,框架提供了方案。

// 修改投影矩阵
camera.changeProjectMatrix(manual, matrix);

// 修改视图矩阵
camera.changeViewMatrix(manual, matrix);

这两个方法可以用于修改投影或者视图矩阵,第一个参数manual用于控制开启或者关闭手动模式,第二个参数即为要设置的矩阵值。

渲染目标和视图

知道了要花那些物体、以及如何讲这些物体投影到平面上,还需要知道将这些东西最终画在什么上面,这就是渲染目标renderTarget

renderTarget可选值为空或者渲染纹理资源,如果为空则会渲染到主屏上,渲染纹理详见对应的章节。

在渲染目标渲染前,我们需要先视情况将其清空,这主要由几个参数决定:

  1. isClearColor/clearColor:设置是否要在绘制前清除渲染目标的颜色,以及要清除为怎样的颜色。
  2. isClearDepth/clearDepth:设置是否要在绘制前清除渲染目标的深度,以及要清除为怎样的深度。
  3. isClearStencil/clearStencil:设置是否要在绘制前清除渲染目标的模板值,以及要清除为怎样的模板值。

背景

在清屏后我们可能需要为整个场景绘制一个背景,这时候便可以使用background数据,其可选以下几种类型:

  1. default:默认行为,只清屏,不绘制背景。
  2. skybox:绘制天空盒,依赖于环境数据Env。
  3. ar:当开启了AR系统后,将会绘制设备摄像头的内容,详见AR相关。

跟随目标

框架默认集成了基本的了相机跟随控制能力,只需要设置target即可,在xml中这是一个节点的node-id,本质上就是一个transform组件。

指定了它之后,相机在移动时,的前向将始终朝向target

深度和掩码

除了通过平截体剔除确定哪些物体要被渲染之外,还需要一种直接精确的剔除方案,这就是掩码(cullMask)。这个实际上已经在可见性与图层中论述过。

除了单相机的渲染,很多时候会有很多个相机,它们之间也可能会有一定的顺序,这时候就可以使用depth来调整这个顺序,越大渲染顺序越靠后。在同一depth下,以标签编写的前后顺序为准。

AR相关

在开启了AR系统后,相机便可以使用AR相关的能力。

其一就是background指定为ar后,场景背景会绘制当前设备摄像机的画面。

第二是isARCamera数据,将其开启后在某些AR系统的模式下将会自动设置视图和透视矩阵,来匹配现实中设备摄像机的位置和视角。

非常需要注意,当开启isARCamera并且AR系统为Plane或者Marker模式时,不能同时设置target数据!!!

后处理

数据postProcess用于指定相机需要用到的后处理资源数组,详见后处理。

相机控制器

相机控制器组件提供了一个简单的绕着相机target旋转的控制方式,一般而言只需要在元素上添加camera-orbit-control属性即可。当然相机控制器也提供了丰富的配置参数,如果想要调节可见API文档CameraOrbitControl。

使用纹理

加载或者创建了纹理后,便可以在组件或者材质的uniforms中使用。但通过以上的章节,我们知道了纹理的种类有许多,虽然组件数据可以通过指定具体的类型,比如cube-texture来取得具体的类型的资源,但对于uniforms来说是无法判断的,同时也比较繁琐。

为了解决这个问题,我们遵循约定大于配置的原则,对于所有这些纹理资源,开发者只需要将组件数据类型指定为texture,加上不同的前缀,资源系统会自动匹配获取对应的资源:

  1. 不加前缀,取得普通纹理。
  2. cube-前缀,取得立方体纹理资源。
  3. video-前缀,取得视频纹理。
  4. render-前缀,取得渲染纹理。

如果是应用于uniforms中,开发者不需要自己去关心它们的区别,只需要uniforms="u_baseColorMap:video-vt"这样即可,但如果是自定义组件数据,就需要开发者自己处理一下了:

onUpdate(data: {texture: XrFrame.Texture | XrFrame.ITextureWrapper}) {
  const realTex = xrFrameSystem.isTextureWrapper(data.texture)
    ? data.texture.texture
    : data.texture;
}

视频纹理

有时候我们需要将视频放入场景中进行渲染,使用视频纹理VideoTexture资源就可以实现这个需求。视频纹理本质上是创建一个普通纹理,然后定时用视频解码数据对它进行更新。

通过加载器

视频纹理的创建也可以通过加载的方式创建。用xml加载:

<xr-asset-load
  type="video-texture" asset-id="vt" src="/assets/video.mp4"
  options="autoPlay:true,loop:true,abortAudio:false,placeHolder:/assets/video.jpg"
/>

或是使用代码加载:

scene.assets.loadAsset({
  type: 'video-texture', assetId: 'vt', src: '/assets/video.mp4',
  options: {autoPlay: true}
});

注意到视频纹理的几个选项,autoPlay开启后视频加载成功时会自动播放,loop开启时会循环播放,abortAudio用于指定是否要禁止声音(默认禁止),placeHolder则是作为视频尚未加载成功时的一个占位图,可选。

特别注意,placeHolder的尺寸必须和视频完全一致!!!

代码创建

视频纹理也可以手动在代码中创建:

const vt = await createVideoTexture({src, autoPlay, loop, placeHolder});

注意这是个异步方法,在有placeHolder时会在图片加载完毕时返回,否则将在视频准备好时返回。

控制

视频纹理对于开发者而言主要是视频,所以我们提供了一些用于控制视频播放的方法:

// 开始播放,异步方法
await vt.play();

// 从`pos`秒开始播放,异步方法
await vt.seek(pos);

// 停止播放
vt.stop();

// 释放视频
vt.release();

// 在播放结束并且非loop的情况下,会执行
vt.onEnd = () => {};

// 在基础库`v2.33.0`及以上,提供了暂停/唤醒方法
// 同时可以配合新暴露的播放状态使用
const xrSystem = wx.getXrFrameSystem();
if (vt.state === xrSystem.EVideoState.Playing) {
  vt.pause();
} else if (vt.state === xrSystem.EVideoState.Paused) {
  vt.resume();
}

注意,如果是自己创建的视频资源,请务必自己调用release方法释放!!!

立方体纹理

立方体纹理CubeTexture是一种特殊的纹理,一般用于实现天空盒或者环境贴图,虽然在微信小程序框架中一般使用普通全景纹理,但仍然提供给开发者一个选择。

通过加载器

立方体纹理的创建也可以通过加载的方式创建。用xml加载:

<xr-asset-load
  type="cube-texture" asset-id="sky" src="/assets/sky/"
  options="faces: right.jpg left.jpg top.jpg bottom.jpg front.jpg back.jpg,wrapU:1,wrapV:2"
/>

或是使用代码加载:

scene.assets.loadAsset({
  type: 'cube-texture', assetId: 'sky', src: '/assets/sky/',
  options: {faces: ['right.jpg', 'left.jpg', 'top.jpg', 'bottom.jpg', 'front.jpg', 'back.jpg']}
});

options中除了faces用于配置每个面的纹理地址,其他参数和TextureLoader一致。

代码创建

也可以代码创建,通过注册加入资源系统:

function createGreenCubeTexture(scene: XrFrame.Scene) {
  const buffer = new Uint8Array([0, 1, 0, 1]);
  return scene.createTexture({
    type: xrFrameSystem.ETextureType.Cube,
    slices: 6,
    source: [buffer, buffer, buffer, buffer, buffer, buffer],
    pixelFormat: xrFrameSystem.ETextureFormat.RGBA8,
    width: 1,
    height: 1,
    magFilter: xrFrameSystem.EFilterMode.NEAREST,
    minFilter: xrFrameSystem.EFilterMode.NEAREST,
    wrapU: xrFrameSystem.EWrapMode.CLAMP_TO_EDGE,
    wrapV: xrFrameSystem.EWrapMode.CLAMP_TO_EDGE,
    anisoLevel:1
  });
}

// 可以将其注册到资源系统
xrFrameSystem.registerCubeTexture('green', createGreenCubeTexture);

可见其实它也是一种普通的纹理,只不过类型不一样。

立方体纹理也支持更新,和普通纹理基本一致,只不过必须提供slice参数。

网格

网格Mesh是一种组件,严格来讲应该称为网格渲染器,但出于精简就这么命名了。其作用是组织起几何数据和材质实现渲染的载体。

创建一个网格

网格一般在xml中创建,可以直接以组件模式使用或者是用代理的封装元素xr-mesh,此封装元素派生自节点。

组件的方式:

<xr-node mesh="geometry:cube;material:mat" />

元素的模式:

<xr-mesh geometry="cube" uniforms="u_baseColorMap:waifu" states="cullOn:false" cast-shadow receive-shadow />

以上案例我们可以看到网格的所有数据,其中geometry是必传的,值为注册过的几何数据的资源id。material是创建过的材质的资源id,如果不写则会默认使用一个标准的使用了standard效果的材质。

uniformsstates比较特殊,它们其实是材质的数据,但为了方便开发者使用,不去给每一个网格都创建一个材质,所以我们提供了这样的一种方式。在这种方式下,会自动拷贝一份材质,对材质的修改不会影响到原始材质,同时对原始材质的修改也不会影响到这个网格,这点一定要注意!

最后的castShadowreceiveShadow是阴影相关的数据,详细可见灯光中相关的部分。

材质

材质 Material 基于 效果 Effect,提供了修改渲染状态、Uniforms 的接口,真正决定了物体最后的渲染方式,体现为物体表面的外观。

创建一个材质

创建材质有两种方式,一般我们会优先在wxml中进行创建。

在wxml中创建

xml中创建需要用到xr-asset-material标签:

<xr-asset-material asset-id="mat" effect="standard" uniforms:"u_baseColorMap: waifu" states="alphaMode:BLEND" renderQueue="2500" />

其中asset-id是会添加到资源系统中的ideffect是已经注册过的效果,这两个参数是必填的。uniforms类型是dict,用于覆盖效果中定义的那些属性,会根据那些属性的type来进行解析,比如Vector2对应1 1这样的数组;states类型也是dict,用于覆盖效果中定义的那些渲染状态(需要效果中开启useMaterialStates);最后的renderQueue用于覆盖效果中的默认渲染顺序,大于等于2500为透明物体。

用代码创建

有时候我们需要用代码创建材质,也很简单:

// 第一个参数是效果实例的引用,第二个参数是默认`uniforms`
const mat = scene.createMaterial(
  // 使用内置的 Standard 效果
  scene.assets.getAsset('effect', 'standard'),
  {u_baseColorMap: scene.assets.getAsset('texture', 'waifu')}
);

// 可以将其添加到资源系统中备用
scene.assets.addAsset('material', 'test-mat', mat);

创建完,接下来就是使用了。

使用材质

材质和其他资源一样,可以通过在组件schema中设定数据类型为material,然后在xml中通过资源id索引,或者用setData直接使用。目前使用了材质的组件只有网格,详见对应章节。

除了被引用外,材质也提供了一些方法来对其进行修改:

// 一系列修改Uniforms的接口
material.setFloat(key, value);
material.setVector(key, vec);
material.setMaterial(key, mat);
material.setTexture(key, texture);

// 修改渲染状态
material.setRenderState(key, value);

// 修改宏
material.setMacros({key: value});

// 重置图片,同时关闭对应的宏
material.resetTexture(key);

// 重置状态
materia.clearRenderState(key);

材质支持渲染状态列表

状态名称 说明 类型
renderQueue 渲染顺序 number
cullOn 是否开启剔除 bool
depthTestOn 是否开启深度测试 bool
depthTestWrite 是否开启深度写入 bool
alphaMode 透明模式 ‘OPAQUE’ ‘BLEND’ ‘MASK’
alphaCutOff 是否开启透明剔除 bool
depthTestComp 深度测试方法 number, ECompareFunc对应的值
stencilTestOn 是否开启模板测试 boolean
stencilComp 模板测试相关 number, ECompareFunc对应的值
stencilRef 模板测试相关 number
stencilReadMask 模板测试相关 number
stencilWriteMask 模板测试相关 number
stencilPass 模板测试相关 number, EStencilOp对应的值
stencilFail 模板测试相关 number, EStencilOp对应的值
stencilZFail 模板测试相关 number, EStencilOp对应的值
colorWrite 颜色通道写入掩码,基础库v2.31.1开始支持 number, 一个4bits的mask,由高到低为ABGR四个通道,比如0b1001表示只开启RA通道写入

效果

几何数据 提供了渲染的原材料,材质 决定了渲染的方式,但在讨论材质之前,我们要讨论一下其基于的效果 Effect。

效果可以认为是一个材质模板,通过光照模式lightMode宏定义definition来对模板的各个功能进行开关。

内置效果

详见 内置效果资源。

定制一个效果

function createSimpleEffect (scene: XRFrame.Scene) {
  return scene.createEffect({
    name: 'simple',
    properties: [
      {
        key: 'u_baseColorFactor',
        type: xrFrameSystem.EUniformType.FLOAT4,
        default: [1, 1, 1, 1]
      }
    ],
    images: [
      {
        key: 'u_baseColorMap',
        default: 'white',
        macro: 'WX_USE_BASECOLORMAP'
      }
    ],
    // 透明物体需要大于`2500`!
    defaultRenderQueue: 2000,
    passes: [
      {
        renderStates: {
          cullOn: true,
          blendOn: false,
          depthWrite: true,
          cullFace: xrFrameSystem.ECullMode.BACK,
        },
        lightMode: 'ForwardBase',
        useMaterialRenderStates: true,
        shaders: [0, 1]
      }
    ],
    shaders: [`#version 100

    attribute vec3 a_position;
    attribute highp vec2 a_texCoord;

    uniform mat4 u_view;
    uniform mat4 u_projection;
    uniform mat4 u_world;

    varying highp vec2 v_uv;

    void main()
    {
      v_uv = a_texCoord;
      gl_Position = u_projection * u_view * u_world * vec4(a_position, 1.);
    }`,
      `#version 100
    precision mediump float;

    uniform highp vec4 u_baseColorFactor;
    #ifdef WX_USE_BASECOLORMAP
      uniform sampler2D u_baseColorMap;
    #endif

    varying highp vec2 v_uv;

    void main()
    {
  #ifdef WX_USE_BASECOLORMAP
      vec4 baseColor = texture2D(u_baseColorMap, v_Uv) * u_baseColorFactor;
  #else
      vec4 baseColor = u_baseColorFactor;
  #endif

      gl_FragData[0] = baseColor;
    }  
      `],
  });
}

在效果的参数中,首先是其名字name,然后是其能允许的属性propertiesimages

定义属性

属性对应后续生成材质的uniforms的定义。每个property都需要一个键key、类型type和对应于类型的默认值,而每个image则不需要type、默认值也是已经注册到了资源系统的纹理的资源id

这里特别要注意在那个marco参数,它定义了当用户修改设置(通过材质的接口)了这个属性后,将会开启的宏,这个宏会直接应用到着色器中。

注意,所有的外部设置的宏都必须以WX_开头 为了让整个渲染正常运作,微信小程序框架内置了一些属性和宏,可见文章末尾的附录。

渲染队列

defaultRenderQueue定义的渲染队列,决定了渲染顺序,将在下一章材质中详细论述。

透明物体需要大于2500

配置Pass

Pass对应的是使用了基于这个效果的材质的网格的一次渲染,这个和相机组件相关,使用哪个pass渲染取决于相机当次绘制指定的lightMode

lightMode是光照模式,目前建议只能使用一个模式ForwardBase。当存在多个pass都使用了同一个光照模式时,将会按顺序将网格渲染多次。

renderStates顾名思义即渲染状态,渲染状态直接对应底层图形API都会提供的一系列概念,比如混合blend、深度depthTest等,比如示例里就定义了blendOn: false,即表明要关掉混合

更多的渲染状态可见API文档IRenderStates,这里不赘述,这里主要要说明一个规则,即useMaterialRenderStates这个参数的作用。

xr-frame框架的设计中,渲染状态是由效果决定默认值,材质决定最终值的,而useMaterialRenderStates则就是用于决定材质是否能够覆盖效果默认值的,如果为false,则总是使用效果提供的默认值,如果为true,则由下面会讲到材质的渲染状态的规则来决定。

在配置完这些之后,就是要定制shaders了。这是一个有两个数字的数组,分别指定了顶点着色器片段着色器在顶层参数中的shaders的索引。

编写着色器

效果配置的顶层参数中最后一项是shaders,这是一个字符串数组,每个元素都是一个完整的着色器。着色器使用标准的glsl1语法,这个不属于教程范畴不再赘述。

我们提供了一些内置可用的着色器的函数,在文章末尾会给出。

注册到资源系统

将效果注册到资源系统可用:

xrFrameSystem.registerEffect('custom', createSimpleEffect);

注册后在schema中类型指定为effect的数据,便可以使用custom这个资源id引用到创建的资源了。

几何数据

渲染的基础之一是几何数据Geometry资源,它描述了一个模型的顶点信息、索引信息以及顶点的存取结构。

一般来讲,几何数据是通过模型中自动加载,或使用 内置Geometry,但有时候我们需要去定制一些程序化生成的数据比如粒子等等,所以还是要明白如何去定制。

定制一个看看

const geometry = scene.createGeometry(
  vertexLayout, vertexBuffer,
  indexBuffer, indexType
)

可见构造一个Geometry需要提供好几个参数,他们是:

VertexLayout

首先是VertexLayout,用于描述顶点布局,举个例子来说:

const layout = new xrFrameSystem.VertexLayout({
  attributes: [
    {
      name: 'a_position',
      format: xrFrameSystem.EVertexFormat.FLOAT2,
      offset: 0,
      usage: xrFrameSystem.EVertexLayoutUsage.POSITION,
    },
    {
      name: 'a_texCoord',
      offset: 8,
      format: xrFrameSystem.EVertexFormat.FLOAT2,
      usage: xrFrameSystem.EVertexLayoutUsage.UV0,
    },
    {
      name: 'a_color',
      format: xrFrameSystem.EVertexFormat.UBYTE4,
      offset: 16,
      usage: xrFrameSystem.EVertexLayoutUsage.COLOR,
    }
  ],
  stride: 20
});

这里定义了一个自定义的VertexLayout,其中有两个参数:

  1. attributes:描述了顶点的结构,即如何GPU将如何理解传入的顶点数据,比如第一个元素,其表示此顶点属性在shader中名字为a_position,格式是FLOAT2,在顶点Buffer中偏移为0,并且用做POSITION
  2. stride:描述了每个顶点所占带宽的字节数。
  3. usage会影响到渲染这些数据时开启的宏,详见内置效果。

Buffer和Type

后面三个参数分别是顶点数据索引数据索引格式,顶点数据按照布局结构存储着整个Geometry的顶点数据,索引数据存储着图元对顶点的索引,而索引格式则是决定了索引数据的存储格式,其可以为UINT16或者UINT32,如果为UINT16,则索引数据中的顶点索引值不得超过65535

设置参数

在创建完一个新的几何数据后,开发者还需要设置一些参数来让它正确得运作起来,主要是SubMesh和包围球/包围盒。

SubMesh

有了数据和布局后,几何数据还需要提供一些信息去让渲染器知道如何使用这些数据,我们提供了叫做SubMesh的抽象,来将Geometry分割为数个部分:

// 添加一个SubMesh,其索引数据长度为`length`个顶点,第一个索引偏移为`offset`
geometry.addSubMesh(length, offset);

// 修改第`subMeshIndex`位置的SubMesh信息
geometry.modifySubMesh(subMeshIndex, length, offset);

这样分割的原因主要是即便是同一个几何数据,也可能拥有不同的材质来处理不同的部分,但是出于综合考虑,目前渲染时几何数据和材质是一一对应的,所以只支持一个SubMesh

包围球和包围盒

对于每个几何数据来说,在相机的剔除阶段,都需要一个包围球/包围盒来决定其是否在可视范围内,开发者可以通过以下方案来设置包围球:

// `center`为球心相对于模型原点的偏移,`radius`是球的半径
geometry.setBoundBall(center, radius);

或是设置包围盒:

// `center`为球心相对于模型原点的偏移,`size`是盒的三维长度,默认会同步更新包围球
geometry.setBoundBox(center, size);

更新数据

在某些场合,开发者还会需要去动态更新顶点或索引数据,比如粒子系统,我们也提供了一些方案来完成这种需求(注意这种更新操作只能在Mesh已经被提交到GPU后使用):

// 从字节偏移`offset`开始,将`buffer`整个更新到顶点数据。
geometry.uploadVertexBuffer(offset, buffer);

// 从字节偏移`offset`开始,将`buffer`整个更新到索引数据。
geometry.uploadIndexBuffer(offset, buffer);

之后便可以使用这个几何数据了。

注册到资源系统

和其他资源一样,我们提供了一套注册机制来让开发者定制在xml中也可以被引用的几何数据:

xrFrameSystem.registerGeometry('custom', scene => {
  return scene.createGeometry(
    vertexLayout, vertexBuffer,
    indexBuffer, indexType
  );
});

注册后在schema中类型指定为geometry的数据,便可以使用custom这个资源id引用到创建的资源了。