封面图 by Thư Anh on Unsplash
去年全景图在微博上很是火爆了一阵,正好我也做过一点全景相关的项目,这些天抽空写下这一篇用Three.js来实现全景图的文章,和大家一起探讨。真的是抛砖引玉,还请包涵。
1. 开胃菜:用纯css实现一个伪全景图
先跟大家分享一个比较简单的例子作为开胃小菜,利用css3里的animation,我们可以让一张比较长的画卷,以匀速滚动的形式从左往右展现,代码如下:
.panorama {
width: 300px;
height: 300px;
background-image: url(./img.jpeg);
background-size: auto 100%;
animation: panorama 8s linear infinite;
}
@keyframes panorama {
to {
background-position: 100% 0;
}
}
2. 正餐:用Three.js实现全景图
尝了开胃菜,大家大概也能猜到全景图的原理是怎样的了,下面我们就来说说这道正餐。
Three.js是一个强大的开源项目,这里就不多做介绍了,我们主要会用到它的这几个功能:Camera(相机)、SphereBufferGeometry(球体)BoxGeometry(正方体)MeshBasicMaterial(材质)Mesh/Scene(场景)WebGLRenderer(渲染)
引入Three.js文件,压缩版的文件有140kb,只能说勉强能接受吧。
<script src=“https://threejs.org/build/three.min.js”></script>
先说一下全景图的实现原理:通过创造一个球体/正方体,并在其表面贴上专门的背景图,然后将相机放在球体/正方体的中心,监听手指拖动/陀螺仪移动来改变相机的面向,从而实现全景图。
相机:首先,我们要创造一个相机,并指明相机的面向。
// 相机
camera = new THREE.PerspectiveCamera(opt.fov, opt.width / opt.height, 1, 10000);
camera.target = new THREE.Vector3(0, 0, 0);
相机需要传的四个参数分别是fov(相机摄像角度,可用于放大和缩小)、width/height(宽高比)、neer(近距离界限)、far(远距离界限)。
生成球体:生成一个球体,并让相机位于它的中心。
// 球体
var geometry = new THREE.SphereBufferGeometry(opt.radius, 60, 60);
geometry.scale(-1, 1, 1);
添加材质: 把准备好的背景图添加到材质上。
// 材质
var material = new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load(opt.url)
});
创建场景:场景背景我设成了灰色,方便调试,如果不设的话默认是黑色。
// 场景
var mesh = new THREE.Mesh(geometry, material);
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
scene.add(mesh);
渲染:设置好dpr、画布宽高,Three.js就会生成一个canvas。
// 渲染
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(opt.width, opt.height);
canvas = renderer.domElement;
opt.container.appendChild(canvas);
监听滑动:通过监听touchstart、touchmove、touchend,来判断手指划过的距离,并以此计算出相机应该在x轴和y轴方向上各移动多少角度(一般不考虑z轴)。
// 监听
window.addEventListener(touchstart, start);
window.addEventListener(touchmove, move);
window.addEventListener(touchend, end);
// 开始触摸
function start(event) {
var evt = event.changedTouches[0];
startX = evt.clientX;
lastX = evt.clientX;
startY = evt.clientY;
lastY = evt.clientY;
}
// 滑动
function move(event) {
var evt = event.changedTouches[0];
switch (event.changedTouches.length) {
case 1:
lon += (evt.clientX – lastX) * factor;
lat += (evt.clientY – lastY) * factor;
lastX = evt.clientX;
lastY = evt.clientY;
break;
case 2:
// 如果要做放大缩小效果的话,可以在这里补充,我偷懒没写这种情况╮(╯▽╰)╭ }
}
// 结束
function end(event) {
// 这里可以添加减速效果增加流畅性,没错我又偷懒了
}
至于为何不管z轴,大家看看相机xyz轴分别对应的转动方向就能明白了。相机默认是由+z至-z拍摄,z轴转动只会让视线变斜。
监听陀螺仪:陀螺仪也分为x轴(beta:-180至+180)、y轴(Gamma:-90至90)、z轴(Alpha:0至360),如图:
插图来自公众号“交互设计前端开发”
插图来自公众号“交互设计前端开发”
插图来自公众号“交互设计前端开发”
我们要做的就是监听陀螺仪事件,并把变化的角度告诉相机(事先通过window,orientation判断是否支持陀螺仪)。
// 监听
.addEventListener(deviceorientation, orient);
function orient(event) {
orientLon = event.gamma;
orientLat = event.beta – 70; // 减90°让默认状态是手机直立,但一般人手机都会向后仰一点,所以我少减了20°
}
实时渲染:Three.js的requestAnimationFrame(实时渲染),能让canvas实时更新,无论是否有变化,我们利用这个特点,进行相机视角变化后的渲染。
// 动态渲染
var render = function () {
requestAnimationFrame( render );
lat -= orientLat;
lon -= orientLon;
camera.rotation.x = lat * Math.PI / 180;
camera.rotation.y = lon * Math.PI / 180;
camera.rotation.z = 0;
// camera.rotation.x = THREE.Math.degToRad(lat);
// Three.js自带有换算角度的方法,两种写法都可以
// camera.rotation.y = THREE.Math.degToRad(lon);
renderer.render(scene, camera);
};
render();
我这里用的是通过改变相机的角度(rotation)来达到改变视角的目的,还有一种实现方式是通过告诉相机一个点,让它把视线对准这个点,来改变视角。 当画图中有别的元素,并且要把视角对准它时,这个方法就更实用了。
camera.lookAt(x, y, z);
抛砖引玉:其实我说的这些,都只是最基本的内容,如果想要实现一个完美的全景图,还有很多地方可以改进,比如:
拖动结束时,可以加一个减速停止的效果,防止拖动出现抖动、不顺畅的情况;当角度大于90°或小于-90°时,让相机停止,不然画面会倒过来;实时渲染必定会让手机变成暖宝宝,有没有什么方法能优化性能呢?Three.js还提供了个方法cancelAnimationFrame,我们是不是能在视角未改变的时候停止渲染呢?resize的情况也可以兼容一下,可以使用camera.updateProjectionMatrix( );当event.changedTouches.length为2时,表示用户在用两指滑动,这时我们可以处理放大缩小的情况,通过改变camera的fov来实现;全景图的背景都是很大的,我们应该想办法去对它进行优化,比如我们使用正方体时,如果背景比较单一,那我们完全可以六个面统一用一个面的背景图,这样图片大小会大大缩减;通过监听mousedown、mousemove、mouseup兼容pc端;
3. 甜点:利用svg添加标签
在利用Three.js实现全景图后,我们能否做一些别的提升呢?
我在查找资料的时候,发现
有个网站
通过使用svg实现了在画布上添加内容的效果,大家可以参考一下。
通过svg的polygon、polyline、circle,能在画布上添加各种标签,并监听拖动事件,改变元素的translate3d、points,来达到标签跟随画布一同滚动的效果。
4. 小结:
相信大家都已经明白全景图的原理和实现逻辑了,我也列了几条可以优化的点,有兴趣深究的同学可以亲自去实现一下。因为这个demo是我闲暇之余做的,可能会有许多瑕疵,性能上也还有很多有待提升的空间,希望大家多包涵,也请大家多多指点。作者来自:
暂无评论内容