Three.js 加载glb模型

<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>

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值