事情是这样的,前几天我接到一个外包工头
的新需求,某品牌要搭建一个在线VR展厅,用户能够在手机上经过陀螺仪或者拖动来360度全景参观展厅,这个VR展厅里会有一些信息点,点击以后能够呈现更多信息(视频,图文等)...javascript
我第一反应是用3D引擎,由于我不久前刚用three.js
作过一个BMW
的在线展厅,基本把three.js
摸熟了。css
这里另有一篇文章教你们用threejs作这个BMW在线DIY~html
使用3D引擎先搭一个基本的3D场景,下面的演示使用three.js,同类的3D引擎我还调研过babylon.js,playcanvas,使用都差不太多,学会一个基本都通的java
var scene, camera, renderer;
function initThree(){
//场景
scene = new THREE.Scene();
//镜头
camera = new THREE.PerspectiveCamera(90, document.body.clientWidth / document.body.clientHeight, 0.1, 100);
camera.position.set(0, 0, 0.01);
//渲染器
renderer = new THREE.WebGLRenderer();
renderer.setSize(document.body.clientWidth, document.body.clientHeight);
document.getElementById("container").appendChild(renderer.domElement);
//镜头控制器
var controls = new THREE.OrbitControls(camera, renderer.domElement);
//一下子在这里添加3D物体
loop();
}
//帧同步重绘
function loop() {
requestAnimationFrame(loop);
renderer.render(scene, camera);
}
window.onload = initThree;
复制代码
如今咱们能看到一个黑乎乎的世界,由于如今scene
里什么都没有,接着咱们要把三维物体放进去了,使用3D引擎的实现方式无非都是如下几种css3
这种方式最容易理解,咱们在一个房间里,看向天花板,地面,正面,左右两面,背面共计六面。咱们把全部六个视角拍成照片就获得下面六张图git
如今咱们直接使用立方体(box)搭出这样一个房间程序员
var materials = [];
//根据左右上下先后的顺序构建六个面的材质集
var texture_left = new THREE.TextureLoader().load( './images/scene_left.jpeg' );
materials.push( new THREE.MeshBasicMaterial( { map: texture_left} ) );
var texture_right = new THREE.TextureLoader().load( './images/scene_right.jpeg' );
materials.push( new THREE.MeshBasicMaterial( { map: texture_right} ) );
var texture_top = new THREE.TextureLoader().load( './images/scene_top.jpeg' );
materials.push( new THREE.MeshBasicMaterial( { map: texture_top} ) );
var texture_bottom = new THREE.TextureLoader().load( './images/scene_bottom.jpeg' );
materials.push( new THREE.MeshBasicMaterial( { map: texture_bottom} ) );
var texture_front = new THREE.TextureLoader().load( './images/scene_front.jpeg' );
materials.push( new THREE.MeshBasicMaterial( { map: texture_front} ) );
var texture_back = new THREE.TextureLoader().load( './images/scene_back.jpeg' );
materials.push( new THREE.MeshBasicMaterial( { map: texture_back} ) );
var box = new THREE.Mesh( new THREE.BoxGeometry( 1, 1, 1 ), materials );
scene.add(box);
复制代码
好,如今咱们把镜头camera(也就是人的视角),放到box内,而且让全部贴图向内翻转后,VR全景就实现了。github
box.geometry.scale( 1, 1, -1 );
复制代码
如今咱们进入了这个盒子!!web
threejs官方立方体全景示例canvas
咱们将房间360度球形范围内全部的光捕捉到一个图片上,再将这张图片展开为矩形,就能获得这样一张全景图片
var sphereGeometry = new THREE.SphereGeometry(/*半径*/1, /*垂直节点数量*/50, /*水平节点数量*/50);//节点数量越大,须要计算的三角形就越多,影响性能
var sphere = new THREE.Mesh(sphereGeometry);
sphere.material.wireframe = true;//用线框模式你们能够看得清楚是个球体而不是圆形
scene.add(sphere);
复制代码
如今咱们把这个全景图片贴到这个球体上
var texture = new THREE.TextureLoader().load('./images/scene.jpeg');
var sphereMaterial = new THREE.MeshBasicMaterial({map: texture});
var sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);
// sphere.material.wireframe = true;
复制代码
和以前同样,咱们把镜头camera(也就是人的视角),放到球体内,而且让全部贴图向内翻转后,VR全景就实现了
如今咱们进入了这个球体!!
var sphereGeometry = new THREE.SphereGeometry(/*半径*/1, 50, 50);
sphereGeometry.scale(1, 1, -1);
复制代码
在VR全景中,咱们须要放置一些信息点,用户点击以后作一些动做。
如今咱们创建这样一个点的数组
var hotPoints=[
{
position:{
x:0,
y:0,
z:-0.2
},
detail:{
"title":"信息点1"
}
},
{
position:{
x:-0.2,
y:-0.05,
z:0.2
},
detail:{
"title":"信息点2"
}
}
];
复制代码
遍历这个数组,并将信息点的指示图添加到3D场景中
var pointTexture = new THREE.TextureLoader().load('images/hot.png');
var material = new THREE.SpriteMaterial( { map: pointTexture} );
for(var i=0;i<hotPoints.length;i++){
var sprite = new THREE.Sprite( material );
sprite.scale.set( 0.1, 0.1, 0.1 );
sprite.position.set( hotPoints[i].position.x, hotPoints[i].position.y, hotPoints[i].position.z );
scene.add( sprite );
}
复制代码
看到HOT指示图了吗?
添加点击事件,首先将所有的sprite放到一个数组里
sprite.detail = hotPoints[i].detail;
poiObjects.push(sprite);
复制代码
而后咱们经过射线检测(raycast),就像是镜头中心向鼠标所点击的方向发射出一颗子弹,去检查这个子弹最终会打中哪些物体。
document.querySelector("#container").addEventListener("click",function(event){
event.preventDefault();
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
mouse.x = ( event.clientX / document.body.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / document.body.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( poiObjects );
if(intersects.length>0){
alert("点击了热点"+intersects[0].object.detail.title);
}
});
复制代码
threejs
等3d引擎太强大了,这些引擎的代码量都有大几百K,在今天的网速下显得无所谓,但在几年前我接到需求时仍然是重要的考量因素。既然咱们只用到3D引擎的一点点功能,那么可否找到一个更加轻量的3D引擎呢。
有!css3d-engine,这个3d引擎只有14kb
,而且在多个大牌商业项目中应用
window.onload=initCSS3D;
function initCSS3D(){
var s = new C3D.Stage();
s.size(window.innerWidth, window.innerHeight).update();
document.getElementById('container').appendChild(s.el);
var box = new C3D.Skybox();
box.size(954).position(0, 0, 0).material({
front: {image: "images/scene_front.jpeg"},
back: {image: "images/scene_back.jpeg"},
left: {image: "images/scene_right.jpeg"},
right: {image: "images/scene_left.jpeg"},
up: {image: "images/scene_top.jpeg"},
down: {image: "images/scene_bottom.jpeg"},
}).update();
s.addChild(box);
function loop() {
angleX += (curMouseX - lastMouseX + lastAngleX - angleX) * 0.3;
angleY += (curMouseY - lastMouseY + lastAngleY - angleY) * 0.3;
s.camera.rotation(angleY, -angleX, 0).updateT();
requestAnimationFrame(loop);
}
loop();
var lastMouseX = 0;
var lastMouseY = 0;
var curMouseX = 0;
var curMouseY = 0;
var lastAngleX = 0;
var lastAngleY = 0;
var angleX = 0;
var angleY = 0;
document.addEventListener("mousedown", mouseDownHandler);
document.addEventListener("mouseup", mouseUpHandler);
function mouseDownHandler(evt) {
lastMouseX = curMouseX = evt.pageX;
lastMouseY = curMouseY = evt.pageY;
lastAngleX = angleX;
lastAngleY = angleY;
document.addEventListener("mousemove", mouseMoveHandler);
}
function mouseMoveHandler(evt) {
curMouseX = evt.pageX;
curMouseY = evt.pageY;
}
function mouseUpHandler(evt) {
curMouseX = evt.pageX;
curMouseY = evt.pageY;
document.removeEventListener("mousemove", mouseMoveHandler);
}
}
复制代码
方案二的好处除了库很小之外,仍是div+css来搭建三维场景的。但这个库的做者几乎不维护,遇到问题必须得本身想办法解决,好比使用在电脑上会看到明显的面片边缘
可是在手机上浏览的话表现仍是至关完美的
咱们继续为它添加可交互的信息点
var hotPoints=[
{
position:{
x:0,
y:0,
z:-476
},
detail:{
"title":"信息点1"
}
},
{
position:{
x:0,
y:0,
z:476
},
detail:{
"title":"信息点2"
}
}
];
复制代码
function initPoints(){
var poiObjects = [];
for(var i=0;i<hotPoints.length;i++){
var _p = new C3D.Plane();
_p.size(207, 162).position(hotPoints[i].position.x,hotPoints[i].position.y,hotPoints[i].position.z).material({
image: "images/hot.png",
repeat: 'no-repeat',
bothsides: true,//注意这个两面贴图的属性
}).update();
s.addChild(_p);
_p.el.detail = hotPoints[i].detail;
_p.on("click",function(e){
console.log(e.target.detail.title);
})
}
}
复制代码
这样就能够显示信息点了,而且因为是div,咱们很是容易添加鼠标点击交互等效果
不过,bothsides
属性为true时,背面的信息点图片是反的。
因此咱们这里要作一点处理,根据其与相机的夹角重置一下信息点的旋转角度。(若是是那种怎么旋转都无所谓的图片,好比圆点则无需处理
)
var r = Math.atan2(hotPoints[i].position.z-0,0-0) * 180 / Math.PI+90;
_p.size(207, 162).position(hotPoints[i].position.x,hotPoints[i].position.y,hotPoints[i].position.z).material({
image: "images/hot.png",
repeat: 'no-repeat',
bothsides: false,
}).update();
复制代码
以上两个方案,我觉得能够给客户交差了。但客户又提出了一些想法
全景图质量须要更高,但加载速度不容许更慢
每一个场景的信息点挺多的,坐标编辑太麻烦了
当时我内心想,总共才收你万把块钱,难不成还得给你定制个引擎,再作个可视化编辑器?
直到客户发过来一个参考连接,我看完惊呆了
,全景图很是清晰,但首次加载速度极快,像百度地图同样,是一块块从模糊到清晰被加载出来的。
经过检查参考连接网页的代码,发现了方案三
pano2vr是一款所见即所得的全景VR制做软件(正版149欧元),功能挺强大的,能够直接输出成HTML5静态网页,体验很是不错。
而其核心库pano2vr_player.js
代码量也只有238kb
。
咱们能够直接使用这个软件来可视化的添加信息点,输出成HTML5后,除了静态图片之外,全部配置信息都在这个pano.xml
文件里
总体的交互体验都很是好,但默认的信息点样式不喜欢,咱们能够经过下面的代码来修改信息点图片
pano.readConfigUrlAsync("pano.xml",()=>{
var pois=pano.getPointHotspotIds();
var hotScale = 0.2;
for(var i=0;i<pois.length;i++){
var ids=pois[i];
var hotsopt=pano.getHotspot(ids);
hotsopt.div.firstChild.src="images/hot.png";
hotsopt.div.firstChild.style.width = 207*hotScale+"px";
hotsopt.div.firstChild.style.height = 162*hotScale+"px";
hotsopt.div.onmouseover = null;
hotsopt.div.setAttribute("ids",ids);
hotsopt.div.onclick=function() {
//在这里能够响应信息点的点击事件啦
console.log(this.getAttribute("ids"));
};
}
});
复制代码
哈哈,没想到最终的方案不只极其简单的就实现了体验良好的VR全景,还附送了很是方便的信息点编辑。除去第一次开发的耗时,之后再制做新的VR场景也就是花个10分钟便可搞定。
但想到外包工头
常常压榨个人报价,压缩个人工期,无理变动需求
收到工程款的时候他请我去K歌,坐在KTV的包间里我没有告诉他使用pano2vr的事,而是对他说
每一个VR场景的信息点都要花1天时间编辑
每制做一个新的VR场景,你收品牌方8k
我每一个场景收你3k,你躺赚5k
毕竟我们老朋友了,我够意思吧
他豪爽的干掉手中的啤酒说:“好兄弟,我给你唱一首!”
本故事纯属虚构,文末配图若有侵权,请联系我跟老板大哥喝一杯
微信搜索并关注“大帅老猿
”,回复“webvr
”得到本文中三种方案的实现源码
若是以为我是个有趣的程序员,请关注我;若是以为本文还不错,记得点赞收藏哦,说不定哪天就用得上!