import * as THREE from 'three'; import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'; // 导入轨道控制器 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer.js'; // import gsap from 'gsap'; import ResourceTracker from '/@/utils/threejs/ResourceTracker.js'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'; import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'; import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; import { setModalCenter } from '/@/utils/threejs/util'; import Stats from 'three/examples/jsm/libs/stats.module.js'; import { useModelStore } from '/@/store/modules/threejs'; class UseThree { stats: Stats | null = null; // 帧 canvasContainer: HTMLCanvasElement | null; //canvas 容器 CSSCanvasContainer: HTMLCanvasElement | null = null; // css canvas 容器 camera: THREE.PerspectiveCamera | null = null; // 相机 scene: THREE.Scene | null = null; renderer: THREE.WebGLRenderer | null = null; css3dRender: CSS3DRenderer | null = null; orbitControls: OrbitControls | null = null; animationAction: THREE.AnimationAction | null = null; clock: THREE.Clock | null = new THREE.Clock(); // 计时器 timeoutId: NodeJS.Timeout | null = null; animationId = 0; spriteText: THREE.Sprite | null = null; mouse = new THREE.Vector2(); rayCaster: THREE.Raycaster | null = null; animations: THREE.AnimationClip[] = []; mixers: THREE.AnimationMixer[] = []; FPS = 40; timeS = 0; resourceTracker: ResourceTracker | null = null; track: any = null; composer; //后期 constructor(canvasSelector, cssCanvas?) { this.resourceTracker = new ResourceTracker(); this.track = this.resourceTracker.track.bind(this.resourceTracker); this.animationId = 0; this.canvasContainer = document.querySelector(canvasSelector); //初始化 this.init(cssCanvas); // this.animate(); window.addEventListener('resize', this.resizeRenderer.bind(this)); // 添加滚动事件,鼠标滚动模型执行动画 // window.addEventListener('wheel', this.wheelRenderer.bind(this)); } init(cssCanvas?) { // 初始化场景 this.initScene(); // 初始化环境光 // this.initLight(); // 初始化相机 this.initCamera(); //初始化渲染器 this.initRenderer(); // 初始化控制器 this.initControles(); if (cssCanvas) { this.initCSSRenderer(cssCanvas); } // this.setTestPlane(); this.rayCaster = this.track(new THREE.Raycaster()); this.createStats(); // this.removeSawtooth(); } createStats() { this.stats = Stats(); this.stats?.setMode(0); this.stats.domElement.style.position = 'absolute'; this.stats.domElement.style.left = '200px'; this.stats.domElement.style.top = '100px'; document.body.appendChild(this.stats.domElement); } initScene() { this.scene = this.track(new THREE.Scene()); // const axesHelper = new THREE.AxesHelper(100); // this.scene?.add(axesHelper); // const size = 1000; // const divisions = 10; // const gridHelper = new THREE.GridHelper(size, divisions); // this.scene?.add(gridHelper); } initLight() { const light = this.track(new THREE.AmbientLight(0xffffff, 1)); light.position.set(0, 1000, 1000); (this.scene as THREE.Scene).add(light); } initCamera() { this.camera = this.track(new THREE.PerspectiveCamera(50, this.canvasContainer.clientWidth / this.canvasContainer.clientHeight, 0.000001, 10000)); // const helper = new THREE.CameraHelper(this.camera); // this.scene?.add(helper); } initRenderer() { this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true }) as THREE.WebGLRenderer; // 设置屏幕像素比 this.renderer?.setPixelRatio(window.devicePixelRatio); // 设置渲染的尺寸 this.renderer?.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); // 色调映射 this.renderer.toneMapping = THREE.ACESFilmicToneMapping; this.renderer.outputEncoding = THREE.sRGBEncoding; this.renderer.shadowMap.enabled = true; // this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 曝光程度 this.renderer.toneMappingExposure = 1; this.canvasContainer?.appendChild(this.renderer.domElement); } initCSSRenderer(cssCanvas) { this.CSSCanvasContainer = document.querySelector(cssCanvas); this.css3dRender = this.track(new CSS3DRenderer()) as CSS3DRenderer; this.css3dRender.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); this.CSSCanvasContainer?.appendChild(this.css3dRender.domElement); this.css3dRender.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera); this.orbitControls = this.track(new OrbitControls(this.camera as THREE.Camera, this.css3dRender?.domElement)) as OrbitControls; this.orbitControls.update(); } initControles() { this.orbitControls = this.track(new OrbitControls(this.camera as THREE.Camera, this.renderer?.domElement)) as OrbitControls; // this.orbitControls.enableDamping = true; } setModel(modalName) { const modelStore = useModelStore(); return new Promise(async (resolve, reject) => { try { const a = new Date().getTime(); let modalValue = modelStore.modelArr.get(modalName); if (!modalValue) { const db = window['CustomDB']; const modalArr = await db.modal.where('modalName').equals(modalName).toArray(); modalValue = modalArr[0].modalVal; } if (modalValue) { console.log('模型下载速度', new Date().getTime() - a); const b = new Date().getTime(); try { const gltfLoader = this.track(new GLTFLoader()); const dracoLoader = this.track(new DRACOLoader()); dracoLoader.setDecoderPath('/model/draco/gltf/'); dracoLoader.setDecoderConfig({ type: 'js' }); //使用兼容性强的draco_decoder.js解码器 dracoLoader.preload(); gltfLoader.setDRACOLoader(dracoLoader); gltfLoader.setPath('/model/glft/'); gltfLoader.parse( modalValue, '/model/glft/', (gltf) => { const object = this.track(gltf.scene); setModalCenter(object); object.traverse((obj) => { if (obj instanceof THREE.Mesh) { obj.material.emissiveIntensity = 1; obj.material.emissiveMap = obj.material.map; obj.material.blending = THREE.CustomBlending; // if (obj.name !== 'buxiugangse') { // obj.receiveShadow = true; // } // obj.castShadow = true; } }); object.name = modalName; console.log('模型渲染时间', object, new Date().getTime() - b); resolve(gltf); dracoLoader.dispose(); }, (err) => { console.log(err); } ); } catch (error) { console.log(error); } } } catch (error) { reject('加载模型出错'); } }); } setTestPlane() { const plane = this.track(new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshPhongMaterial({ color: 0x999999, specular: 0x101010 }))); plane.rotation.x = -Math.PI / 2; plane.position.y = 0.03; plane.receiveShadow = true; this.scene?.add(plane); } removeSawtooth() { this.composer = this.track(new EffectComposer(this.renderer as THREE.WebGLRenderer)); const FXAAShaderPass = this.track(new ShaderPass(FXAAShader)); FXAAShaderPass.uniforms['resolution'].value.set(1 / this.canvasContainer.clientWidth, 1 / this.canvasContainer.clientHeight); FXAAShaderPass.renderToScreen = true; this.composer.addPass(FXAAShaderPass); } /* 场景环境背景 */ setEnvMap(hdr) { const pmremGenerator = new THREE.PMREMGenerator(this.renderer as THREE.WebGLRenderer); // 使用hdr作为背景色 pmremGenerator.compileEquirectangularShader(); this.track(new THREE.TextureLoader()) .setPath('/model/hdr/') .load(hdr + '.jpeg', (texture) => { texture.mapping = THREE.EquirectangularReflectionMapping; const envMap = pmremGenerator.fromEquirectangular(texture).texture; (this.scene as THREE.Scene).environment = envMap; pmremGenerator.dispose(); }); } render() { this.stats?.update(); this.startAnimation(); this.orbitControls?.update(); this.camera?.updateMatrixWorld(); // this.composer?.render(); // this.css3dRender?.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera) this.renderer?.render(this.scene as THREE.Object3D, this.camera as THREE.Camera); } /* 漫游 */ startMY() {} /* 初始动画 */ startAnimation() {} animate() { if (this.animationId != -1) { setTimeout(() => { this.animationId = requestAnimationFrame(this.animate.bind(this)); }, 1000 / 30); this.render(); } } resizeRenderer() { // 更新相机比例 (this.camera as THREE.PerspectiveCamera).aspect = this.canvasContainer.clientWidth / this.canvasContainer.clientHeight; // 刷新相机矩阵 this.camera?.updateProjectionMatrix(); // 设置场景尺寸 this.renderer?.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); // this.css3dRender?.setSize(this.canvasContainer.clientWidth, this.canvasContainer.clientHeight); } wheelRenderer(e: WheelEvent) { const timeScale = e.deltaY > 0 ? 1 : -1; this.animationAction?.setEffectiveTimeScale(timeScale); (this.animationAction as THREE.AnimationAction).paused = false; this.animationAction?.play(); if (this.timeoutId) { clearTimeout(this.timeoutId); } this.timeoutId = setTimeout(() => { this.animationAction?.halt(0.5); }, 500); } deleteModal() { try { const gl = this.renderer?.domElement?.getContext('webgl'); gl && gl.getExtension('WEBGL_lose_context')?.loseContext(); const gl1 = this.css3dRender?.domElement?.getContext('webgl'); gl1 && gl1.getExtension('WEBGL_lose_context')?.loseContext(); this.renderer?.forceContextLoss(); this.renderer?.dispose(); if (this.renderer) this.renderer.domElement = null; this.renderer = null; this.resourceTracker && this.resourceTracker.dispose(); if (this.canvasContainer) this.canvasContainer.innerHTML = ''; this.canvasContainer = null; this.orbitControls = null; this.css3dRender = null; this.camera = null; this.clock = null; cancelAnimationFrame(this.animationId); this.animationId = -1; this.scene = null; this.mixers = []; console.log('销毁场景', this.scene); } catch (error) { console.log(error); } THREE.Cache.clear(); console.log('3D环境已清理干净'); } deleteModal1() { this.scene?.children.forEach((obj) => { if (obj.type === 'Group') { obj.traverse(function (item) { if (item.type === 'Mesh') { item.geometry.dispose(); item.material.dispose(); !!item.clear && item.clear(); } }); } else if (obj.material) { obj.material.dispose(); } else if (obj.geometry) { obj.geometry.dispose(); } this.scene?.remove(obj); !!obj.clear ?? obj.clear(); obj = null; }); const gl = this.renderer?.domElement.getContext('webgl'); gl && gl.getExtension('WEBGL_lose_context')?.loseContext(); this.renderer?.forceContextLoss(); this.renderer?.dispose(); this.camera = null; this.orbitControls = null; if (this.renderer) this.renderer.domElement = null; this.renderer = null; if (this.css3dRender) this.css3dRender.domElement = null; this.css3dRender = null; if (this.canvasContainer) this.canvasContainer.innerHTML = ''; if (this.CSSCanvasContainer) this.CSSCanvasContainer.innerHTML = ''; this.resourceTracker && this.resourceTracker.dispose(); !!this.scene?.clear ?? this.scene?.clear(); cancelAnimationFrame(this.animationId); this.animationId = -1; this.stats = null; this.scene = null; THREE.Cache.clear(); } } export default UseThree;