模型工作
由于obj模型可能是一个整体,而不是一个个单独的部件,所以再炸开的时候,是无法进行部件分解的。因此,在搭建代码环境前,需要检查模型是否是可分解的模型。
上图为单一模型,没有分解部件,因此需要先在建模软件进行部件分解
主要流程为:
-
A键全选模型
-
tab键切换成编辑模式
-
此时,鼠标选取一些部件的点,通过ctrl + l键进行部件关联
-
鼠标右键->分离->分离选中项,则键该部件进行分离
-
以此此操作,最终将整个模型部件进行分离,此时每个部件都有一个单独的名称
代码环境搭建
通过threejs搭建场景快捷便利,实现炸开效果,需要对单独的部件进行操作
炸开效果
主要流程:
-
初始化三维场景,需要新建一个场景,设置背景光、基础灯光、轨道控制器等
-
加载模型,调整模型姿态,相机姿态
-
模型炸开:
-
模型炸开需要考虑到一个炸开中心,以及各个部件的炸开方向
-
一般选取模型中心点为炸开中心
-
炸开方向可以通过每个部件的质心到炸开中心形成一个方向向量,以此向量形成炸开方向
-
计算每个部件质心到炸开中心的距离,作为一个距离权重,以此保证在相似方向上的部件,离炸开中心越远,分离的距离也越远
//收集所有部件信息 model.traverse((child) => { //计算每个部件质心 if (child.isMesh) { //计算每个部件的质心 const geometry = child.geometry; geometry.computeBoundingBox(); const partCenter = new THREE.Vector3(); geometry.boundingBox.getCenter(partCenter); child.localToWorld(partCenter); //计算基础方向向量 const direction = new THREE.Vector3() .subVectors(partCenter, targetCenter) .normalize(); // 添加随机偏移使爆炸效果更自然 const randomOffset = new THREE.Vector3( (Math.random() - 0.5) * 0.3, (Math.random() - 0.5) * 0.3, (Math.random() - 0.5) * 0.3 ); // 计算距离权重(距离中心越远,移动距离越大) const distanceWeight = partCenter.distanceTo(targetCenter) * 1; parts.push({ mesh: child, originalPosition: child.position.clone(), originalRotation: child.rotation.clone(), direction: direction.add(randomOffset).normalize(), // direction: direction, distanceWeight: distanceWeight, }); } });
-
通过gsap动画,实现过程效果
//炸开/复原切换函数 const toggleExplode = () => { isExploded.value = !isExploded.value; parts.forEach((part) => { if (isExploded.value) { //炸开动画 gsap.to(part.mesh.position, { x: part.originalPosition.x + part.direction.x * part.distanceWeight * 15, y: part.originalPosition.y + part.direction.y * part.distanceWeight * 15, z: part.originalPosition.z + part.direction.z * part.distanceWeight * 15, duration: 1, ease: "power2.out", }); //添加旋转动画 gsap.to(part.mesh.rotation, { x: part.originalRotation.x + (Math.random() - 0.5) * Math.PI * 0.2, y: part.originalRotation.y + (Math.random() - 0.5) * Math.PI * 0.2, z: part.originalRotation.z + (Math.random() - 0.5) * Math.PI * 0.2, duration: 1, ease: "power2.out", }); } else { //复原动画 gsap.to(part.mesh.position, { x: part.originalPosition.x, y: part.originalPosition.y, z: part.originalPosition.z, duration: 1, ease: "power2.inout", }); gsap.to(part.mesh.rotation, { x: part.originalRotation.x, y: part.originalRotation.y, z: part.originalRotation.z, duration: 1, ease: "power2.inOut", }); } }); };
高亮效果
涉及到相机的重定位和部件材质的替换
主要流程:
-
找到需要定位的材质
-
存储其原有材质,重新定义新材质给它,以达到高亮效果
//保存原始材质 if (highlightedMesh.value) { //恢复之前高亮部件的材质 highlightedMesh.value.material = originalMaterial.value; } //创建高亮材质 originalMaterial.value = targetPart.material.clone(); const highlightMaterial = targetPart.material.clone(); // 调整为更柔和的蓝白色高亮,更适合金属质感 highlightMaterial.emissive = new THREE.Color(0x3388ff); // 柔和的蓝色 highlightMaterial.emissiveIntensity = 0.5; // 降低发光强度,使效果更自然 // 如果需要更细腻的金属质感,可以同时调整这些参数 highlightMaterial.metalness = 0.8; // 增加金属感 highlightMaterial.roughness = 0.2; // 降低粗糙度,增加光泽感 //应用高亮材质 targetPart.material = highlightMaterial; highlightMaterial.value = targetPart;
-
重新计算部件质心,相机指向新的位置并设置移动动画
//计算部件的包围盒中心 const boundingBox = new THREE.Box3().setFromObject(targetPart); const center = boundingBox.getCenter(new THREE.Vector3()); //计算相机新位置 const offset = new THREE.Vector3(2, 2, 2); const newCameraPosition = center.clone().add(offset); //使用GSAP制作相机移动动画 gsap.to(camera.position, { x: newCameraPosition.x, y: newCameraPosition.y, z: newCameraPosition.z, duration: 2, ease: "power2.inOut", onUpdate: () => { camera.lookAt(center); }, }); //更新控制器目标点 gsap.to(controls.target, { x: center.x, y: center.y, z: center.z, duration: 1, ease: "power2.inOut.inOut", onComplete: () => { controls.update(); }, });
-
-
最终效果
-