测试点云数数据大小 2.94G
cesium 加载:
数据处理:cesiumlab 点云切片->cesium 3Dtiles API 加载
threejs 加载
只支持 pcd 格式,故将 lsa 数据导入,在导出为了 pcd,在将数据直接转出 pcd 会直接闪退,不知是不是数据量的问题,还是电脑问题,然后试了下数据抽稀,在转化导出 200 多 M,然后直接加载,没有做 lod
Potree 加载 las
https://github.com/potree/potree
通过自带 Octree 优化加载性能
因为使用 Potree 加载的点云数据需要八叉树索引,而默认的 las 是没有构建有的(在软件上可视化时是会默认自动构建并可视化),所以需要对其进行转化为此库需要使用的相应的格式需要使用此库中自带的工具PotreeConverter 来转换
转换后的数据如上,原理类似于 3Dtiles 文件的组织
threejs+potree-core 切片加载
three.js 加载点云切片数据
实现原理是是先使用 potree 的八叉树索引构建工具将 las 数据转化为 octree 数据格式,然后使用网络 potree-core 库(potree 简化版),并实在 three 中通过八叉树索引加载点云动态加载,代码修改参考于 potree-core 的示例代码库。
<template>
<canvas id="gl"></canvas>
</template>
<script setup>
import GUI from "lil-gui";
import {
AmbientLight,
AxesHelper,
DirectionalLight,
BoxGeometry,
Clock,
GridHelper,
LoadingManager,
SpotLight,
SpotLightHelper,
Mesh,
MeshLambertMaterial,
MeshStandardMaterial,
PCFSoftShadowMap,
PerspectiveCamera,
PlaneGeometry,
Vector3,
MeshBasicMaterial,
PointLight,
PointLightHelper,
Scene,
WebGLRenderer
} from "three";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
import Stats from "three/examples/jsm/libs/stats.module";
import {PCDLoader} from "three/examples/jsm/loaders/PCDLoader.js"; // 注意是examples/jsm
import * as animations from "./utils/threeUtils/animations";
import {resizeRendererToDisplaySize} from "./utils/threeUtils/responsiveness";
import { PointCloudOctree, Potree } from 'potree-core'
import {onMounted, onUnmounted} from "vue";
const animation = {enabled: false, play: true};
let canvas;
let renderer;
let scene;
let loadingManager;
let ambientLight;
let pointLight;
let spotLight;
let directionalLight;
let cube;
let camera;
let cameraControls;
// let dragControls;
let axesHelper;
let pointLightHelper;
let spotLightHelper;
// let cameraHelper
let clock;
let stats;
let gui;
let points;
let pointClouds;
const potree = new Potree();
onMounted(() => {
init();
animate();
});
onUnmounted(() => {
destroy();
if (stats.dom && stats.dom.parentElement) {
stats.dom.parentElement.removeChild(stats.dom);
}
});
const init = () => {
// renderer&&scene
{
canvas = document.querySelector("#gl");
renderer = new WebGLRenderer({
canvas,
antialias: true,
alpha: true,
logarithmicDepthBuffer: false,//对数深度缓冲区
precision: 'highp',//渲染精度
premultipliedAlpha: true,//
preserveDrawingBuffer: false,//是否保留绘图缓冲区
powerPreference: 'high-performance'//电源偏好
});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// renderer.shadowMap.enabled = true; //开启阴影渲染
// renderer.shadowMap.type = PCFSoftShadowMap; //阴影映射的类型
scene = new Scene();
}
// LoadingManager
{
loadingManager = new LoadingManager();
loadingManager.onStart = () => {
console.log("loading started");
};
loadingManager.onProgress = (url, loaded, total) => {
console.log("loading in progress:");
console.log(`${url} -> ${loaded} / ${total}`);
};
loadingManager.onLoad = () => {
console.log("loaded!");
};
loadingManager.onError = () => {
console.log("❌ error while loading");
};
}
// light
{
//环境光
ambientLight = new AmbientLight("white", 0.4);
//点光源
pointLight = new PointLight("#ffdca8", 1.2, 100);
pointLight.position.set(-2, 3, 3);
pointLight.castShadow = true; //开启阴影投射
pointLight.shadow.radius = 4; //设置软阴影的半径
pointLight.shadow.camera.near = 0.5;
pointLight.shadow.camera.far = 4000;
//设置阴影贴图的大小,值越大阴影的质量越高,但同时也会更消耗性能。
pointLight.shadow.mapSize.width = 2048;
pointLight.shadow.mapSize.height = 2048;
//聚光灯
spotLight = new SpotLight(0xffffff, 1.0);
spotLight.intensity = 1.0; //光照强度
spotLight.angle = Math.PI / 6; //发散角度:光锥角度的二分之一
spotLight.position.set(4, 5, 8);
spotLight.castShadow = true;
//平行光
directionalLight = new DirectionalLight(0xffffff, 1);
directionalLight.position.set(10, 10, 10);
// directionalLight.position.set(100, 60, 50);
directionalLight.castShadow = true;
scene.add(spotLight);
scene.add(ambientLight);
scene.add(pointLight);
scene.add(directionalLight);
}
//object
{
const sideLength = 1;
const cubeGeometry = new BoxGeometry(sideLength, sideLength, sideLength);
const cubeMaterial = new MeshStandardMaterial({
color: "#f69f1f",
metalness: 0.5,
roughness: 0.7
});
cube = new Mesh(cubeGeometry, cubeMaterial);
cube.castShadow = true;
cube.position.y = 0.5;
const planeGeometry = new PlaneGeometry(6, 6);
const planeMaterial = new MeshLambertMaterial({
color: "gray",
emissive: "teal",
emissiveIntensity: 0.2,
side: 2,
transparent: true,
opacity: 0.4
});
const plane = new Mesh(planeGeometry, planeMaterial);
plane.rotateX(Math.PI / 2);
plane.receiveShadow = true;
// console.log("cube", cube);
scene.add(cube);
scene.add(plane);
}
//potree
{
points = new Potree();
points.pointBudget = 1000000000
pointClouds = [];
points.loadPointCloud('metadata.json', (url) => {
return `/test/${url}`
}).then((pco) => {
pco.material.size = 1.0
pco.material.shape = 2
pco.material.inputColorEncoding = 1
pco.material.outputColorEncoding = 1
console.log('PointCloud file loaded', pco)
pco.position.set(0, 0, 0);
console.log(pco)
const box = pco.pcoGeometry.boundingBox
const size = box.getSize(new Vector3())
console.log(size)
const geometry = new BoxGeometry(size.x, size.y, size.z)
const material = new MeshBasicMaterial({ color: 0xFF0000, wireframe: true })
const mesh = new Mesh(geometry, material)
mesh.scale.set(0.001, 0.001, 0.001);
console.log(mesh)
mesh.raycast = () => false
size.multiplyScalar(0.5)
addPointCloud(pco)
})
}
// camera
{
camera = new PerspectiveCamera(
60,
canvas.clientWidth / canvas.clientHeight,
0.1,
1000
);
camera.position.z = 30;
}
// controls
{
cameraControls = new OrbitControls(camera, canvas);
cameraControls.target = cube.position.clone();
cameraControls.enableDamping = true;
// cameraControls.autoRotate = true;
cameraControls.update();
}
// helpers
{
axesHelper = new AxesHelper(40);
// axesHelper.visible = false;
scene.add(axesHelper);
pointLightHelper = new PointLightHelper(pointLight, undefined, "orange");
pointLightHelper.visible = false;
scene.add(pointLightHelper);
spotLightHelper = new SpotLightHelper(spotLight, 0xffffff);
spotLightHelper.visible = false;
scene.add(spotLightHelper);
//可视化平行光阴影对应的正投影相机对象
// const cameraHelper = new CameraHelper(directionalLight.shadow.camera);
// cameraHelper.visible = false
// scene.add(cameraHelper);
const gridHelper = new GridHelper(20, 20, "teal", "darkgray");
gridHelper.position.y = -0.01;
scene.add(gridHelper);
}
// STATS & CLOCK
{
clock = new Clock();
stats = new Stats();
// stats.dom.style.left = "200px";
canvas.parentNode.appendChild(stats.dom);
}
//DEBUG GUI
{
gui = new GUI({title: "🐞 Debug GUI", width: 250});
const lightsFolder = gui.addFolder("Lights");
lightsFolder.add(pointLight, "visible").name("point light");
lightsFolder.add(ambientLight, "visible").name("ambient light");
lightsFolder.add(spotLight, "visible").name("spotLight light");
lightsFolder.add(directionalLight, "visible").name("directional light");
const helpersFolder = gui.addFolder("Helpers");
helpersFolder.add(axesHelper, "visible").name("axes");
helpersFolder.add(pointLightHelper, "visible").name("pointLightHelper");
helpersFolder.add(spotLightHelper, "visible").name("spotLightHelper");
// helpersFolder.add(cameraHelper, 'visible').name('directionalLightHelper')
const cameraFolder = gui.addFolder("Camera");
cameraFolder.add(cameraControls, "autoRotate");
// reset GUI state button
const resetGui = () => {
localStorage.removeItem("guiState");
gui.reset();
};
gui.add({resetGui}, "resetGui").name("RESET");
// gui.close();
}
};
//添加pointCloud
const addPointCloud =(pco) => {
scene.add(pco)
pointClouds.push(pco)
}
const animate = () => {
requestAnimationFrame(animate);
stats.update();
if (animation.enabled && animation.play) {
animations.rotate(cube, clock, Math.PI / 3);
animations.bounce(cube, clock, 1, 0.5, 0.5);
}
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
cameraControls.update();
renderer.render(scene, camera);
potree.updatePointClouds(pointClouds, camera, renderer)
};
const destroy = () => {
if (gui) gui.destroy();
scene.traverse(child => {
if (child instanceof Mesh) {
child.geometry.dispose();
for (const key in child.material) {
const value = child.material[key];
if (value && typeof value.dispose === "function") {
value.dispose();
}
}
}
});
};
</script>
<style>
#gl {
width: 100vw;
height: 100vh;
display: block;
background: rgb(25, 25, 25);
}
.lil-gui.root > .children > .lil-gui > .title {
color: white;
padding-right: 180px;
}
</style>