如何在React Three Fiber中实现GLB模型拆分为部件并缩放定位功能
React Three Fiber 实现 GLB 模型部件拆分与缩放定位方案
要实现类似 Autodesk Forge 的模型拆分、选中定位功能,核心分为两个核心模块:解析 GLB 模型提取所有独立部件并存储初始状态,分别实现爆炸拆分逻辑和部件选中后的相机适配逻辑。
前置依赖
你需要提前安装对应工具库:
@react-three/fiber:Three.js 的 React 封装核心库@react-three/drei:常用 R3F 工具组件库,包含轨道控制器、GLB 加载等封装能力@react-three/gltfjsx(可选):可以将 GLB 模型一键生成对应 React 组件,方便自定义每个部件的属性
步骤1:GLB 模型加载与部件信息提取
加载 GLB 时递归遍历所有子节点,筛选出所有 Mesh 类型的节点,存储每个部件的初始位置、包围盒信息,方便后续计算:
import { useGLTF, OrbitControls } from '@react-three/drei' import { useRef, useState, useMemo } from 'react' import * as THREE from 'three' const ModelViewer = () => { const gltf = useGLTF('/你的模型路径/model.glb') const partsRef = useRef([]) // 存储所有部件的引用 const controlsRef = useRef() // 轨道控制器引用 const [explodeFactor, setExplodeFactor] = useState(0) // 拆分系数,0为原始状态,数值越大拆分距离越远 // 计算整个模型的中心点,作为拆分的基准原点 const modelCenter = useMemo(() => { const box = new THREE.Box3().setFromObject(gltf.scene) const center = new THREE.Vector3() box.getCenter(center) return center }, [gltf]) // 遍历模型提取所有Mesh部件 useMemo(() => { partsRef.current = [] gltf.scene.traverse((child) => { if (child.isMesh) { // 存储每个部件的初始位置、原始包围盒 child.userData.initialPosition = child.position.clone() child.userData.boundingBox = new THREE.Box3().setFromObject(child) partsRef.current.push(child) } }) }, [gltf])
步骤2:实现模型拆分(爆炸)效果
原理是每个部件沿着「模型中心到自身中心」的方向,按照拆分系数偏移位置,系数为 0 时回到原始位置:
// 监听拆分系数变化,更新所有部件位置 useMemo(() => { partsRef.current.forEach(part => { const partCenter = new THREE.Vector3() part.userData.boundingBox.getCenter(partCenter) // 计算从模型中心指向部件中心的方向向量 const direction = partCenter.clone().sub(modelCenter).normalize() // 按照拆分系数偏移部件位置,0.1是拆分幅度系数,可根据模型大小自行调整 const offset = direction.multiplyScalar(explodeFactor * 0.1) part.position.copy(part.userData.initialPosition.clone().add(offset)) }) }, [explodeFactor, modelCenter])
步骤3:实现部件选中缩放定位
给每个 Mesh 绑定点击事件,点击时计算部件的包围盒,调用控制器的 fitToBounds 方法自动适配相机位置,实现缩放定位:
const handlePartClick = (part, e) => { e.stopPropagation() // 适配相机到选中部件的包围盒,第二个参数控制是否开启动画过渡 controlsRef.current.fitToBounds(part.userData.boundingBox, true) // 如果需要高亮选中部件,可在这里修改材质属性或者添加外轮廓效果 } return ( <> <OrbitControls ref={controlsRef} /> <primitive object={gltf.scene} onClick={(e) => handlePartClick(e.object, e)} /> {/* 拆分控制滑块示例,可自行替换为UI组件 */} <div style={{ position: 'absolute', top: 20, left: 20, background: '#fff', padding: '10px' }}> <label>模型拆分程度:</label> <input type="range" min="0" max="10" value={explodeFactor} onChange={(e) => setExplodeFactor(Number(e.target.value))} /> </div> </> ) }
注意事项
- 导出 GLB 模型时需要确保每个独立部件都是单独的 Mesh 节点,不要合并不必要的几何体,否则无法拆分出单个部件
- 如果部件存在嵌套分组,遍历的时候要确保所有层级的 Mesh 都被提取到,不要只遍历第一层子节点
- 拆分幅度系数可以根据自己的模型尺寸调整,避免拆分后部件偏移过远或者偏移不足
- 如果需要选中部件时同步高亮,可直接修改对应 Mesh 的材质属性,也可以搭配 drei 的
Outline组件实现外轮廓高亮效果,交互体验更好
内容的提问来源于stack exchange,提问作者hannesvieider
相关产品推荐
相关产品推荐

