import * as THREE from 'three'; import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'; // 导入轨道控制器 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import gsap from 'gsap'; import ResourceTracker from '/@/utils/threejs/ResourceTracker'; const resourceTracker = new ResourceTracker(); const track = resourceTracker.track.bind(resourceTracker); class UseThree { container: HTMLCanvasElement; camera: THREE.PerspectiveCamera | null = null; scene: THREE.Scene | null = null; renderer: THREE.WebGLRenderer | null = null; orbitControls: OrbitControls | null = null; giftLoader: THREE.Object3D | null = null; animationMixer: THREE.AnimationMixer | null = null; animationAction: THREE.AnimationAction | null = null; clock: THREE.Clock = new THREE.Clock(); // 计时器 timeoutId: NodeJS.Timeout | null = null; animationId: number = 0; constructor(selector) { this.animationId = 0; this.container = document.querySelector(selector); //初始化 this.init(); this.animate(); window.addEventListener('resize', this.resizeRenderer.bind(this)); // 添加滚动事件,鼠标滚动模型执行动画 // window.addEventListener('wheel', this.wheelRenderer.bind(this)); } init() { // 初始化场景 this.initScene(); // 初始化环境光 this.initLight(); // 初始化相机 this.initCamera(); //初始化渲染器 this.initRenderer(); // 初始化控制器 this.initControles(); } initScene() { this.scene = new THREE.Scene(); const axesHelper = new THREE.AxesHelper(5); // this.scene.add(axesHelper); } initLight() { const light = new THREE.AmbientLight(0xfafafa, 1); light.position.set(0, 200, 200); (this.scene as THREE.Scene).add(light); } initCamera() { this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100); this.camera.position.set(0, 0.2, 0.3); } initRenderer() { this.renderer = new THREE.WebGLRenderer({ antialias: true }); // 设置屏幕像素比 this.renderer.setPixelRatio(window.devicePixelRatio); // 设置渲染的尺寸 this.renderer.setSize(window.innerWidth, window.innerHeight); // 色调映射 this.renderer.toneMapping = THREE.ACESFilmicToneMapping; // 曝光程度 this.renderer.toneMappingExposure = 3; this.container.appendChild(this.renderer.domElement); } initControles() { this.orbitControls = new OrbitControls(this.camera as THREE.Camera, this.renderer?.domElement); } test() { // const planeGeometry = new THREE.PlaneGeometry(1, 1); const meshStandardMaterial = new THREE.MeshStandardMaterial(); const mesh = new THREE.Mesh(planeGeometry, meshStandardMaterial); mesh.position.set(0, -0.2, 0); mesh.rotation.z = Math.PI / 4; this.scene?.add(mesh); } setGLTFModel(modalName) { const a = new Date().getTime() / 1000; return new Promise(async (resolve, reject) => { try { const db = window['CustomDB']; const modalArr = await db.modal.where('modalName').equals(modalName).toArray(); if (modalArr.length > 0) { const modalValue = modalArr[0].modalVal; new THREE.ObjectLoader().parse(modalValue, (e) => { const group = e.children[0]; const cityMaterial = new THREE.MeshBasicMaterial({ color: new THREE.Color(0x0c016f), }); group?.traverse((item: THREE.Mesh) => { if (item.type === 'Mesh') { item.material = cityMaterial; this.setMaterial(item); } }); this.scene?.add(e.children[0]); console.log(new Date().getTime() / 1000 - a + ' s'); resolve(this.scene); }); } } catch (error) { reject('加载模型出错'); } }); } setMaterial(obj) { obj.geometry.computeBoundingBox(); const { max, min } = obj.geometry.boundingBox; const distance = max.y - min.y + 2; obj.material.onBeforeCompile = (shader) => { shader.uniforms.uDistance = { value: distance, }; shader.uniforms.uTopColor = { value: new THREE.Color(0xaaaeff), }; this.addGrad(shader); this.addRadiusSpread(shader); this.addLineSpread(shader); this.addToSpread(shader); }; } addGrad(shader) { shader.vertexShader = shader.vertexShader.replace( '#include ', ` #include varying vec3 vPosition; ` ); shader.vertexShader = shader.vertexShader.replace( '#include ', ` #include vPosition = position; ` ); shader.fragmentShader = shader.fragmentShader.replace( '#include ', ` #include uniform vec3 uTopColor; uniform float uDistance; varying vec3 vPosition; ` ); shader.fragmentShader = shader.fragmentShader.replace( '#include ', ` #include vec4 distGradColor = gl_FragColor; // 设置混合的百分比 float gradMix = (vPosition.y + (uDistance / 2.0)) / uDistance / 5.0; // 设置混合颜色 vec3 colorMix = mix(distGradColor.xyz, uTopColor, gradMix); gl_FragColor = vec4(colorMix, 1.0); //#end# ` ); } addRadiusSpread(shader) { // 设置扩散中心店 shader.uniforms.uRadiusSpreadCenter = { value: new THREE.Vector2(0, 0), }; // 扩散时间 shader.uniforms.uRadiusSpreadTime = { value: 0, }; shader.uniforms.uRadiusSpreadWidth = { value: 20, }; shader.fragmentShader = shader.fragmentShader.replace( '#include ', ` #include uniform vec2 uRadiusSpreadCenter; uniform float uRadiusSpreadTime; uniform float uRadiusSpreadWidth; ` ); shader.fragmentShader = shader.fragmentShader.replace( '//#end#', ` float spreadRadius = distance(vPosition.xz + vec2(-720, -550), uRadiusSpreadCenter); // 扩散范围 float spreadIndex = -(spreadRadius - uRadiusSpreadTime) * (spreadRadius - uRadiusSpreadTime ) + uRadiusSpreadWidth; if(spreadIndex > 0.0){ gl_FragColor = mix(gl_FragColor, vec4(1.0, 1.0, 1.0, 1.0), spreadIndex / uRadiusSpreadWidth ); } //#end# ` ); gsap.to(shader.uniforms.uRadiusSpreadTime, { value: 250, duration: 1, ease: 'none', repeat: -1, }); } addLineSpread(shader) { // 设置扩散中心店 shader.uniforms.uLineSpreadCenter = { value: new THREE.Vector2(0, 0), }; // 扩散时间 shader.uniforms.uLineSpreadTime = { value: 0, }; shader.uniforms.uLineSpreadWidth = { value: 20, }; shader.fragmentShader = shader.fragmentShader.replace( '#include ', ` #include uniform vec2 uLineSpreadCenter; uniform float uLineSpreadTime; uniform float uLineSpreadWidth; ` ); shader.fragmentShader = shader.fragmentShader.replace( '//#end#', ` float spreadLine = vPosition.x - 400.0; // 扩散范围 float spreadLineIndex = -(spreadLine - uLineSpreadTime * 5.0) * (spreadLine - uLineSpreadTime * 5.0) + uLineSpreadWidth; if(spreadLineIndex > 0.0){ gl_FragColor = mix(gl_FragColor, vec4(1.0, 1.0, 1.0, 1.0), spreadLineIndex / uLineSpreadWidth ); } //#end# ` ); gsap.to(shader.uniforms.uLineSpreadTime, { value: 150, duration: 2, ease: 'none', repeat: -1, }); } addToSpread(shader) { // 设置扩散中心店 shader.uniforms.uTopSpreadCenter = { value: new THREE.Vector2(0, 0), }; // 扩散时间 shader.uniforms.uTopSpreadTime = { value: 0, }; shader.uniforms.uTopSpreadWidth = { value: 20, }; shader.fragmentShader = shader.fragmentShader.replace( '#include ', ` #include uniform vec2 uTopSpreadCenter; uniform float uTopSpreadTime; uniform float uTopSpreadWidth; ` ); shader.fragmentShader = shader.fragmentShader.replace( '//#end#', ` float spreadTopLine = vPosition.y * 2.0; // 扩散范围uTopSpreadWidth float spreadTopIndex = -(spreadTopLine - uTopSpreadTime) * (spreadTopLine - uTopSpreadTime) + uTopSpreadWidth; if(spreadTopIndex > 0.0){ gl_FragColor = mix(gl_FragColor, vec4(0.0, 1.0, 1.0, 1.0), spreadTopIndex / uTopSpreadWidth / uTopSpreadTime * 2.0); } //#end# ` ); gsap.to(shader.uniforms.uTopSpreadTime, { value: 200, duration: 5, ease: 'none', repeat: -1, }); } addLine(geometry) { const edges = new THREE.EdgesGeometry(geometry); const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xff0000 })); (this.scene as THREE.Scene).add(line); // console.log(line); } /* 场景环境背景 */ setEnvMap(hdr) { new RGBELoader().setPath('/public/model/hdr/').load(hdr + '.hdr', (texture) => { texture.mapping = THREE.EquirectangularReflectionMapping; (this.scene as THREE.Scene).background = texture; (this.scene as THREE.Scene).environment = texture; }); } render() { const delta = this.clock.getDelta(); this.renderer?.render(this.scene as THREE.Object3D, this.camera as THREE.Camera); this.animationMixer?.update(delta); } animate() { // this.renderer?.setAnimationLoop(this.render.bind(this)); if (this.animationId != -1) { this.render(); this.animationId = requestAnimationFrame(this.animate.bind(this)); } } resizeRenderer() { // 更新相机比例 (this.camera as THREE.PerspectiveCamera).aspect = window.innerWidth / window.innerHeight; // 刷新相机矩阵 this.camera?.updateProjectionMatrix(); // 设置场景尺寸 this.renderer?.setSize(window.innerWidth, window.innerHeight); } 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); } clearScene() { this.scene?.children.forEach((obj: any) => { if (obj.type === 'Group') { obj.traverse(function (item: any) { 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; this.renderer.domElement = null; this.renderer = null; // css3dRender.domElement = null; // css3dRender = null; // model3D.innerHTML = ''; // css3D.innerHTML = ''; // model3D = null // css3D = null !!this.scene?.clear ?? this.scene?.clear(); cancelAnimationFrame(this.animationId); this.animationId = -1; // this.stats = null // scene = null THREE.Cache.clear(); console.log('3D环境已清理干净'); } } export default UseThree;