Skip to main content

创建全景图解析引擎

前言

本全景图引擎是使用知名的3d JavaScript库——three.js构建的。下面将记录创建threejs引擎的过程。
image.png

渲染器(renderer)

渲染器可以比喻为拍戏时候的拍摄地点。
首先,创建渲染器对象。渲染器与HTML中的渲染窗口对接,包含了窗口宽度、窗口高度窗口背景颜色等尺寸。然后使用vue的dom操作将渲染区域渲染到容器中.

<template>
<div ref='threeDom'></div>
// 渲染窗口容器
</template>
<script>
rendererInit() { //初始化渲染器
var width = 1100; //窗口宽度
var height = 600; //窗口高度


this.renderer = new THREE.WebGLRenderer({
antialias: true, //抗锯齿,提高全景图质量
});
this.renderer.setClearColor(0xffffff); //添加背景颜色
this.renderer.setSize(width, height); // 设定渲染器尺寸
this.renderer.setPixelRatio(window.devicePixelRatio); //设定屏幕像素
this.$refs.threeDom.appendChild(this.renderer.domElement); // 将
},
</script>

场景(scene)

初始化场景时需要设定场景中的照明颜色和强度,以及设定辅助坐标系(本项目中辅助坐标系已经隐藏)

sceneInit(){ 
this.scene = new THREE.Scene(); //初始化场景
var ambient = new THREE.AmbientLight(0x444444, 3); //添加光源颜色和光照强度
var axisHelper = new THREE.AxesHelper(0);
//参数设为0用于隐藏坐标系
this.scene.add(ambient, axisHelper);
//渲染到场景中
}

创建球体网格模型

将全图片资源贴到创建的球体模型上,让摄像机在球体中心向四周张望,实现全景图预览的效果.创建球体网格模型,设置THREE.DoubleSide 双面渲染,这样才可以看到全景图。

modelling(){ //开始建立模型
this.mygroup = new THREE.Group();
var textureLoader = new THREE.TextureLoader(); //创建纹理贴图加载器
var img = textureLoader.load(require('../../public/img/main.jpeg'));
//使用加载器加载本地纹理
var geometry = new THREE.SphereGeometry(130, 256, 256); // 创建球体网格模型
var material = new THREE.MeshLambertMaterial({
map: img, //设置贴图属性,使用图片贴图
side: THREE.DoubleSide, //双面渲染
});
var meshSphere = new THREE.Mesh(geometry, material); //创建网格模型对象Mesh
meshSphere.name = '球体容器';
this.mygroup.add(meshSphere);
this.scene.add(this.mygroup);// 添加到场景
},

创建场景切换提示文字

使用canvas创建场景中的提示文字,用于切换场景提示.

modelling(){ //开始建立模型
...
this.mygroup = new THREE.Group();
var canvasText = this.getcanvers('切换场景'); //调用函数,创建canvas对象
var texture = new THREE.CanvasTexture(canvasText);
var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60); //生成一个平面模型
var materialText = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
side: THREE.DoubleSide, //双面渲染
});
var meshText = new THREE.Mesh(geometryText, materialText);
meshText.name = '切换场景';
meshText.position.set(40, 20, -90)
this.mygroup.add(meshText);
this.scene.add(this.mygroup);
},

getcanvers(text) { //生成canvers图案函数
var canvasText = document.createElement("canvas");
var c = canvasText.getContext('2d');
// 矩形区域填充背景
c.fillStyle = "#FFFFFF"; //canver背景
c.fillRect(0, 0, 300, 200); //生成一个矩形
c.translate(160, 80);
c.fillStyle = "#000000"; //文字颜色颜色
c.font = "bold 100px 宋体"; //字体样式
c.textBaseline = "middle";
c.textAlign = "center"; //文本居中
c.fillText(text, 0, 0);
var texture = new THREE.CanvasTexture(canvasText); //Canvas纹理
var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60); //矩形平面初始化
var materialText = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
side: THREE.DoubleSide, //双面渲染
});
var meshText = new THREE.Mesh(geometryText, materialText);
meshText.name = text;
meshText.position.set(40, 20, -90);
return canvasText;
},

场景切换

场景切换模块负责在场景发生双击事件之后寻找最近的点来响应该事件。首先要在初始化中监听双击事件。three.js 提供了一个THREE.Raycaster()扫描射线。它能够检测出鼠标在3D场景中的具体位置,Raycaster射线会记录与之相交几何体,并以数组的形式从近到远返回对应模型的mesh ,只需要向射线中传入鼠标的位置和当前相机即可,这样我们就可以根据模型的名称获取当前点击的那个模型并触发对应的事件。

2

init(){
this.$refs.threeDom.addEventListener('dblclick', this.onMouseDblclick);
//添加事件监听
},
onMouseDblclick(event){
// 当双击事件触发时
// raycaster返回检测到的对象,最前面的是最近的数组。
event.preventDefault(); //阻止默认的动作执行
var raycaster = new THREE.Raycaster(); //生成射线对象
// 参数:
// Raycaster( origin : Vector3, direction : Vector3, near : Float, far : Float )
// origin —— 光线投射的原点向量。
// direction —— 向射线提供方向的方向向量,应当被标准化。
// near —— 返回的所有结果比near远。near不能为负值,其默认值为0。
// far —— 返回的所有结果都比far近。far不能小于near,其默认值为Infinity(正无穷。)
var mouse = new THREE.Vector2(); // 创建鼠标三维向量,向射线传参
var container = this.$refs.threeDom; // 获取渲染窗口的dom
let getBoundingClientRect = container.getBoundingClientRect();
// 创建客户端动作对象,获取客户做出的动作
mouse.x = ((event.clientX - getBoundingClientRect.left) / container.offsetWidth) * 2 - 1;
// 计算鼠标三维向量中的x分量大小
mouse.y = -((event.clientY - getBoundingClientRect.top) / container.offsetHeight) * 2 + 1;
// 计算鼠标三维向量中的y分量大小
raycaster.setFromCamera(mouse, this.camera);
// 设置看向射线的摄像机
var intersects = raycaster.intersectObjects(this.scene.children[2].children);
//检测所有在射线与物体之间,包括或不包括后代的相交部分。返回结果时,相交部分将按距离进行排序,最近的位于第一个。
if (intersects.length != 0) {
for (var item of intersects) {
if (item.object.name != '闪现'||item.object.name != '返回') {
//找到标签对应的操作类型,如果在空白处点击,忽略
this.action.paused = true;
//停止旋转
this.$confirm('是否切换场景?', '提示', {
//调用element交互弹出通知
confirmButtonText: '切换',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.action.paused = false; //开启旋转
if (item.object.name == '闪现') {
this.changeScene('enter'); //改变页面场景
} else if (item.object.name == '返回') {
this.changeScene('back'); //改变页面场景
}
}).catch(() => {
this.action.paused = false; //开启旋转
});
break;
}
}
} else { //未选中状态
console.log("未选中状态");
}
},

相机位置

把透视摄像机放在房间的中心。使用透视相机。
3

 cameraInit() { //初始化相机
var width = 800; //窗口宽度
var height = 800; //窗口高度
this.camera = new THREE.PerspectiveCamera(90, width / height, 1, 1000); //使用透视相机
this.camera.position.set(0, 0, 10); //设置相机位置
this.camera.lookAt(new THREE.Vector3(0, 0, 0)); // 相机看向
},

初始化控制器

控制器用于感知鼠标在场景中的滚动,让控制器围绕中心焦点旋转。

controlInit(){ //初始化控制器
this.controls = new OrbitControls(this.camera, this..threeDom); // 初始化
this.controls.target.set(0, 0, 0); // 设置控制器的焦点,使控制器围绕这个焦点进行旋转
this.controls.minDistance = 0; // 设置移动的最短距离(默认为零)
this.controls.maxPolarAngle = Math.PI; //绕垂直轨道的距离(范围是0-Math.PI,默认为Math.PI)
this.controls.maxDistance = 70; // 设置移动的最长距离(默认为无穷)
this.controls.enablePan = false; //禁用右键功能
this.controls.addEventListener('change', this.refresh); //监听鼠标、键盘事件,并更新页面渲染
},
refresh(){ //刷新页面
this.renderer.render(this.scene, this.camera); //执行渲染操作
this.stats.update(); //更新性能监控的值
},

自动滚动操作。

addAnimation(){ //添加并开启动画
this.clock = new THREE.Clock(); // 创建three.js 时钟对象
var times = [0, 3600]; // 帧动画序列,采用3600帧
var position_x = [0, 360]; //一周的角度,0~360
var keyframe = new THREE.KeyframeTrack('meshSphere.rotation[y]', times, position_x);
var duration = 100; //持续时间
var cilp = new THREE.AnimationClip('sphereRotate', duration, [keyframe]); //剪辑 keyframe对象
this.mixer = new THREE.AnimationMixer(this.mygroup); //动画混合实例
this.action = this.mixer.clipAction(cilp);
this.action.timeScale = 1; //播放速度
this.action.setLoop(THREE.LoopPingPong).play(); // 循环播放
this.animate(); //开启动画
},

animate() {
//循环渲染
this.rotateAnimate = requestAnimationFrame(this.animate);
this.renderer.render(this.scene, this.camera);
this.update(); // 刷新页面渲染
}