【重要-threeJS 渲染性能上的优化方案】--- 加载读取渲染压缩包中的模型
需要准备的插件和包(threeJS等包省略,这个三是实现标题功能核心的包):
- jszip Docs:JSZip (stuk.github.io)
- jszip-utils
-
FileSaver
package1&2 是 从 压缩包中读取 模型的关键!!!, package1&3是模型打包压缩必须的!
【代码有很多地方可以自行精简】
首先实现上传模型,自动打包成zip文件并自动下载zip包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jszip/3.7.1/jszip.min.js"
type="application/javascript"></script>
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/FileSaver.js/2014-08-29/FileSaver.min.js" type="application/javascript"></script> <title>Document</title>
</head>
<body>
<input type="file" name="file" id="fileID">
<button type="submit" onclick="toZip()">sure</button>
</body>
<script>
function toZip() {
var file = document.getElementById("fileID");
// 文件上传后 点击submit, 获取到上传文件
var zip = new JSZip();
zip.file(file.files[0].name, file.files[0]);
//console.log(file.files[0]);
zip.generateAsync({ // 这里可以看jszip官方文档
type: "blob",
compression: "DEFLATE",
compressionOptions: {
level: 9
}
}).then(function (content) {
// console.log(2);
saveAs(content, file.files[0].name.split('.')[0]+'.zip');
//到这里 就可以上传模型,自动会压缩并打包。
});
// if (false) {
// var content = "这里可以拿到接口返回的压缩包二进制数据,还原后解压";
// zip.loadAsync(content).then(function (zip) {
// new_zip.file("getContent.txt").async("string");
// });
// }
}
</script>
</html>
再实现结合vue3+threeJS+jszip 读取渲染压缩包里模型功能[threeJS 基本操作,例如打灯光,设置相机等操作就不注释了。网上很多例子。]
<script setup>
import JSZip from "jszip";
import * as THREE from "three";
import { OrbitControls } from "@/js/Controls";
import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import {
CSS2DRenderer,
CSS2DObject,
} from "three/examples/jsm/renderers/CSS2DRenderer.js";
import { ref, onMounted, getCurrentInstance, nextTick, onUpdated } from "vue";
const { proxy } = getCurrentInstance();
// let scene, mesh;base
let scene, raycaster, mouse, sphere103, lineGeometry, line, base;
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
const num = ref(0);
const isShow = ref(0);
const info = ref("model loading...");
const camera = ref(null);
const renderer = ref(null);
const labelRenderer = ref(null);
const controls = ref(null);
const path = "/model/408.fbx";
const createScene = () => {
scene = new THREE.Scene();
scene.position.y = 0;
};
const createLight = () => {
scene.add(new THREE.AmbientLight(0x444444));
const light = new THREE.PointLight(0xffffff);
light.position.set(0, 50, 50);
//告诉平行光需要开启阴影投射
light.castShadow = true;
scene.add(light);
};
const createCamera = () => {
camera.value = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.value.position.set(100, 35, 0); // 设置相机位置
camera.value.lookAt(new THREE.Vector3(0, 0, 0)); // 设置相机方向
scene.add(camera.value);
};
const createRender = async () => {
const element = document.getElementById("container");
renderer.value = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.value.setSize(element.clientWidth, element.clientHeight); // 设置渲染区域尺寸
renderer.value.shadowMap.enabled = true; // 显示阴影
renderer.value.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.value.setClearColor(0x3f3f3f, 1); // 设置背景颜色
element.appendChild(renderer.value.domElement); // 这里是canvas
labelRenderer.value = new CSS2DRenderer();
labelRenderer.value.setSize(element.clientWidth, element.clientHeight);
labelRenderer.value.domElement.style.position = "absolute";
labelRenderer.value.domElement.style.top = "0px";
// console.dir(labelRenderer.value.domElement);
element.appendChild(labelRenderer.value.domElement); // 这里domElement是 divlabel的父级
};
const createControls = () => {
controls.value = new OrbitControls(
camera.value,
labelRenderer.value.domElement
);
controls.value.minDistance = 5;
controls.value.maxDistance = 100;
};
function setScaleToFitSize(obj) { // 这里是将模型适配到一个合适的比例
const boxHelper = new THREE.BoxHelper(obj);
boxHelper.geometry.computeBoundingBox();
const box = boxHelper.geometry.boundingBox;
const maxDiameter = Math.max(
box.max.x - box.min.x,
box.max.y - box.min.y,
box.max.z - box.min.z
);
const scaleValue = camera.value.position.x / maxDiameter;
obj.scale.set(scaleValue, scaleValue, scaleValue);
}
const loadSTL = (scale = 0.02) => {
const new_zip = new JSZip(); // 实例化jszip
const loader = new GLTFLoader();
// 这里的 工具类是配合jszip使用的,工具类读取zip数据,得到二进制数据流,存到 blob中,之后,threeJS 加载器loader去加载这个二进制数据就好了
JSZipUtils.getBinaryContent("/model/825B.zip", function (err, data) {
if (err) {
throw err; // or handle err
}
new_zip.loadAsync(data).then(function (res) {
let fileList = res.files;
for (let key in fileList) {
// 读取模型文件内容
new_zip
.file(key)
.async("arraybuffer")
.then((content) => {
// Blob构造文件地址,通过url加载模型
let blob = new Blob([content]);
let modelUrl = URL.createObjectURL(blob);
console.log(modelUrl);
loader.load(modelUrl, (gltf) => {
gltf.scene.traverse(function (child) {
if (child.isMesh) {
//模型自发光
child.material.emissive = child.material.color;
child.material.emissiveMap = child.material.map;
}
});
setScaleToFitSize(gltf.scene);
scene.add(gltf.scene);
});
});
}
});
});
};
const onProgress = (xhr) => {
// console.log("加载完成的百分比" + (xhr.loaded / xhr.total) * 100 + "%");
num.value = Math.floor((xhr.loaded / xhr.total) * 100);
};
const render = () => {
requestAnimationFrame(render);
renderer.value.render(scene, camera.value);
labelRenderer.value.render(scene, camera.value);
controls.value.update();
// console.log(scene)
};
const loadLabel = async (base) => { // 自定义 标签的加载
const { data } = await proxy.$axios.post("/getPoints", {
belongTo: path,
});
var radius = 0.4,
segemnt = 16,
rings = 16;
var sphereMaterial = new THREE.MeshLambertMaterial({ color: "#ECF0F1" });
var material = new THREE.LineBasicMaterial({ color: 0x26a7f2 });
data.forEach((v, index) => {
sphere103 = new THREE.Mesh(
new THREE.SphereGeometry(radius, segemnt, rings),
sphereMaterial
);
sphere103.name = "mesPoint";
lineGeometry = new THREE.BufferGeometry(); //three.js 125 版本以上就废弃掉 目前支持的是 bufferGeometry
const div = document.createElement("div");
div.className = "tag";
div.innerHTML = `${v.id}`;
div.addEventListener("click", (e) => {
console.log(e.target);
});
const divLabel = new CSS2DObject(div);
divLabel.addEventListener("click", (e) => {
consolelog(e.target);
});
// divLabel.visible = false;
divLabel.position.set(v.positionX * 2, v.positionY * 2, v.positionZ * 2);
const begin = new THREE.Vector3(v.positionX, v.positionY, v.positionZ);
const end = new THREE.Vector3(
v.positionX * 2,
v.positionY * 2,
v.positionZ * 2
);
lineGeometry.setFromPoints([begin, end]);
line = new THREE.Line(lineGeometry, material, THREE.LineSegments);
sphere103.position.set(v.positionX, v.positionY, v.positionZ);
line.add(divLabel);
scene.add(sphere103);
scene.add(line);
});
};
const init = async () => {
createScene(); // 创建场景
createLight(); // 创建光源
createCamera(); // 创建相机
createRender(); // 创建渲染器
createControls(); // 创建控件对象
render(); // 渲染
loadSTL(); // 加载模型
};
function raycastMeshes(callback, raycaster) {
let intersects = [];
let meshes = [];
// 获取整个场景
let theScene = scene || new THREE.Scene();
console.log(theScene);
// 获取鼠标点击点的射线
let theRaycaster = raycaster || new THREE.Raycaster();
// 对场景及其子节点遍历
for (let i in theScene.children) {
// 如果场景的子节点是Group或者Scene对象
if (
theScene.children[i] instanceof THREE.Group ||
theScene.children[i] instanceof THREE.Scene
) {
// 场景子节点及其后代,被射线穿过的模型的数组集合
// intersects = theRaycaster.intersectObjects(theScene.children[i].children, true)
let rayArr = theRaycaster.intersectObjects(
theScene.children[i].children,
true
);
intersects.push(...rayArr);
console.log(intersects);
} else if (theScene.children[i] instanceof THREE.Mesh) {
let rayArr = theRaycaster.intersectObjects(
theScene.children[i].children,
true
);
meshes.push(rayArr);
console.log(meshes);
// 如果场景的子节点是Mesh网格对象,场景子节点被射线穿过的模型的数组集合
// intersects.push(theRaycaster.intersectObject(theScene.children[i]))
}
}
intersects = filtersVisibleFalse(intersects); // 过滤掉不可见的
// 被射线穿过的模型的数组集合
if (intersects && intersects.length > 0) {
return callback(intersects);
} else {
// this.hiddenDetailDiv()
return null;
}
}
function filtersVisibleFalse(arr) {
let arrList = arr;
if (arr && arr.length > 0) {
arrList = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i].object.visible) {
arrList.push(arr[i]);
}
}
}
return arrList;
}
function clickApp(intersects) {
if (intersects[0].object !== undefined) {
// console.log(intersects[0].object, '这就是成功点击到的对象了~')
console.log(intersects);
}
}
onMounted(() => {
init();
});
</script>
<template>
<div>
<div class="progress" v-show="isShow">
<el-progress
:text-inside="true"
:stroke-width="24"
:percentage="num"
status="success"
/>
<div class="progressInfo">{{ info }}</div>
</div>
<div class="main" id="container"></div>
</div>
</template>
最终效果: