需求背景:前端对glb/gltf模型进行线上管理,支持上传本地模型,每次上传后展示模型,在提交给后端的时候带上该模型的截图,具体效果不便展示,相关代码如下:
1.安装依赖
// "three": "^0.162.0",
npm install three
2.引入依赖,初始化画布并渲染模型
<divref="modelDisplay"class="model-display"id="model-display"
></div>
// 1.引入
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment";
import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module";var scene;
var camera;
var renderer;
var control;// 2.初始化
methods:{//初始化initCanvas(item) {const _this = this;const domDiv = document.getElementById("model-display");// 场景,相机scene = new THREE.Scene();this.initCamera(domDiv);// 渲染器// 参数 antialias: true 为开启抗锯齿renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(domDiv.offsetWidth, domDiv.offsetHeight);renderer.setClearColor(0x000000, 0);domDiv.appendChild(renderer.domElement);// 材质const environment = new RoomEnvironment();const pmremGenerator = new THREE.PMREMGenerator(renderer);scene.environment = pmremGenerator.fromScene(environment).texture;this.initControl();// 坐标系// const axesHelper = new THREE.AxesHelper(14);// scene.add(axesHelper);function animate() {requestAnimationFrame(animate);renderer.render(scene, camera);}animate();},initCamera(domDiv) {camera = new THREE.PerspectiveCamera(45,domDiv.offsetWidth / domDiv.offsetHeight,1,2200);camera.position.set(-70, 50, 50);//设置相机默认看向哪里 三个 0 代表 默认看向原点camera.lookAt(0, 0, 0);},initControl() {// 控制器control = new OrbitControls(camera, renderer.domElement);},// 当选择的模型发生变化时,调用loader方法loader() {const _this = this;const loader = new GLTFLoader();loader.load(process.env.VUE_APP_BASE_API + this.form.modelPath,(gltf) => {scene.add(_this.editModel(gltf.scene));renderer.render(scene, camera);});},// 调整模型缩放editModel(_scene) {const boxHelper = new THREE.BoxHelper(_scene);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 scale = camera.position.z / maxDiameter;_scene.scale.set(scale, scale, scale); // 调整模型缩放_scene.position.set(0, 0, 0);return _scene;},// 关闭后清除画布,保证下次打开的时候是空白画布disposeScene(scene) {while (scene.children.length > 0) {let obj = scene.children[0];scene.remove(obj);if (obj.geometry) {obj.geometry.dispose();}if (obj.material) {if (Array.isArray(obj.material)) {obj.material.forEach((material) => {material.dispose();});} else {obj.material.dispose();}}}renderer.clear();},
}
3.截图
// 截屏方法screenPic() {return new Promise((resolve, reject) => {let canvas = renderer.domElement;renderer.render(scene, camera);const imgUrl = canvas.toDataURL("image/png", 1.0);resolve(this.base64ToFile(imgUrl, this.form.modelName.split(".")[0]));});},// base64编码图片转file格式base64ToFile(base64, fileName) {if (typeof base64 != "string") {return;}var arr = base64.split(",");var type = arr[0].match(/:(.*?);/)[1];var fileExt = type.split("/")[1];var bstr = atob(arr[1]);var n = bstr.length;var u8arr = new Uint8Array(n);while (n--) {u8arr[n] = bstr.charCodeAt(n);}return new File([u8arr], `${fileName}.` + fileExt, {type: type,});},