Skip to main content

无后端异步渲染全景图

前言

在本项目中,由于需要做一个异步渲染全景图的功能,也就是允许用户上传图片,再通过three.js引擎渲染出全景图显示出来。
image.png
由于没有服务器和后台,所以图片的上传和threejs引擎渲染都在前端来完成。
架构如下:
架构.png

图片的上载和编码

需求分析:将图片从本地获取,然后将其转化为字符串数据流的形式保存到data中,方便threejs引擎调用。

创建data存放

data() {
return {
···
picurl: '', //用于存放base64数据流
···
}
}

编写图片获取函数

GetImg() {
if (Pictures === null) {
// 生成文件上传的控件
Pictures = document.createElement('input')
Pictures.setAttribute('type', 'file')
Pictures.style.display = 'none'
// 更改视图
if (window.addEventListener) {
Pictures.addEventListener('change', this.uploadFile, false)
} else {
Pictures.attachEvent('onchange', this.uploadFile)
}
document.body.appendChild(Pictures)
}
Pictures.click()
},

转码为base64编码

上传完图片后,判断图片的大小,并将上传的图片限制在100MB以内。

uploadFile(el) {
this.$set(this.temp, 'isdisplay', false);
this.$message({
duration: 9000,//设置提示时间
message: '全景图生成中,请耐心等待十秒',
type: 'warning'
});

if (el && el.target && el.target.files && el.target.files.length > 0) {
//this.$set(this.temp, 'img', el);

const files = el.target.files[0]
const isLt2M = files.size / 1024 / 1024 < 100
//大小限制100MB
const size = files.size / 1024 / 1024
console.log(size)
// 判断上传文件的大小
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过100MB!')
} else if (files.type.indexOf('image') === -1) { //如果不是图片格式
// this.$dialog.toast({ mes: '请选择图片文件' });
this.$message.error('请选择图片文件');
} else {
const that = this;
const reader = new FileReader(); // 创建读取文件对象
reader.readAsDataURL(el.target.files[0]); // 发起异步请求,读取文件
reader.onload = function () { // 文件读取完成后
// 读取完成后,将结果赋值给img的src
that.picurl = this.result;
console.log(this.result);
};
setTimeout(() => {
this.init();
}, 7000)

}
}

},

下面的readAsDataURL语句会把图片读为base64编码:

reader.readAsDataURL(el.target.files[0]);

然后将其作为string数据流保存到data中。

reader.onload = function () { // 文件读取完成后
// 读取完成后,将结果赋值给img的src
that.picurl = this.result;
console.log(this.result);
};

three.js使用base64编码制作texture纹理

由于没有后台,所以不能使用正常的图片作为参数交给threejs进行处理,所以我们使用刚刚得到的base64数据流作为参数制作纹理。这个问题困扰了很久,最后到reddit上的这篇文章找到了解决方案。

//加载全景图资源
const image = new Image();
image.src = this.picurl; //获取前面保存的base64编码
var texture1 = new THREE.Texture(); //创建材质贴图
texture1.image = image; // 获取贴图图片
image.onload = function () {
texture1.needsUpdate = true; // 检测贴图是否变化,重新渲染
};
var geometry = new THREE.SphereGeometry(130, 256, 256); // 创建球体网格模型
var material = new THREE.MeshLambertMaterial({
map: texture1, //使用base64贴图
side: THREE.DoubleSide, //使摄影机内部能够看到贴图,双面渲染
});

异步渲染

最开始执行上面的步骤的时候,调用 this.init() 函数之后,提示dom对象不存在。原因在于,我设置了点击 upload 按钮之后触发页面刷新,更改页面,这个渲染对象threeDom在页面刷新之后才被挂载,而在这个dom挂载之前,threejs已经开始操作dom了!!!,因此它会提示找不到这个dom,从而无法运行。


如何解决这个问题?
答案就是设置等待,让init异步执行,使threejs等待一段时间,待threeDom挂载完成后再开始操作dom。

setTimeout(() => {
this.init(); //等待一段时间后再初始化threejs
}, 7000)

在upload之后等待一段时间,待页面刷新完成后再初始化threejs。

实现效果

Details