<template>
<div ref="modelThree"></div>
</template>
<script setup>
import * as THREE from 'three'
import { ref, onMounted, onBeforeUnmount } from "vue";
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
let scene, camera, renderer, controls = null;
/********************** 创建3D场景 ***********************/
scene = new THREE.Scene();
/********************** 创建光源 ***********************/
const createdLight = () => {
//环境光:没有特定方向,整体改变场景的光照明暗
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
//平行光
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(200, 500, 100); // 根据需要自行调整位置
dirLight.castShadow = true; // 启用阴影投射
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
scene.add(dirLight);
}
createdLight();
/********************** 创建相机 ***********************/
const createdCamera = () => {
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 20000);
camera.position.set(0, 1000, 5000);
camera.lookAt(0, 300, 500);
}
createdCamera();
/********************** 创建渲染器对象 ***********************/
const createRender = () => {
renderer = new THREE.WebGLRenderer( {
alpha: true , //透明度
antialias:true, //开启抗锯齿
// toneMapping: 2, //曝光度
});
renderer.setClearColor(0x011D32, .5); //设置背景颜色和透明度
renderer.setSize(window.innerWidth, window.innerHeight);
// 开启 HDR 渲染
renderer.toneMapping = THREE.ACESFilmicToneMapping;
//阴影
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setPixelRatio( window.devicePixelRatio * 2); //像素采样率
//色彩映射
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.outputEncoding = THREE.sRGBEncoding;//渲染器输出编码
}
createRender();
/********************** 创建轨道控制器 ***********************/
const createControls = () => {
controls = new OrbitControls(camera, renderer.domElement);
controls.minPolarAngle = 0; // 设置最小极角为0,允许向上旋转
controls.maxPolarAngle = Math.PI; // 设置最大极角为π,允许向下旋转
}
createControls();
/********************** 加载glb模型 ***********************/
//加载glb模型
const loadGlb = () => {
let dracoLoader = new DRACOLoader();
let loader = new GLTFLoader();
dracoLoader.setDecoderPath('/draco/'); //解压缩路径
loader.setDRACOLoader(dracoLoader); //加载文件
loader.load("/mod/glb/newGlb/newGlb.glb", glb => {
glb.scene.traverse( function ( child ) {
if (child.isMesh) {
// 批量更改所有Mesh的材质
child.castShadow = true; // 设置网格投射阴影
child.receiveShadow = true; // 设置网格接收阴影
child.material = new THREE.MeshStandardMaterial({ //PBR物理材质
map: child.material.map, //获取原来材质的颜色贴图属性值
side: THREE.DoubleSide, //双面贴图,不设置则模型底部镂空,设置则模型内部易遮挡相机,减少能观察的范围,作取舍
transparent: true, //处理透明贴图背景变黑
alphaTest: .9, //透明度,处理透明贴图直接透过模型
})
}
});
scene.add(glb.scene);
focusModel(glb.scene); // 计算相机距离模型的最佳位置
});
}
loadGlb();
// 计算相机距离模型的最佳位置
const focusModel = (model) => {
let boundingBox = new THREE.Box3().setFromObject(model); // 获取模型的边界框
let center = boundingBox.getCenter(new THREE.Vector3()); // 获取边界框的中心点
let size = boundingBox.getSize(new THREE.Vector3()); // 获取边界框的大小
//设置旋转基点为模型中心
controls.target.copy(center);
// 计算相机距离模型的最佳位置
let maxDimension = Math.max(size.x, size.y, size.z);
let distance = maxDimension / (2.5 * Math.tan(THREE.MathUtils.degToRad(camera.fov) / 2));
// 设置相机位置和目标
camera.position.copy(center);
camera.position.z += distance;
camera.lookAt(center);
}
/********************** 挂载及渲染 ***********************/
const modelThree = ref(null);
onMounted(()=>{
modelThree.value.appendChild(renderer.domElement);
})
const renderScene = () => {
if(!renderer) return;
requestAnimationFrame(renderScene);
renderer.render(scene, camera);
}
renderScene();
// 画布跟随窗口变化
window.onresize = function () {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
/********************** 清空 ***********************/
onBeforeUnmount(() => {
clear();
})
const clear = () => {
clearThree(); //删除模型和贴图
destroyThreejs(); //清除场景控件
}
//清除场景控件
const destroyThreejs = () => {
try {
renderer.dispose();
renderer.forceContextLoss();
renderer.content = null;
let gl = renderer.domElement.getContext("webgl");
if (gl && gl.getExtension("WEBGL_lose_context")) {
gl.getExtension("WEBGL_lose_context").loseContext();
}
renderer = null;
camera = null;
scene.traverse((child) => {
if (child.material) {
child.material.dispose();
}
if (child.geometry) {
child.geometry.dispose();
}
child = null;
});
scene = null;
} catch (e) {
console.error("Failed to destroy threejs", e);
}
}
//删除模型和贴图
const clearThree = () => {
while(scene.children.length > 0){
clearThree(scene.children[0])
scene.remove(scene.children[0]);
}
if(scene.geometry) scene.geometry.dispose()
if(scene.material){
Object.keys(scene.material).forEach(prop => {
if(!scene.material[prop])
return
if(typeof scene.material[prop].dispose === 'function')
scene.material[prop].dispose()
})
scene.material.dispose()
}
}
</script>
<style lang="scss" scoped>
</style>