需求很简单,在已有的三维场景中加载工业点云模型,模型如下:
点云数据过大,尝试用threejs直接加载结果直接崩了,在cloudcomPare软件中加载发现模型点位高达2500多万,这肯定不行,找方法优化模型(点位去重)。
克隆一份并进行抽稀
tools->other->Remove duplicate points菜单访问。这个工具根据点之间的最小距离删除点云中的重复点。
选择一个或几个点云然后启动这个工具。CloudCompare只需要输入点之间的最小距离这一个参数(默认值非常小):
抽稀后五百多万,满足我们的需求了,模型质量达到需求标准。
接下来我们看看,怎么用three.js把模型加载出来。我们用的是pcd格式,不同格式可以用cloudcomPare转换导出。
话不多说,直接贴代码
<template>
<div style="height:1080px; width:1920px ">
<div id="three" style="height: 100%; width: 100%"></div>
</div>
</template>
<script>
import * as THREE from "three";
import { PCDLoader } from "three/examples/jsm/loaders/PCDLoader.js"; // 注意是examples/jsm
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; // 放大缩小旋转等控制操作
var clock = new THREE.Clock();
var FPS = 30;
var renderT = 1 / FPS; //单位秒 间隔多长时间渲染渲染一次
var timeS = 0;
const scene = new THREE.Scene(); // 场景
const loader = new PCDLoader(); //PCD加载器
export default {
components: {},
data() {
return {
animationId: null,
elem: null,
scene: null,
// mesh: null, //网格模型对象
camera: null, //相机对象
renderer: null, //渲染器对象
loader: null,
controls: null,
publicPath: process.env.BASE_URL // public
};
},
beforeDestroy() {
this.destroyModel();
},
mounted() {
// 初始化模型
this.initModel(`/static/models/pcd/matrixaiColorPcd.pcd`, "three")
},
methods: {
initModel(pcdPath, domName) {
this.elem = document.getElementById(domName);
this.camera = new THREE.PerspectiveCamera(
30, // 视野
this.elem.clientWidth / this.elem.clientHeight, // 纵横比
0.1, // 近平面
1000 // 远平面
);
// 渲染器
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
this.renderer.setClearColor(new THREE.Color(0x303030)); // 背景色
this.renderer.setSize(this.elem.clientWidth, this.elem.clientHeight);
this.elem.appendChild(this.renderer.domElement);
const THIS = this;
try {
//加载PCD文件
loader.load(
pcdPath,
function (points) {
points.material.color = new THREE.Color(); // 模型颜色
scene.add(points);
// 构造盒子
var middle = new THREE.Vector3();
points.geometry.computeBoundingBox();
points.geometry.boundingBox.getCenter(middle);
points.applyMatrix4(
new THREE.Matrix4().makeTranslation(
-middle.x,
-middle.y,
-middle.z
)
);
// 比例
var largestDimension = Math.max(
points.geometry.boundingBox.max.x,
points.geometry.boundingBox.max.y,
points.geometry.boundingBox.max.z
);
THIS.camera.position.y = largestDimension * 1;
THIS.animate();
THIS.controls = new OrbitControls(
THIS.camera,
THIS.renderer.domElement
);
THIS.controls.addEventListener("change", THIS.animate); // 监听鼠标、键盘事件 放大缩小等
},
function (xhr) {
// console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
//第二层 捕捉报错
function (error) {
console.log(error)
THIS.$Message.error("模型地址不对,请稍候再试!");
}
);
} catch (error) {
console.log(error)
THIS.$Message.error("模型地址不对,请稍候再试!");
}
},
animate() {
this.animationId = requestAnimationFrame(this.animate);
var T = clock.getDelta();
timeS = timeS + T;
if (timeS > renderT) {
this.renderer.render(scene, this.camera); //执行渲染操作
timeS = 0;
}
},
destroyModel() {
clearTimeout();
try {
scene.clear();
this.renderer.dispose();
this.renderer.forceContextLoss();
this.renderer.content = null;
cancelAnimationFrame(this.animationId); // 去除animationFrame
const gl = this.renderer.domElement.getContext("webgl");
gl && gl.getExtension("WEBGL_lose_context").loseContext();
} catch (e) {
console.log("销毁失败");
}
}
}
};
</script>
<style scoped>
</style>
经过一些尝试和研究了threejs的PCDLoader以后,发现其加载性能还是不够友好,所以我重写了loader,目前支持多片点云模型加载,点云规模在5000万左右。
附上loader的代码,希望能对正在阅读这篇文章的你有所帮助。
function PCDLoader(manager) {
THREE.Loader.call(this, manager);
this.littleEndian = true;
}
PCDLoader.prototype = Object.create(THREE.Loader.prototype);
PCDLoader.prototype.constructor = PCDLoader;
PCDLoader.prototype.load = function (url, onLoad, onProgress, onError) {
const scope = this;
const loader = new THREE.FileLoader(scope.manager);
loader.setPath(scope.path);
loader.setResponseType("arraybuffer");
loader.setRequestHeader(scope.requestHeader);
loader.setWithCredentials(scope.withCredentials);
loader.load(
url,
function (data) {
try {
onLoad(scope.parse(data, url));
} catch (e) {
handleLoadError(e, onError);
scope.manager.itemError(url);
}
},
onProgress,
function (err) {
handleLoadError(err, onError);
scope.manager.itemError(url);
}
);
};
PCDLoader.prototype.parse = function (data, url) {
function handleLoadError(err, onError) {
if (onError) {
onError(err.message || err);
} else {
console.error(err);
}
}
// ... (前面的函数保持不变)
function createBufferGeometry(position, normal, color) {
const geometry = new THREE.BufferGeometry();
if (position.length > 0) {
geometry.setAttribute("position", new THREE.Float32BufferAttribute(position, 3));
}
if (normal.length > 0) {
geometry.setAttribute("normal", new THREE.Float32BufferAttribute(normal, 3));
}
if (color.length > 0) {
geometry.setAttribute("color", new THREE.Float32BufferAttribute(color, 3));
}
geometry.computeBoundingSphere();
return geometry;
}
// ... (其余的代码)
const geometry = createBufferGeometry(position, normal, color);
const material = new THREE.PointsMaterial({ size: 0.005 });
if (color.length > 0) {
material.vertexColors = true;
} else {
material.color.setHex(Math.random() * 0xffffff);
}
const mesh = new THREE.Points(geometry, material);
const reversedName = url.split("").reverse().join("");
const reversedWithoutSlash = /([^\/]*)/.exec(reversedName);
const reversedNameWithoutSlash = reversedWithoutSlash && reversedWithoutSlash[1];
const name = reversedNameWithoutSlash ? reversedNameWithoutSlash.split("").reverse().join("") : "";
mesh.name = name;
return mesh;
};
// 导出PCDLoader类
window.PCDLoader = PCDLoader;