如何用Three.js快速实现全景图

封面图 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是我闲暇之余做的,可能会有许多瑕疵,性能上也还有很多有待提升的空间,希望大家多包涵,也请大家多多指点。作者来自:

    THE END
    喜欢就支持一下吧
    点赞9 分享
    评论 抢沙发
    头像
    欢迎您留下宝贵的见解!
    提交
    头像

    昵称

    取消
    昵称表情代码图片

      暂无评论内容