本章作者:@maplor
书接上文
在上一篇文章《我在云栖写代码:打造沉浸式数字展厅(第一章)》 中,我们介绍了通过一套代码多端运行。今天为大家带来的是三维沉浸式展厅的技术解析以及转场动画的实现。
什么是三维沉浸式体验
举个栗子🌰:
结合转场动画进入三维展厅,进入后交互形式是在一个空间内进行左右滑动探索,并内置交互点,可进行点击下钻了解详情。
核心技术点
在前端实现上,分为两大部分:三维展厅和转场动画
三维展厅
实现原理
在交互上,用户虽然只能左右的滑动视角,但如果只是将场景贴图平铺,左右滑动,看起来视角会很“假”,没有透视的效果。所以我们需要构造一个真实的三维场景,以获得最好的体验效果。
设计师在 C4D 中设计的三维场景是这样的:
通过球形摄像机渲染的贴图:
我们可以在三维空间中制作一个球体,把贴图贴在球体的内侧,将摄像机放在球心,这样就能看到一个立体的效果:
代码示意
我们使用的是达摩院 XRLab 自研的渲染引擎来渲染三维场景。当然用 Three.js 渲染也很简单:
const geometry = new THREE.SphereGeometry( 5, 32, 16 );
const material = new THREE.MeshBasicMaterial();
material.side = THREE.BackSide;
material.map = new THREE.TextureLoader().load("贴图地址");
const sphere = new THREE.Mesh( geometry, material );
scene.add( sphere );
另外细心的小伙伴会发现画面是左右翻转的,这是因为贴图贴在物体的背面。可以预先对贴图做水平翻转,或者在 shader 中将贴图的 uv 翻转来修复这个问题:
// 顶点着色器
varying vec2 v_textureCoord;
// 源代码
v_textureCoord = textureCoord;
// 翻转水平uv
v_textureCoord = vec2(1.0 - textureCoord.x, textureCoord.y);
性能优化
前面我们实现了三维场景的展示,但在保证清晰度的情况下贴图的尺寸会非常大,而且展厅内容集中在一侧,我们并不需要把整个场景都展示出来。于是我们对展示的区域做裁剪,并限制交互转动的角度,示意图:
生成 Geometry 的时候只要增加后面的参数即可:
const width = Math.PI * 2 / 3;
const height = Math.PI / 4
const geometry = new SphereGeometry(
5,
32,
16,
(Math.PI * 3 / 2) - (width / 2),
width,
(Math.PI - height) / 2,
height
);
交互转动的限制也需要额外自行实现:
// 以场景的高度(弧度制)作为 fovY,计算的可用场景宽度 fovX
const fovX = 2 * Math.atan(canvasAspectRatio * Math.tan(height / 2));
// 得到合理的转动限制角度
const thetaLimit = Math.min(Math.PI, (width - fovX) / 2);
// 其余代码,设置限制角度等等
通过以上的工作,我们不仅实现了三维场景清晰的展示,而且每个场景的贴图大小控制在 300Kb 左右,可以非常快速的完成加载,保证用户体验流畅。
转场动画
转场动画在场景切换时展示,通过预先渲染的视频,连接各个场景,让访问者有连贯的体验感受。
一开始我们打算在前一个场景内渲染转场动画,在动画结束时跳转到下一个场景。示意图:
这样做在页面跳转时会有短暂的白屏和数据加载状态,那既然已经是一个单页应用,我们可以做的更好,于是有了这个方案,让转场动画显示在最顶层,不受页面跳转影响:
这个方案中用户全程不会看到加载状态,整个体验流程是连贯的,无中断的。
定下了方案,要实现就比较简单了。我们使用类似 Portal 的能力,将转场动画渲染到最顶层,并在动画播放到一半时切换路由,跳转到下一个场景。等动画播放结束后,顶层动画消失,用户看到的已经是下一个场景的页面。
// 渲染转场动画
function TransitionVideo({ to }) {
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
setShow(true);
let timer: NodeJS.Timeout;
if (videoRef.current) {
playVideo(videoRef.current, undefined, () => {
// 触发播放失败时直接跳转,并退出播放
clearTimeout(timer);
assign(to);
handleExit();
});
videoRef.current.addEventListener('ended', handleExit);
}
timer = setTimeout(() => {
assign(to);
}, 1000);
return () => clearTimeout(timer);
}, []);
// 退出播放
function handleExit() {
// ...other code
}
return (
// Portal 渲染 Video
)
}
经验 & 思考
在前端实现三维场景,相比普通的平面页面可以提供更好的视觉体验和沉浸式的体感,缺点则是开发成本提高。如果使用模型渲染,还需要加载模型,时间更长,造成用户流失。这里我们把场景渲染为贴图,并限制了交互能力,是我们选的效果和性能的平衡点。大家在自己的业务中需要选择合适的平衡点。
下集预告
本篇主要介绍了 沉浸式体验 相关的解法和实现。下一篇将是本系列的最后一篇 精益求精,打造页面的秒开体验和业务埋点的经验。
喜欢文章的朋友们记得持续关注我们哦👇~