- GLTF格式介绍 – 面向不熟悉
GLTF格式的用户; - 画廊 – 由
xr-frame渲染的GLTF画廊; - GLTF使用说明 – 在
xr-frame里使用GLTF的方法与规则; - 模型优化工具
分类: 组件
后处理
后处理一般指一次渲染的最后环节,它接受一个相机的渲染结果,用图像处理算法对结果增强来达到一些效果,比如模糊、辉光、渐晕等等。
xr-frame内置的后处理系统以相机为基础,将后处理资源组装起来,实现最终的效果。
⚠️ 后处理系统在框架内部涉及到渲染管线,目前暂时不接受定制,未来视情况开放。
后处理资源
单个后处理资源对应后处理中的一个步骤,我们可以用以下几种方式来创建它:
在xml中创建
在XML中创建是最常用的使用方式
<xr-asset-post-process asset-id="blur" type="blur" is-hdr data="radius:10" />
可见我们指定了资源的asset-id、后处理类型type、是否为is-hdr和参数data。如此一来在资源系统中将会创建一个类型为blur、开启HDR、参数为{radius:10}的后处理资源blur。
在代码中创建
当然,如果有需要我们也可以使用代码创建:
const xrFrameSystem = wx.getXrFrameSystem();
scene.assets.addAsset('post-process', 'vignette', scene.createPostProcess({
type: 'vignette',
isHDR: false,
data: {
intensity: 0,
smoothness: 2,
color: [0 0 0 1]
}
}));
这里创建了一个类型为vignette的后处理资源。
在相机中配置
有了资源,只需要将它们配置给相机便可以开启后处理效果了:
<xr-camera
......
post-process="blur vignette"
/>
相机元素的post-process属性接受一个字符串数组,数组的每个元素都是后处理资源的id,它们会按照步骤顺序执行。
修改参数
有时候我们想修改后处理资源的参数来调整效果,有两种方式可以实现:
在代码中修改
一个最常用的方法就是在代码中拿到资源引用后修改:
const blur = scene.assets.getAssets('post-process', 'blur');
blur.data.radius = 20;
使用帧动画
另一种方法是使用帧动画,指定某帧的属性为后处理资源:
{
"keyframe": {
"blur": {
"0": {
"asset-post-process.assetData.radius": 10
},
"100": {
"asset-post-process.assetData.radius": 64
}
}
},
"animation": {
"parent": {
"keyframe": "blur",
"duration": 4,
"ease": "linear",
"loop": -1
}
}
}
内置后处理资源
目前框架内置支持的后处理资源类型和对应效果可见:内置后处理资源。
阴影
在前面网格一章中我们提到过阴影,阴影是灯光和这二者的协作结果,只有主平行光能够产生阴影,以如下场景为例:
<xr-mesh node-id="plane" position="0 -0.4 0" scale="5 0.2 5" geometry="cube" uniforms="u_baseColorFactor:0.48 0.78 0.64 1" receive-shadow
/>
<xr-gltf model="test-gltf" cast-shadow/>
<xr-camera
position="3 3 3" target="plane"
/>
<xr-light type="directional" rotation="60 0 0" color="1 1 1" intensity="2.5" cast-shadow />
可见,我们给plane这个xr-mesh开启了receive-shadow去接收阴影,然后开启了xr-gltf的cast-shadow来产生阴影,最后开启了主光源的cast-shadow总开关允许灯光产生阴影。
当然,在实际使用中会有一些需要关注的地方:
- 支持自己给自己投影,可以同时开启产生和接收阴影。
- 可能需要视情况调整
shadow-distance来避免阴影投影过大或者过小。 - 适当调整
shadow-bias来防止自阴影。
宏和uniforms
以下宏或者uniforms会被传递到着色器中:
| 类型 | 宏 | 说明 |
|---|---|---|
| 开启接受阴影 | WX_RECEIVE_SHADOW | bool |
可以直接通过以下方法来获取当前物体阴影:
| 方法 | 说明 |
|---|---|
float shadowCalculation(vec3 posWorld) | 传入世界坐标,返回阴影强度 |
天空盒
在相机的章节里我们提到过天空盒,当camera.background设置为skybox,将使用环境组件所定义的天空盒纹理,其由两个属性定义:
sky-map:天空盒纹理,默认由环境数据提供,但也可以接受用户使用此属性覆盖。可以使用任何纹理,包括视频纹理。is-sky2d:天空盒渲染模式是否为2D,开启时会渲染一个平铺自适应的2D背景,此时纹理应当是一张2D图。
uniforms和宏
和渲染相关的环境数据均已全局uniform和全局宏的方式传入着色器,它们是:
| 类型 | 宏 | 说明 |
|---|---|---|
| 开启环境漫反射 | WX_USE_IBL_DIFFUSE | bool |
| 开启环境高光反射 | WX_USE_IBL_SPECULAR | bool |
| 高光反射mipmaps数量 | WX_USE_SPECULAR_MIPMAPS | int |
| 高光反射使用RGBD | WX_USE_SPECULAR_RGBD | bool |
| 类型 | uniforms | 说明 |
|---|---|---|
| 漫反射系数 | u_diffuseSH | vec3[9] |
| 漫反射曝光值 | u_diffuseExp | texture2d |
| 高光反射纹理 | u_specularEnvMap | texture2d |
| 高光反射曝光值 | u_specularExp | v |
| 环境和天空盒旋转 | u_envRotation | float |
图集
图集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,width和height是渲染纹理的宽高。
手动代码创建
手动代码创建直接使用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-plane的uniforms中的一张纹理也设置为了rt。在绘制中就会用第一个相机先绘制到渲染纹理上,然后第二个相机绘制的物体中的那个平面在使用渲染纹理渲染。
注意这里我们用到了layer和cullMask,这个在前面的章节提到过。因为我们不想让第一个相机绘制那个平面,第二个相机则会绘制所有东西。
相机
相机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决定,声明是否使用透视投影,目前默认即为透视投影,关于这两者的区分开发者可以自行查阅。
无论是对于哪种投影类型,以下参数都是通用的:
- near:近裁剪平面,决定相机最近能看到多远的物体。
- far:远裁剪平面,决定相机最远能看到多远的物体。
在选择了透视投影的模式下,有以下参数可供调整:
- fov:视场角,角度,默认为60。
在选择了正交投影的情况下则有以下参数:
- orthSize:相机可视范围大小,是横向的尺寸,纵向会根据
aspect换算。
自定义矩阵
本质上来讲,投影方式和参数方式决定了投影矩阵,而相机所在元素挂载的transform组件则决定了视图矩阵,这里不再赘述这种基本概念,一般来讲都是框架自动计算的。但在某些高级需求,比如水面折射投影的渲染需求中,确实有可能要自己定制投影方式、甚至是定制视图矩阵的计算。为了解决这种需求,框架提供了方案。
// 修改投影矩阵
camera.changeProjectMatrix(manual, matrix);
// 修改视图矩阵
camera.changeViewMatrix(manual, matrix);
这两个方法可以用于修改投影或者视图矩阵,第一个参数manual用于控制开启或者关闭手动模式,第二个参数即为要设置的矩阵值。
渲染目标和视图
知道了要花那些物体、以及如何讲这些物体投影到平面上,还需要知道将这些东西最终画在什么上面,这就是渲染目标renderTarget。
renderTarget可选值为空或者渲染纹理资源,如果为空则会渲染到主屏上,渲染纹理详见对应的章节。
在渲染目标渲染前,我们需要先视情况将其清空,这主要由几个参数决定:
- isClearColor/clearColor:设置是否要在绘制前清除渲染目标的颜色,以及要清除为怎样的颜色。
- isClearDepth/clearDepth:设置是否要在绘制前清除渲染目标的深度,以及要清除为怎样的深度。
- isClearStencil/clearStencil:设置是否要在绘制前清除渲染目标的模板值,以及要清除为怎样的模板值。
背景
在清屏后我们可能需要为整个场景绘制一个背景,这时候便可以使用background数据,其可选以下几种类型:
default:默认行为,只清屏,不绘制背景。skybox:绘制天空盒,依赖于环境数据Env。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,加上不同的前缀,资源系统会自动匹配获取对应的资源:
- 不加前缀,取得普通纹理。
cube-前缀,取得立方体纹理资源。video-前缀,取得视频纹理。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参数。