foreword
=====
The thing is like this, I received a 外包工头
new request a few days ago. A certain brand wants to build an online VR showroom. Information point, after clicking, more information (video, graphics, etc.) can be presented...
image.png
My first reaction was to use a 3D engine, because I just used three.js
an BMW
online showroom not long ago, and I am basically three.js
familiar with it.
2021-06-03 11_01_41.gif
I will write another article to teach you to use threejs to make this [BMW Online DIY], please follow me if you are interested~
Solution 1: WebGL3D engine
Use a 3D engine to build a basic 3D scene. The following demonstration uses three.js. I have also researched babylon.js and playcanvas for similar 3D engines. They are all similar in use, and I can learn a basic one.
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;
Now we can see a dark world, because scene
there is nothing in it now, and then we have to put three-dimensional objects in it. The implementation methods of using a 3D engine are nothing more than the following
Realized using a cube (box)
This way is the easiest to understand. We are in a room, looking at the ceiling, the ground, the front, the left and right sides, and the back, a total of six sides. We take all six angles of view as photos to get the following six pictures
image.png
Now we directly use the cube (box) to build such a room
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);
2021-06-14 19_51_17.gif
Ok, now we put the lens camera (that is, the human perspective) into the box, and after flipping all the textures inward, the VR panorama is realized.
box.geometry.scale( 1, 1, -1 );
Now we're inside the box! !
2021-06-14 19_41_37.gif
threejs official cube panorama example
Implemented using a sphere
We capture all the light in the 360-degree spherical range of the room onto a picture, and then expand the picture into a rectangle to get such a panoramic picture
image.png
var sphereGeometry = new THREE.SphereGeometry(/*半径*/1, /*垂直节点数量*/50, /*水平节点数量*/50);//节点数量越大,需要计算的三角形就越多,影响性能
var sphere = new THREE.Mesh(sphereGeometry);
sphere.material.wireframe = true;//用线框模式大家可以看得清楚是个球体而不是圆形
scene.add(sphere);
image.png
Now let's paste this panorama onto this 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;
2021-06-14 14_54_38.gif
As before, we put the lens camera (that is, the perspective of the person) into the sphere, and after flipping all the textures inward, the VR panorama is realized
Now we are inside this sphere! !
var sphereGeometry = new THREE.SphereGeometry(/*半径*/1, 50, 50);
sphereGeometry.scale(1, 1, -1);
2021-06-14 15_15_28.gif
threejs official sphere panorama example
Add information points
In the VR panorama, we need to place some information points, and the user will do some actions after clicking.
Now we create an array of points like this
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"
}
}
];
Traverse this array and add the indication map of the information point to the 3D scene
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 );
}
See the HOT instruction map?
2021-06-14 20_22_12.gif
Add a click event, first put all sprites into an array
sprite.detail = hotPoints[i].detail;
poiObjects.push(sprite);
Then we pass ray detection (raycast), which is like launching a bullet from the center of the lens to the direction where the mouse is clicked, to check which objects the bullet will eventually hit.
2021-06-15 01_35_14.gif
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);
}
});
Solution 2: CSS3D
threejs
When the 3D engine is too powerful, the code size of these engines is hundreds of K, which does not matter at today's Internet speed, but it was still an important consideration when I received the demand a few years ago. Since we only use a little function of the 3D engine, can we find a lighter 3D engine?
have! css3d-engine, this 3d engine is only 14kb
used in many big-name commercial projects
-
Taobao Creation Festival https://shrek.imdevsh.com/show/zwj/
-
adidas will never wither https://shrek.imdevsh.com/show/drose/
-
adidas is in full swing https://shrek.imdevsh.com/show/bbcny/
-
adidas will never follow https://shrek.imdevsh.com/show/crazylight/
Use skybox to achieve
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);
}
}
The advantage of option two is that apart from the small library, it is still div+css to build a three-dimensional scene. However, the author of this library hardly maintains it. When encountering problems, you have to find a way to solve it yourself. For example, you will see obvious mesh edges when using it on a computer.
image.png
But when browsing on mobile phones, the performance is quite perfect.
2021-06-14 22_20_26.gif
Add information points
We continue to add interactive information points to it
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,//Pay attention to the properties of this two-sided map }).update(); s.addChild(_p);
_p.el.detail = hotPoints[i].detail;
_p.on(“click”,function(e){
console.log(e.target.detail.title);
})
}
}
这样就可以显示信息点了,并且由于是div,我们非常容易添加鼠标点击交互等效果
![图片](https://mmbiz.qpic.cn/mmbiz_png/t1ynS50Irh3H6sn2Pc31b8LLcjL2Ht5ZoZiaHPARu2sZK6j80sU6cON6FlsmeicUVCT1MXH939IfolSwpXwIYZibQ/640?wx_fmt=png)
image.png
不过,`bothsides`属性为true时,背面的信息点图片是反的。
![图片](https://mmbiz.qpic.cn/mmbiz_png/t1ynS50Irh3H6sn2Pc31b8LLcjL2Ht5ZzSdsibZk0RFGWeeIAtkhBp0d7Vw5UZibSOQCKt4RymZOvj9U8NL56QoA/640?wx_fmt=png)
image.png
所以我们这里要做一点处理,根据其与相机的夹角重置一下信息点的旋转角度。(`如果是那种怎么旋转都无所谓的图片,比如圆点则无需处理`)
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();
需求升级了!
======
以上两个方案,我以为可以给客户交差了。但客户又提出了一些想法
* **全景图质量需要更高,但加载速度不允许更慢**
* **每个场景的信息点挺多的,坐标编辑太麻烦了**
当时我心里想,总共才收你万把块钱,难不成还得给你定制个引擎,再做个可视化编辑器?
![图片](https://mmbiz.qpic.cn/mmbiz_png/t1ynS50Irh3H6sn2Pc31b8LLcjL2Ht5ZuvWUIWrbxbxPic6eENNodMP4WEicexf8FVPyJvE1uPTBZrPdjzUoPCZA/640?wx_fmt=png)
直到客户发过来一个参考链接,我看完`惊呆了`,全景图非常清晰,但首次加载速度极快,像百度地图一样,是一块块从模糊到清晰被加载出来的。
![图片](https://mmbiz.qpic.cn/mmbiz_gif/t1ynS50Irh3H6sn2Pc31b8LLcjL2Ht5Zic1oKjJhuYVpDBhPOWYiaXYmuo2IVV5U78CyKCTkibUlgnbMlRYQI8u8A/640?wx_fmt=gif)
2021-06-14 23\_31\_28.gif
通过检查参考链接网页的代码,发现了方案三
方案三:pano2vr
===========
![图片](https://mmbiz.qpic.cn/mmbiz_png/t1ynS50Irh3H6sn2Pc31b8LLcjL2Ht5Z3WzDvhFfhGYnFsSk0De26v1icBO2RUmDXyh2hjR0FNPRhNr2vS8xhBw/640?wx_fmt=png)
image.png
pano2vr是一款所见即所得的全景VR制作软件(正版149欧元),功能挺强大的,可以直接输出成HTML5静态网页,体验非常不错。
而其核心库`pano2vr_player.js`代码量也只有`238kb`。
![图片](https://mmbiz.qpic.cn/mmbiz_png/t1ynS50Irh3H6sn2Pc31b8LLcjL2Ht5Z3OhqPFq0TuMjjdv8QFdIzSqt10YAM4wWI2D366NBpY9YKw2vAauA9g/640?wx_fmt=png)
image.png
我们可以直接使用这个软件来可视化的添加信息点,输出成HTML5后,除了静态图片以外,所有配置信息都在这个`pano.xml`文件里
![图片](https://mmbiz.qpic.cn/mmbiz_png/t1ynS50Irh3H6sn2Pc31b8LLcjL2Ht5Zy0tBacniaJPXAbELib4DibId7wELeeDxOzGNyBTL32qVhkf51PObkNAkQ/640?wx_fmt=png)
image.png
修改信息点图片
-------
整体的交互体验都非常好,但默认的信息点样式不喜欢,我们可以通过下面的代码来修改信息点图片
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() { // Here you can respond to the click event of the information point console.log(this.getAttribute(“ids”)); }; } });
哈哈,没想到最终的方案不仅极其简单的就实现了体验良好的VR全景,还附送了非常方便的信息点编辑。除去第一次开发的耗时,以后再制作新的VR场景也就是花个10分钟即可搞定。
但想到`外包工头`经常_压榨我的报价,压缩我的工期,无理变更需求_
收到工程款的时候他请我去K歌,坐在KTV的包间里我没有告诉他使用pano2vr的事,而是对他说
**每个VR场景的信息点都要花1天时间编辑**
**每制作一个新的VR场景,你收品牌方8k**
**我每个场景收你3k,你躺赚5k**
**毕竟咱们老朋友了,我够意思吧**
他豪爽的干掉手中的啤酒说:“好兄弟,我给你唱一首!”
![图片](https://img-blog.csdnimg.cn/img_convert/e10d17c70a0e55f074f88a6ebe4ecfd3.png)
image.png
* * *
本故事纯属虚构,文末配图如有侵权,请联系我跟老板大哥喝一杯
查看本文配套视频教程
源码
==
微信搜索并关注“`大帅老猿`”,回复“`webvr`”获得本文中三种方案的实现源码
> 如果觉得我是个有趣的程序员,请关注我;如果觉得本文还不错,记得点赞收藏哦,说不定哪天就用得上!