H5案例分享:3D全景之ThreeJs
3D全景之ThreeJs
一、前言
随着H5越来越多的被应用到各个领域,3D也越来越频繁的出现在各个H5案例中,今天我们就来讨论一下3D全景的实现。
据百度百科上介绍:720全景是视角超过人的正常视角的图像。顾名思义就是给人以三维立体感觉的实景360度全方位图像。全景实际上只是一种对周围景象以某种几何关系进行映射生成的平面图片,只有通过全景播放器的矫正处理才能成为三维全景(全景特指水平360度,上下360度全能观看的,能看到“天、地”的全景)。
二、全景图分类
首先我们来看一下全景图的分类:
全景图共分为三种:
1、球面全景图
利用一张全景图围成一个球,自身位置位于球体内。由于图片是矩形,所以最上和最下的缝合处很明显就能够看得出来。
虽然球面全景图具有和人眼最接近的构建模式,但需要很多个立面才可以构建成一个球体,球面的经纬度坐标无法展开成一个平面贴图,相对于其他方案,性能消耗过高,拼接方法过于繁琐,性能消耗高。
2、柱状全景图
这个则是前两种构建模式的结合版啦。其实, 粗略一想就可以想到这种实现方法的缺点, 如果我们将拍摄到的全景图贴在一个圆柱的侧面上, 我们站在圆柱中心朝四周看的话, 应该就有全景观察的效果. 不过这样做也有坏处, 也就是我们的头顶跟脚底都是无法看到的区域.
3、立方体全景图
一个立方体,有六个面组成,所以就需要六张图片啦。自身的位置位于立方体中间。这也是最常见的全景图构建模式。
通过比较,显而易见,采用立方体全景图,无论体验还是性能都比较好。那具体到使用ThreeJS实现全景图这个场景, 我们需要做什么呢?
三、用ThreeJs实现立方体全景图
html结构:
<div id="controlBtn" class="controlBtn controlIconae"></div> <!-- 陀螺仪开启按钮 -->
<div id="pano">
<!-- 正方体的六个面 -->
<div id="bg_section_0" class="bg_section bg_section_4 scale_test">
<img class="bg" src="images/panorama.right.jpg" alt="">
<div class="btn1 bounceant1"></div> <!-- 可点击按钮1 -->
</div>
<div id="bg_section_1" class="bg_section bg_section_5 scale_test">
<img class="bg" src="images/panorama.left.jpg" alt="">
</div>
<div id="bg_section_2" class="bg_section bg_section_2 scale_test">
<img class="bg" src="images/panorama.top.jpg" alt="">
</div>
<div id="bg_section_3" class="bg_section bg_section_3 scale_test">
<img class="bg" src="images/panorama.bottom.jpg" alt="">
</div>
<div id="bg_section_4" class="bg_section bg_section_1 scale_test">
<img class="bg" src="images/panorama.front.jpg" alt="">
<div class="btn2 bounceant1"></div> <!-- 可点击按钮2 -->
</div>
<div id="bg_section_5" class="bg_section bg_section_0 scale_test">
<img class="bg" src="images/panorama.back.jpg" alt="">
</div>
</div>
JS:
引用的JS库:
<script src="js/zepto.min.js"></script>
<script src="js/three.min.js"></script>
<script src="js/CSS3DRenderer.min.js"></script>
<script src="js/DeviceOrientationControls.js"></script>
核心js代码实现步骤:
1、创建场景,即是画布,是所有物体object的容器。在最开始的时候对场景实例化,将之后构建的物体都添加到场景中即可。
/**
* 创建场景
* @type {THREE.Scene}
*/
scene = new THREE.Scene();
2、添加相机
在ThreeJS中, 相机还分为CubeCamera(立方体相机),PerspectiveCamera(透视相机)以及OrthographicCamera(正交相机)。其中, CubeCamera是创建动态贴图用的,OrthographicCamera创建的照相机不具有透视效果。 在这里, 我们用到的是PerspectiveCamera(透视相机)。 如下图所示, fov是相机视角的夹角,aspect等于相机画幅比例,near和far分别是照相机到视景体最近、最远的距离, 均为正值,且far应大于near。
/**
* 添加相机
* @type {THREE.PerspectiveCamera}
*/
camera = new THREE.PerspectiveCamera(
75, // 相机视角的夹角
window.innerWidth / window.innerHeight, // 相机画幅比
1, // 最近焦距
1000 // 最远焦距
);
3、添加渲染器
渲染器是用来设定渲染的结果会在页面的什么元素上面呈现,以及按什么规则来渲染。这里使用的是CSS3DRenderer渲染器。
/*
* 添加渲染器
*/
renderer = new THREE.CSS3DRenderer(); // 定义渲染器
renderer.setSize( window.innerWidth, window.innerHeight ); // 定义尺寸
document.body.appendChild( renderer.domElement ); // 将场景到加入页面中
4、创建正方体
立方体全景图有6个面,我们需要定义每个面贴图的背景图片,3D位置,旋转角度(默认的6个面都是朝着我们的,我们需要定义朝坐标轴的各个方向做90度的旋转,才可以搭建成一个立方体)。
/**
*正方体的6个面的资源及相关(坐标、旋转等)设置
*/
var flipAngle = Math.PI, // 180度
rightAngle = flipAngle / 2, // 90度
tileWidth = 512;
var sides = [{
url: "images/panorama.right.jpg", //right
position: [-tileWidth, 0, 0],
rotation: [0, rightAngle, 0]
}, {
url: "images/panorama.left.jpg", //left
position: [tileWidth, 0, 0],
rotation: [0, -rightAngle, 0]
}, {
url: "images/panorama.top.jpg", //top
position: [0, tileWidth, 0],
rotation: [rightAngle, 0, Math.PI]
}, {
url: "images/panorama.bottom.jpg", //bottom
position: [0, -tileWidth, 0],
rotation: [-rightAngle, 0, Math.PI]
}, {
url: "images/panorama.front.jpg", //front
position: [0, 0, tileWidth],
rotation: [0, Math.PI, 0]
}, {
url: "images/panorama.back.jpg", //back
position: [0, 0, -tileWidth],
rotation: [0, 0, 0]
}];
for ( var i = 0; i < sides.length; i ++ ) {
var side = sides[ i ];
var element = document.getElementById("bg_section_"+i);
element.width = 1026;
element.height = 1026; // 2 pixels extra to close the gap.
// 添加一个渲染器
var object = new THREE.CSS3DObject( element );
object.position.fromArray( side.position );
object.rotation.fromArray( side.rotation );
scene.add( object );
}
5、实时渲染函数
这里我们用的是Threejs的 实时渲染 :就是需要不停的对画面进行渲染,即使画面中什么也没有改变,也需要重新渲染。其中一个重要的函数是requestAnimationFrame,这个函数就是让浏览器去执行一次参数中的函数,这样通过上面animate中调用requestAnimationFrame()函数,requestAnimationFrame()函数又让animate()再执行一次,就形成了我们通常所说的渲染循环了。
/**
* 实时渲染函数
*/
function animate() {
requestAnimationFrame(animate);
// lon = Math.max(-180, Math.min(180, lon));//限制固定角度内旋转
// lon += 0.1;//自动旋转
lat = Math.max(-85, Math.min(85, lat)); //限制固定角度内旋转
phi = THREE.Math.degToRad(85 - lat);
theta = THREE.Math.degToRad(lon+180);
target.x = Math.sin(phi) * Math.cos(theta);
target.y = Math.cos(phi);
target.z = Math.sin(phi) * Math.sin(theta);
camera.lookAt( target );
camera.updateProjectionMatrix();
isDeviceing == false ? initMouseControl() : deviceControl.update();
renderer.render(scene, camera);
}
6、窗口改变时对camera焦点的处理
/**
* 窗体大小改变
*/
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
7、相机焦点跟着鼠标或手指的操作移动
/*
相机焦点跟着鼠标或手指的操作移动
*/
function onDocumentMouseDown( event ) {
event.preventDefault();
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
}
function onDocumentMouseMove( event ) {
var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
lon -= movementX * 0.1;
lat += movementY * 0.1;
}
function onDocumentMouseUp( event ) {
document.removeEventListener( 'mousemove', onDocumentMouseMove );
document.removeEventListener( 'mouseup', onDocumentMouseUp );
}
/**
* 鼠标滚轮改变相机焦距
*/
function onDocumentMouseWheel( event ) {
camera.fov += event.deltaY * 0.05;
camera.updateProjectionMatrix();
}
function onDocumentTouchStart( event ) {
event.preventDefault();
var touch = event.touches[ 0 ];
touchX = touch.screenX;
touchY = touch.screenY;
}
function onDocumentTouchMove( event ) {
event.preventDefault();
var touch = event.touches[ 0 ];
lon -= ( touch.screenX - touchX ) * 0.1;
lat += ( touch.screenY - touchY ) * 0.1;
touchX = touch.screenX;
touchY = touch.screenY;
}
8、绑定陀螺仪
var controlsBtn= document.getElementById("controlBtn"); // 控制陀螺仪开关的按钮
var isDeviceing = false; // 陀螺仪状态
controlsBtn.addEventListener("touchend", controlDevice, true);
isDeviceing == true ? $("#controlBtn").addClass("controlIconae") : $("#controlBtn").addClass("controlIcon");
// 初始化陀螺仪
function initDevices() {
deviceControl = new THREE.DeviceOrientationControls(camera);
}
/* 控制陀螺仪 */
function controlDevice(event) {
if (isDeviceing == true) {
isDeviceing = false;
//关闭陀螺仪
$("#controlBtn").removeClass("controlIcon").addClass("controlIconae");
} else {
isDeviceing = true;
//开启陀螺仪
$("#controlBtn").removeClass("controlIconae").addClass("controlIcon");
}
}
9、给全景里的物体加点击事件
/**
*点击按钮
*/
$('.btn1').on('touchstart',function(){
alert('第一个按钮被点击了');
});
$('.btn2').on('touchstart',function(){
alert('第二个按钮被点击了');
});
DEMO演示
五、注意事项
重力感应在安卓下有时候体验非常不好,所以我做的这个DEMO在iOS和Android下都支持手势操作,想体验重力感应的小伙伴可以点击左上角的按钮,选择开启重力感应或关闭重力感应。
标签
关注微信号:h5-share,获取更多创意H5案例分享!也可访问H5案例分享网站www.h5anli.com,搜索查阅~
微信扫一扫
关注该公众号