s

【重要-threeJS 渲染性能上的优化方案】--- 加载读取渲染压缩包中的模型

需要准备的插件和包(threeJS等包省略,这个三是实现标题功能核心的包):

  1. jszip  Docs:JSZip (stuk.github.io)
  2. jszip-utils
  3. 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>

最终效果:

 

posted @ 2022-05-07 17:01  努力不搬砖的iori  阅读(2376)  评论(0编辑  收藏  举报