需求背景:前端对glb/gltf模型进行线上管理,支持上传本地模型,每次上传后展示模型,在提交给后端的时候带上该模型的截图,具体效果不便展示,相关代码如下:
1.安装依赖
// "three": "^0.162.0",
npm install three
2.引入依赖,初始化画布并渲染模型
<div
ref="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,
});
},