@ridp/threejs
v1.5.2
Published
基于 Three.js 的 3D 可视化工具库,提供场景管理、模型加载、性能优化等开箱即用的功能。
Readme
@ridp/threejs
基于 Three.js 的 3D 可视化工具库,提供场景管理、模型加载、性能优化等开箱即用的功能。
📢 重要提示:
- ⚠️
useThreeJs()Hook 已弃用,请使用 ThreeIns 类 (功能更强大,不限框架)- ⚠️
frameArea()方法已弃用,请使用 setView() 方法 (支持多视角切换)快速迁移:
// ❌ 旧用法 (已弃用) const { scene, camera } = useThreeJs('#container', opts); threeIns.frameArea(model, 1.0); // ✅ 新用法 (推荐) const threeIns = new ThreeIns('#container', opts); threeIns.setView(model, ViewType.ISO, { scale: 1.0 });
特性
- 🚀 开箱即用: 零配置快速搭建 Three.js 场景
- 📦 智能缓存: 基于 IndexedDB 的模型缓存机制,大幅提升二次加载速度
- 🎯 视角控制: 内置多种视角(俯视、侧视、等轴测)和平滑过渡动画
- 🔧 性能优化: 模型优化、批量加载、渐进式渲染等性能优化工具
- 🎨 辅助工具: 包围盒检测、射线拾取、碰撞检测等实用工具
- 💡 TypeScript 支持: 完整的类型定义和 JSDoc 文档
- 🌐 框架无关: 可在任何 JavaScript/TypeScript 项目中使用(Vue/React/Angular/原生JS)
安装
依赖要求
本库依赖以下 peer dependencies:
{
"peerDependencies": {
"dexie": ">=4.0.0 <5.0.0",
"vue": ">=3.0.0 <4.0.0",
"three": ">=0.178.0 <1.0.0"
}
}NPM 安装
npm install @ridp/threejs dexie@^4 vue@^3 three@^0.178Yarn 安装
yarn add @ridp/threejs dexie@^4 vue@^3 three@^0.178PNPM 安装
pnpm add @ridp/threejs dexie@^4 vue@^3 three@^0.178vite.config.js 配置
因为涉及 worker,需要添加以下配置项:
import { defineConfig } from 'vite';
export default defineConfig(() => {
return {
// plugins,
// build: { ... }
optimizeDeps: {
// ...
exclude: ['@ridp/threejs']
},
}
})
快速开始
基础场景初始化
import { ThreeIns } from '@ridp/threejs';
// 创建 3D 场景实例
const threeJsIns = new ThreeIns('#container', {
css3d: true, // 启用 CSS3D 渲染器
renderType: 'loop', // 渲染循环类型
initListener: true, // 初始化事件监听
initialFov: 75, // 初始 FOV
control: { // OrbitControls 配置
init: true,
options: {
maxDistance: Infinity,
minDistance: 0,
enablePan: true,
enableDamping: true,
dampingFactor: 0.25,
},
},
});
// 访问核心对象
console.log(threeJsIns.scene); // THREE.Scene
console.log(threeJsIns.camera); // THREE.PerspectiveCamera
console.log(threeJsIns.renderer); // THREE.WebGLRenderer
console.log(threeJsIns.control); // THREE.OrbitControls加载 GLTF 模型
import { useGLTFLoader, initEnvImage } from '@ridp/threejs';
const { asyncFetch, logCacheReport } = useGLTFLoader();
// 加载模型(带缓存)
async function loadModel() {
const modelUrl = '/models/car.glb';
const version = '1.0.0';
const [err, model] = await asyncFetch(modelUrl, version);
if (model) {
threeJsIns.scene.add(model);
threeJsIns.setView(model, ViewType.ISO, {
scale: 1.0,
showBox: true,
animate: true,
duration: 1000
});
}
// 查看缓存性能报告
logCacheReport();
}
// 加载环境贴图
await initEnvImage(threeJsIns.scene, '/hdr/studio.exr');
loadModel();核心 API
ThreeIns 类
主要的 3D 场景管理类。
构造函数
new ThreeIns(selector: string, options: ThreeInsOptions)参数:
selector: DOM 元素选择器(如#canvas-container)options: 配置对象css3d: 是否启用 CSS3D 渲染器(默认:false)renderType: 渲染循环类型,'loop'或'ondemand'(默认:'loop')initListener: 是否自动监听窗口大小变化(默认:true)initialFov: 初始垂直 FOV(默认:50)control: OrbitControls 配置init: 是否初始化控制器(默认:true)options: OrbitControls 配置对象
实例属性
scene: THREE.Scene- 场景对象camera: THREE.PerspectiveCamera- 相机对象renderer: THREE.WebGLRenderer- WebGL 渲染器css3DRenderer: THREE.CSS3DRenderer- CSS3D 渲染器(如果启用)control: THREE.OrbitControls- 轨道控制器domElement: HTMLElement- 容器 DOM 元素
主要方法
setView()
设置模型视角。
threeJsIns.setView(
model: Object3D,
viewType: ViewType,
options?: {
scale?: number, // 缩放比例,1=占满画布,0.5=50%,2=0%(默认: 1)
showBox?: boolean, // 是否显示包围盒(默认: false)
animate?: boolean, // 是否启用动画(默认: false)
duration?: number, // 动画时长(毫秒,默认: 1000)
offset?: Vector3 // 中心点偏移
}
): void视角类型 (ViewType):
ViewType.TOP- 俯视(从上往下)ViewType.RIGHT- 右视图(从右往左)ViewType.LEFT- 左侧视(从左往右)ViewType.ISO- 等轴测视角(对角线上方俯视)
示例:
// 设置等轴测视角,模型占满画布
threeJsIns.setView(model, ViewType.ISO, {
scale: 1.0,
showBox: true,
animate: true,
duration: 800
});
// 设置俯视,模型缩小到 50%
threeJsIns.setView(model, ViewType.TOP, {
scale: 0.5,
animate: true
});frameArea()
⚠️ 已弃用 - 此方法仅为兼容旧版本保留,请使用
setView()方法代替。
自动调整相机位置使模型完整显示在视口中(默认使用等轴测视角)。
threeJsIns.frameArea(
model: Object3D,
scale: number = 1,
options?: {
showBox?: boolean, // 是否显示包围盒
offset?: Vector3 // 中心点偏移
}
): void参数说明:
scale: 显示比例1.0- 模型占满画布0.5- 模型占画布 50%2.0- 模型放大到 200%
示例:
// ❌ 旧用法(已弃用)
threeJsIns.frameArea(model, 1.0);
// ✅ 推荐用法 - 使用 setView
threeJsIns.setView(model, ViewType.ISO, { scale: 1.0 });
// ❌ 旧用法(已弃用)
threeJsIns.frameArea(model, 0.5, { showBox: true });
// ✅ 推荐用法 - 使用 setView
threeJsIns.setView(model, ViewType.ISO, {
scale: 0.5,
showBox: true,
animate: true
});迁移指南:
| 旧用法 (frameArea) | 新用法 (setView) |
| ----------------------- | ---------------------------------------------- |
| frameArea(model, 1.0) | setView(model, ViewType.ISO, { scale: 1.0 }) |
| frameArea(model, 0.5) | setView(model, ViewType.ISO, { scale: 0.5 }) |
| frameArea(model, 2.0) | setView(model, ViewType.ISO, { scale: 2.0 }) |
updateCameraFOV()
根据画布宽高比动态调整相机 FOV。
threeJsIns.updateCameraFOV(): void当窗口大小变化时自动调用,确保模型在不同宽高比下正确显示。
addAnimate()
添加自定义动画函数到渲染循环。
threeJsIns.addAnimate(fn: Function): void示例:
let time = 0;
threeJsIns.addAnimate(() => {
time += 0.01;
model.rotation.y = time;
});dispose()
销毁场景,释放所有资源。
threeJsIns.dispose(): void清理内容:
- 停止渲染循环
- 移除事件监听器
- 释放几何体、材质、纹理
- 清理 Stats 性能监控
- 销毁 OrbitControls
Hooks
useThreeJs()
⚠️ 已弃用 - 此 Hook 仅为兼容旧版本保留,请使用 ThreeIns 类 代替。
Vue 3 组合式 API 风格的 Three.js 场景初始化 Hook。
为什么要弃用?
- ❌ 功能被 ThreeIns 类完全覆盖
- ❌ 缺少视角切换功能 (setView)
- ❌ 性能优化不如 ThreeIns 类
- ❌ 仅限 Vue 项目使用
- ❌ 资源清理不够完善
推荐迁移到 ThreeIns 类:
// ❌ 旧用法 (useThreeJs Hook - 已弃用)
import { useThreeJs } from '@ridp/threejs';
const { scene, camera, renderer, control } = useThreeJs('#container', { ... });
// ✅ 推荐用法 (ThreeIns 类)
import { ThreeIns, ViewType } from '@ridp/threejs';
const threeJsIns = new ThreeIns('#container', {
css3d: true,
renderType: 'loop',
initListener: true,
initialFov: 75,
control: { init: true, options: { ... } }
});
// 访问核心对象
console.log(threeJsIns.scene); // THREE.Scene
console.log(threeJsIns.camera); // THREE.PerspectiveCamera
console.log(threeJsIns.renderer); // THREE.WebGLRenderer
console.log(threeJsIns.control); // THREE.OrbitControls
// 使用强大的 setView 方法
threeJsIns.setView(model, ViewType.ISO, { scale: 1.0, animate: true });迁移对照表:
| 功能 | useThreeJs (已弃用) | ThreeIns 类 (推荐) |
| ----------- | ------------------------------------ | -------------------------------- |
| 初始化场景 | useThreeJs('#el', opts) | new ThreeIns('#el', opts) |
| 访问 scene | const { scene } = useThreeJs(...) | threeJsIns.scene |
| 访问 camera | const { camera } = useThreeJs(...) | threeJsIns.camera |
| 添加动画 | addAnimate(fn) | threeJsIns.addAnimate(fn) |
| 视角切换 | ❌ 不支持 | ✅ setView(model, ViewType.ISO) |
| 资源清理 | dispose() | ✅ threeJsIns.dispose() |
| 适用范围 | 仅 Vue 3 | 所有 JS/TS 项目 |
useGLTFLoader()
GLTF 模型加载器,支持 IndexedDB 缓存、内存缓存、重试机制和调试模式。
const loader = useGLTFLoader({
debug: false // 是否开启调试日志(默认: false)
});
const {
// 核心加载方法
load, // 基础加载方法
asyncLoad, // Promise 异步加载
asyncFetch, // 带缓存和重试的异步加载(推荐)
asyncCacheLoad, // [已弃用] 旧版缓存加载
// 批量加载
loadBatch, // 批量加载模型
loadBatchSequential, // 顺序批量加载
// IndexedDB 缓存管理
clearCache, // 清空 IndexedDB 缓存
invalidateCache, // 使缓存失效
getCacheStats, // 获取缓存统计
logCacheReport, // 输出缓存性能报告
getCache, // 获取 IDBCache 实例
resetCacheStats, // 重置缓存统计
// 内存缓存管理 (v1.4.2+)
clearMemoryCache, // 清空内存缓存
getMemoryCacheInfo, // 获取内存缓存统计
deleteMemoryCache, // 删除指定模型的内存缓存
// 性能监控
cacheMonitor // 缓存监控器实例
} = loader;新增功能 (v1.4.2):
- 内存缓存机制: 自动缓存已解析的 3D 模型对象,避免重复解析
- 调试模式控制: 通过初始化参数统一控制所有日志输出
初始化配置
// 生产环境 - 默认静默
const loader = useGLTFLoader();
// 开发环境 - 开启调试日志
const loader = useGLTFLoader({
debug: true
});
// 所有操作都会输出详细日志
const model = await loader.asyncFetch('/models/car.glb', '1.0.0');
// 输出: [ asyncFetch ] ====> 缓存未命中
// [ fetchArrayBuffer ] 加载模型耗时: 150ms
// [ 解析模型耗时 ]: 80ms
// [ 内存缓存 ] 存储模型 /models/car.glb (version: 1.0.0)常用方法:
asyncFetch()
推荐的模型加载方法,集成 IndexedDB 缓存、内存缓存、重试机制、性能监控和模型优化。
const model = await asyncFetch(
url: string, // 模型 URL
version?: string, // 模型版本(用于缓存失效)
onProgress?: (percent: number) => void, // 进度回调
options?: {
// 重试配置
maxRetries?: number, // 最大重试次数(默认: 3)
// 模型优化选项
optimizeMaterials?: boolean, // 是否自动优化材质,合并相同材质(默认: false)
simplifyGeometry?: boolean, // 是否简化模型几何体(默认: false)
simplifyRatio?: number, // 简化比例 0-1, 0.5=保留50%面(默认: 0.5)
simplifyOptions?: {
minFaceCount?: number, // 最小面数阈值,低于此值不简化(默认: 100)
preserveUVs?: boolean // 是否保留UV坐标(默认: true)
},
// 缓存控制
useMemoryCache?: boolean // 是否使用内存缓存(默认: true, v1.4.2+)
}
): Promise<THREE.Object3D | null>缓存层级 (v1.4.2+):
- 内存缓存 (最快) - 已解析的 3D 对象,仅需克隆(~2ms)
- IndexedDB 缓存 (快) - ArrayBuffer 数据,需要解析(~200ms)
- 网络加载 (慢) - 完整下载 + 解析(~2s)
示例:
// 基础用法
const model = await asyncFetch('/models/car.glb', '1.0.0');
if (model) {
scene.add(model);
}
// 带进度回调
const model = await asyncFetch('/models/car.glb', '1.0.0', (percent) => {
console.log(`加载进度: ${percent}%`);
});
// 启用材质优化
const model = await asyncFetch('/models/car.glb', '1.0.0', null, {
optimizeMaterials: true // 自动合并相同材质
});
// 启用几何体简化(适合大型模型)
const model = await asyncFetch('/models/large-scene.glb', '1.0.0', null, {
simplifyGeometry: true, // 启用简化
simplifyRatio: 0.5, // 保留 50% 的面
simplifyOptions: {
minFaceCount: 500, // 面数少于 500 的网格不简化
preserveUVs: true // 保留 UV 坐标
}
});
// 禁用内存缓存
const model = await asyncFetch('/models/car.glb', '1.0.0', null, {
useMemoryCache: false // 跳过内存缓存
});
// 综合优化
const model = await asyncFetch('/models/complex.glb', '1.0.0', (p) => {
console.log(`加载: ${p.toFixed(1)}%`);
}, {
maxRetries: 5, // 增加重试次数
optimizeMaterials: true, // 优化材质
simplifyGeometry: true, // 简化几何体
simplifyRatio: 0.7, // 保留 70% 的面
useMemoryCache: true // 使用内存缓存(默认)
});性能对比:
// 首次加载
await asyncFetch('/models/car.glb', '1.0.0');
// 耗时: ~2s (网络 + 解析 + 优化)
// 二次加载 (内存缓存命中)
await asyncFetch('/models/car.glb', '1.0.0');
// 耗时: ~2ms (仅克隆对象)
// 二次加载 (仅 IndexedDB 命中,内存缓存被清空)
loader.clearMemoryCache();
await asyncFetch('/models/car.glb', '1.0.0');
// 耗时: ~200ms (从 IndexedDB 读取 + 解析)logCacheReport()
输出 IndexedDB 缓存性能报告到控制台。
logCacheReport(): void输出示例:
💾 [ 缓存性能报告 ] ====
- 总加载次数: 5
- 缓存命中次数: 3
- 缓存未命中次数: 2
- 缓存命中率: 60.00%
- 总耗时: 2.5s
- 平均耗时: 500ms
- 缓存节省时间: ~1.5s内存缓存管理 API (v1.4.2+)
getMemoryCacheInfo()
获取内存缓存统计信息。
const info = loader.getMemoryCacheInfo();
// 返回值:
{
totalPaths: 2, // 缓存的不同模型路径数量
totalModels: 3, // 缓存的总模型数量
details: [
{
path: '/models/car.glb',
versions: ['1.0.0', '1.1.0'],
count: 2
},
{
path: '/models/truck.glb',
versions: ['1.0.0'],
count: 1
}
]
}clearMemoryCache()
清空所有内存缓存,释放已缓存的 3D 模型对象内存。
const count = loader.clearMemoryCache();
console.log(`释放了 ${count} 个模型`);
// 使用场景: 内存不足时主动释放
if (info.totalModels > 100) {
loader.clearMemoryCache();
}deleteMemoryCache(path, version?)
删除指定模型的内存缓存。
// 删除特定版本
loader.deleteMemoryCache('/models/car.glb', '1.0.0');
// 删除所有版本
loader.deleteMemoryCache('/models/car.glb');使用示例:
import { useGLTFLoader } from '@ridp/threejs/hooks';
const loader = useGLTFLoader({ debug: true });
// 首次加载 - 从网络加载并缓存
const model1 = await loader.asyncFetch('/models/car.glb', '1.0.0');
// [ asyncFetch ] ====> 缓存未命中
// [ 内存缓存 ] 存储模型 /models/car.glb (version: 1.0.0)
// 二次加载 - 从内存缓存获取(极快)
const model2 = await loader.asyncFetch('/models/car.glb', '1.0.0');
// [ 内存缓存命中 ] /models/car.glb (version: 1.0.0)
// [ 内存缓存克隆耗时 ]: 2ms
// 查看内存缓存信息
const info = loader.getMemoryCacheInfo();
console.log(`缓存了 ${info.totalModels} 个模型`);
// 清空内存缓存
loader.clearMemoryCache();
// 再次加载 - 从 IndexedDB 缓存获取
const model3 = await loader.asyncFetch('/models/car.glb', '1.0.0');
// [ asyncFetch ] ====> IndexedDB 缓存命中useRaycaster()
射线拾取工具,用于鼠标交互和物体选择。
const { getIntersects, getPointer } = useRaycaster('app-id');
// 获取鼠标拾取的对象
const { intersects, pointer, x, y } = getIntersects(
event, // 鼠标事件
renderer.domElement, // 渲染器 DOM
camera, // 相机
objects // 可拾取对象数组
);
// intersects[0].object - 拾取到的 3D 对象
// pointer - 标准化设备坐标(-1 到 1)
// x, y - 鼠标在容器中的相对坐标示例:
const canvas = document.getElementById('canvas');
canvas.addEventListener('click', (event) => {
const { intersects } = getIntersects(event, canvas, camera, scene.children);
if (intersects.length > 0) {
const clickedObject = intersects[0].object;
console.log('点击了:', clickedObject.name);
// 高亮显示
clickedObject.material.color.set(0xff0000);
}
});useObb()
有向包围盒(OBB)碰撞检测工具。
import { useObb, obbObjects, intersectColor } from '@ridp/threejs';
const {
resetObbs, // 重置所有 OBB
initObb, // 初始化模型的 OBB
initSingleObbItem, // 初始化单个对象的 OBB
addObbFromArray, // 批量添加 OBB
getObbObjectByParentUid, // 根据 parentUid 获取 OBB 对象
checkObbIntersection // 检测碰撞
} = useObb();使用示例:
// 1. 标记需要碰撞检测的模型
model.userData.needCheck = true;
// 2. 初始化 OBB
initObb('model-uid', model);
// 3. 碰撞检测
const hasCollision = checkObbIntersection(otherObject, obbObjects);
// 4. 高亮碰撞对象
if (hasCollision) {
object.material.color.set(intersectColor);
}useBatchGLTFLoader()
批量模型加载器,支持并发控制和进度追踪。
const { loadBatch, loadBatchSequential } = useBatchGLTFLoader();
// 并发加载(推荐)
const results = await loadBatch([
{ url: '/models/part1.glb', version: '1.0.0' },
{ url: '/models/part2.glb', version: '1.0.0' },
{ url: '/models/part3.glb', version: '1.0.0' }
], {
concurrency: 3, // 最大并发数
onProgress: (progress) => {
console.log(`进度: ${progress.percent.toFixed(2)}%`);
}
});
// 顺序加载
const results = await loadBatchSequential([
{ url: '/models/part1.glb', version: '1.0.0' },
{ url: '/models/part2.glb', version: '1.0.0' }
]);工具函数
场景辅助工具
import {
createCameraHelper, // 创建相机辅助器
createGridHelper, // 创建网格辅助器
createBox3Helper, // 创建包围盒辅助器
createOrbitControl, // 创建轨道控制器
createMapControls, // 创建地图控制器
createRaycaster, // 创建射线投射器
createAxesHelper, // 创建坐标轴辅助器
createArrowHelper, // 创建箭头辅助器
createStats // 创建性能监控器
} from '@ridp/threejs';
// 示例
const gridHelper = createGridHelper(100, 10, 0x888888, 0xcccccc);
scene.add(gridHelper);
const stats = createStats();
document.body.appendChild(stats.dom);功能说明:
createCameraHelper(camera)- 创建相机辅助线,显示相机视锥体createGridHelper(size, divisions, color1, color2)- 创建网格辅助线size: 网格大小,默认 150divisions: 分段数color1/color2: 中心线和其他线的颜色
createBox3Helper(model)- 创建包围盒辅助线createOrbitControl(camera, dom)- 创建轨道控制器createMapControls(camera, dom)- 创建地图控制器createRaycaster()- 创建射线投射器,用于鼠标拾取createAxesHelper(size)- 创建坐标轴辅助线,默认大小 10createArrowHelper(dir, origin, length, color)- 创建箭头辅助线createStats()- 创建性能监控器(基于 stats.js)
initEnvImage()
初始化场景环境贴图。
import { initEnvImage } from '@ridp/threejs';
// EXR 格式
await initEnvImage(scene, '/hdr/studio.exr');
// HDR 格式
await initEnvImage(scene, '/hdr/outdoor.hdr');
// PNG/JPG 格式(作为 CubeTexture)
await initEnvImage(scene, '/textures/env.jpg');getCommonParent()
查找多个网格对象的公共父节点。
import { getCommonParent } from '@ridp/threejs';
// 查找多个选中对象的公共父节点
const meshes = [mesh1, mesh2, mesh3];
const commonParent = getCommonParent(meshes, scene);
console.log('公共父节点:', commonParent);功能说明:
- 遍历每个网格的所有父节点,统计出现频率最高的父节点
- 返回的公共父节点可作为分组、操作的目标对象
- 常用于批量操作、场景组织等场景
使用场景:
- 批量选择对象时查找共同的容器
- 场景层级分析
- 分组操作前的父节点确定
modelOptimizer
模型优化工具,用于减少多边形数量和优化材质。
import { modelOptimizer } from '@ridp/threejs';
// 获取模型统计信息
const stats = modelOptimizer.getModelStats(model);
console.log('网格数量:', stats.meshCount);
console.log('三角形数:', stats.triangleCount);
console.log('顶点数:', stats.vertexCount);
console.log('材质数:', stats.materialCount);
// 简化模型几何体
const optimized = modelOptimizer.simplifyModel(model, {
ratio: 0.5, // 保留 50% 的面
mergeMaterials: true, // 合并相同材质
removeUnused: true // 移除未使用的顶点
});
// 优化材质
modelOptimizer.optimizeMaterials(model);disposeThreeObject()
深度释放 3D 对象及其子对象的资源。
import { disposeThreeObject } from '@ridp/threejs';
// 释放模型资源
disposeThreeObject(model);
// 释放整个场景
disposeThreeObject(scene);清理内容:
- 几何体 (geometry.dispose())
- 材质 (material.dispose())
- 材质中的所有纹理 (texture.dispose())
- 子对象递归清理
注意: 此函数会递归遍历对象的所有子对象,确保所有资源都被正确释放,避免内存泄漏。
sceneRebuilder
渐进式场景重建工具,用于大型模型的分批渲染。
import { ProgressiveSceneBuilder } from '@ridp/threejs';
const builder = new ProgressiveSceneBuilder(scene, camera, {
batchSize: 1000, // 每批添加的网格数
delay: 16, // 批次间延迟(毫秒)
onProgress: (progress) => {
console.log(`构建进度: ${progress.percent}%`);
}
});
await builder.build(model);模型序列化工具
用于 Three.js 对象的序列化和反序列化,支持将 3D 对象转换为可存储的数据格式。
import {
object3DToData,
object3DToDataSync,
dataToObject3D,
dataToObject3DSync
} from '@ridp/threejs';
// 异步序列化(推荐,避免阻塞主线程)
const data = await object3DToData(model, 50);
// 同步序列化
const data = object3DToDataSync(model);
// 异步反序列化(推荐,避免阻塞主线程)
const restoredModel = await dataToObject3D(data, 50);
// 同步反序列化
const restoredModel = dataToObject3DSync(data);功能说明:
object3DToData(object, chunkSize)- 异步序列化对象,使用分块处理避免阻塞chunkSize: 每帧处理的对象数量,默认 50
object3DToDataSync(object)- 同步序列化对象dataToObject3D(data, chunkSize)- 异步反序列化数据为 3D 对象dataToObject3DSync(data)- 同步反序列化数据为 3D 对象
序列化内容:
- 对象类型、名称、位置、旋转、缩放
- 几何体数据(顶点、索引、UV 等)
- 材质数据(颜色、纹理、属性等)
- userData 自定义数据
- 子对象层级关系
使用场景:
- 场景状态保存和恢复
- 模型数据的本地存储
- 跨页面/跨会话的模型传输
对象查询工具
基于 userData 的对象查找工具。
import { getObjectByUserData, getRootObj } from '@ridp/threejs';
// 向下遍历查找首个匹配 userData 的对象
const target = getObjectByUserData(mesh, 'type', 'wheel');
// 向上查找根节点(根据 userData 判定)
const root = getRootObj(mesh, 'isRoot', true);功能说明:
getObjectByUserData(obj, key, value)- 从指定对象开始向下遍历,查找首个 userData[key]=value 的对象(包括自身)getRootObj(obj, rootKey, value)- 从指定对象开始向上查找父级,直到找到 userData[rootKey]=value 的根对象
CSS3D 辅助工具
import { createInfoPlane, createTagPlane } from '@ridp/threejs';
// 从 DOM ID 创建 CSS3D 信息面板
const infoPlane = createInfoPlane('my-info-panel', [0.3, 0.3, 0.3]);
infoPlane.position.set(0, 10, 0);
scene.add(infoPlane);
// 创建 CSS3D 标签面板
const tagPlane = createTagPlane('设备名称', 0.01);
tagPlane.position.set(5, 5, 5);
scene.add(tagPlane);功能说明:
createInfoPlane(id, scale)- 从页面中已存在的 DOM 元素创建 CSS3D 精灵id: DOM 元素的 IDscale: 缩放比例数组[x, y, z],默认[0.3, 0.3, 0.3]- ⚠️ 注意: HTML 元素不能设置为绝对定位
createTagPlane(text, scale)- 创建带文本的 CSS3D 标签text: 标签文本内容scale: 统一缩放比例(三个维度相同)
CacheMonitor
缓存性能监控工具。
import { cacheMonitor } from '@ridp/threejs';
// 获取缓存统计
const stats = cacheMonitor.getStats();
console.log('命中率:', stats.hitRate);
console.log('平均耗时:', stats.avgLoadTime);
// 监听缓存事件
cacheMonitor.on('hit', (key) => {
console.log('缓存命中:', key);
});
cacheMonitor.on('miss', (key) => {
console.log('缓存未命中:', key);
});RetryHelper
重试机制工具,用于处理不稳定的网络请求。
import { RetryHelper, ModelLoadError } from '@ridp/threejs';
const retryHelper = new RetryHelper({
maxRetries: 3, // 最大重试次数
baseDelay: 1000, // 基础延迟(毫秒)
maxDelay: 10000, // 最大延迟(毫秒)
backoffMultiplier: 2 // 退避乘数
});
try {
const result = await retryHelper.execute(async () => {
const response = await fetch('/models/car.glb');
if (!response.ok) {
throw new ModelLoadError('加载失败', response.status);
}
return response.json();
});
} catch (error) {
console.error('重试失败:', error);
}PredictiveLoader
预测性加载工具,根据用户行为预加载可能需要的资源。
import { PredictiveLoader } from '@ridp/threejs';
const loader = new PredictiveLoader({
maxPrefetch: 3, // 最大预加载数
priority: 'recent' // 优先级策略
});
// 预加载模型
loader.prefetch('/models/next-part.glb', '1.0.0');
// 根据用户行为预测
loader.predict(userBehavior, availableModels);实例类
IDBCache
IndexedDB 缓存管理类。
import { IDBCache } from '@ridp/threejs';
const cache = new IDBCache('my-cache-db', 1);
// 保存模型到缓存
await cache.saveModel('model-key', modelData, {
version: '1.0.0',
timestamp: Date.now()
});
// 从缓存加载模型
const data = await cache.loadCachedModel('model-key', '1.0.0');
// 清除特定模型缓存
await cache.deleteModel('model-key');
// 清空所有缓存
await cache.clear();
// 获取缓存统计
const stats = await cache.getStats();完整示例
Vue 3 组件示例
<template>
<div class="canvas-container" ref="containerRef"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { ThreeIns, useGLTFLoader, initEnvImage, ViewType } from '@ridp/threejs';
import * as THREE from 'three';
const containerRef = ref(null);
let threeJsIns = null;
const { asyncFetch, logCacheReport } = useGLTFLoader();
onMounted(async () => {
// 1. 初始化场景
threeJsIns = new ThreeIns(containerRef.value, {
css3d: false,
renderType: 'loop',
initListener: true,
initialFov: 75,
control: {
init: true,
options: {
maxDistance: Infinity,
minDistance: 0,
enablePan: true,
enableDamping: true,
dampingFactor: 0.25,
},
},
});
// 2. 加载环境贴图
await initEnvImage(threeJsIns.scene, '/hdr/studio.exr');
// 3. 加载模型
const [err, model] = await asyncFetch('/models/car.glb', '1.0.0', {
onProgress: (progress) => {
const percent = (progress.loaded / progress.total * 100).toFixed(2);
console.log(`加载进度: ${percent}%`);
}
});
if (model) {
threeJsIns.scene.add(model);
// 4. 设置视角
threeJsIns.setView(model, ViewType.ISO, {
scale: 1.0,
showBox: false,
animate: true,
duration: 1000
});
}
// 5. 查看缓存性能
logCacheReport();
});
onUnmounted(() => {
// 清理资源
if (threeJsIns) {
threeJsIns.dispose();
}
});
</script>
<style scoped>
.canvas-container {
width: 100%;
height: 100vh;
}
</style>多模型加载示例
import { ThreeIns, useBatchGLTFLoader, ViewType } from '@ridp/threejs';
const threeJsIns = new ThreeIns('#container', { /* ... */ });
const { loadBatch } = useBatchGLTFLoader();
// 批量加载多个模型
const models = [
{ url: '/models/floor.glb', version: '1.0.0' },
{ url: '/models/wall.glb', version: '1.0.0' },
{ url: '/models/roof.glb', version: '1.0.0' },
{ url: '/models/furniture.glb', version: '1.0.0' }
];
const results = await loadBatch(models, {
concurrency: 2,
onProgress: (progress) => {
console.log(`加载进度: ${progress.percent}%`);
console.log(`已完成: ${progress.loaded}/${progress.total}`);
}
});
// 添加所有模型到场景
results.forEach(result => {
if (result.model) {
threeJsIns.scene.add(result.model);
}
});
// 自动适配视角
const group = new THREE.Group();
results.forEach(r => r.model && group.add(r.model));
threeJsIns.setView(group, ViewType.TOP, { scale: 0.8 });交互式场景示例
import { ThreeIns, useRaycaster, useObb } from '@ridp/threejs';
const threeJsIns = new ThreeIns('#container', { /* ... */ });
const { getIntersects } = useRaycaster('app');
const { initObb, checkObbIntersection } = useObb();
// 加载模型后初始化碰撞检测
model.userData.needCheck = true;
initObb('model-1', model);
// 点击事件
threeJsIns.renderer.domElement.addEventListener('click', (event) => {
const { intersects } = getIntersects(
event,
threeJsIns.renderer.domElement,
threeJsIns.camera,
[model]
);
if (intersects.length > 0) {
const object = intersects[0].object;
console.log('点击了:', object.name);
// 检测碰撞
const hasCollision = checkObbIntersection(object, obbObjects);
if (hasCollision) {
object.material.color.set(0xff0000);
}
}
});性能优化建议
1. 使用模型缓存和自动优化
对于重复访问的模型,启用 IndexedDB 缓存可以显著提升加载速度。同时可以启用材质优化和几何体简化来提升渲染性能:
// 首次加载: ~2s (含优化)
await asyncFetch('/models/large-scene.glb', '1.0.0', {
optimizeMaterials: true, // 合并相同材质,减少 draw calls
simplifyGeometry: true, // 简化几何体,减少三角形数
simplifyRatio: 0.5 // 保留 50% 的面
});
// 二次加载: ~200ms (从缓存读取,仍需优化)
await asyncFetch('/models/large-scene.glb', '1.0.0', {
optimizeMaterials: true,
simplifyGeometry: true,
simplifyRatio: 0.5
});优化选项说明:
optimizeMaterials(材质优化)- 自动合并相同属性的材质
- 减少材质切换次数
- 降低 GPU draw calls
- 适用场景: 模型中有很多相似材质的对象
simplifyGeometry(几何体简化)- 减少模型的三角形数量
- 显著提升渲染性能
- 适合大型场景或低性能设备
- 可配置简化比例和最小面数阈值
simplifyRatio(简化比例)0.8- 保留 80% 的面(轻度简化)0.5- 保留 50% 的面(中度简化,推荐)0.3- 保留 30% 的面(重度简化,适合远景模型)
性能对比:
未优化模型: 50万三角形, 120个材质
优化后: 25万三角形 (50%减少), 45个材质 (62%减少)
FPS提升: 30 FPS -> 55 FPS (83%提升)2. 批量加载优化
使用批量加载时,合理控制并发数:
// 推荐: 根据设备性能动态调整
const concurrency = navigator.hardwareConcurrency || 4;
await loadBatch(models, { concurrency });3. 手动模型优化
如果需要在加载后手动优化模型:
import { modelOptimizer } from '@ridp/threejs';
// 材质优化
modelOptimizer.optimizeMaterials(model);
// 几何体简化
modelOptimizer.simplifyModel(model, 0.5, {
minFaceCount: 500,
preserveUVs: true
});
// 查看优化效果
const stats = modelOptimizer.getModelStats(model);
console.log('优化后统计:', stats);4. 渐进式渲染
对于超大场景,使用渐进式渲染避免长时间卡顿:
import { ProgressiveSceneBuilder } from '@ridp/threejs';
const builder = new ProgressiveSceneBuilder(scene, camera, {
batchSize: 1000,
delay: 16
});
await builder.build(model);5. 合理使用阴影
阴影是性能杀手,仅在必要时启用:
renderer.shadowMap.enabled = true; // 启用阴影
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 软阴影
// 仅重要对象投射阴影
importantObject.castShadow = true;
otherObjects.castShadow = false;常见问题
Q: 如何处理模型加载失败?
A: 使用 asyncFetch 的重试机制:
const [err, model] = await asyncFetch(url, version, {
retryCount: 5,
timeout: 60000
});
if (err || !model) {
console.error('模型加载失败:', err);
// 显示错误提示或加载备用模型
}Q: 如何清理模型缓存?
A: 使用 clearCache 方法:
const { clearCache } = useGLTFLoader();
await clearCache(); // 清空所有缓存
// 或使用 IDBCache 实例
import { IDBCache } from '@ridp/threejs';
const cache = new IDBCache();
await cache.clear();Q: 模型显示太小或太大?
A: 使用 setView 的 scale 参数:
// 模型太小 - 放大
threeJsIns.setView(model, ViewType.ISO, { scale: 2.0 });
// 模型太大 - 缩小
threeJsIns.setView(model, ViewType.ISO, { scale: 0.5 });注意: 旧方法
frameArea()已弃用,请使用setView()代替。
Q: 如何监听窗口大小变化?
A: 如果 initListener: true,会自动监听。否则手动监听:
window.addEventListener('resize', () => {
threeJsIns.updateCameraFOV();
threeJsIns.camera.updateProjectionMatrix();
threeJsIns.renderer.setSize(
threeJsIns.domElement.clientWidth,
threeJsIns.domElement.clientHeight
);
});Q: 如何导出场景截图?
A: 使用 renderer.domElement.toDataURL():
function captureScreenshot() {
threeJsIns.render(); // 确保最新帧已渲染
const dataURL = threeJsIns.renderer.domElement.toDataURL('image/png');
const link = document.createElement('a');
link.download = 'screenshot.png';
link.href = dataURL;
link.click();
}更新日志
v1.4.2 (2026-01-07)
🎉 重大更新:
✨ 新增内存缓存机制: 自动缓存已解析的 3D 模型对象,避免重复解析
- 首次加载后,二次加载仅需 ~2ms(对象克隆)
- 相比完整加载流程,速度提升可达 100倍以上
- 新增
clearMemoryCache()- 清空内存缓存 - 新增
getMemoryCacheInfo()- 获取内存缓存统计 - 新增
deleteMemoryCache(path, version?)- 删除指定缓存 - 支持
useMemoryCache选项控制是否使用内存缓存
✨ 新增调试模式控制:
useGLTFLoader现在支持初始化配置{ debug: boolean }- 统一控制所有内部函数的日志输出
- 生产环境默认静默,开发环境可开启详细日志
- 错误日志始终输出,不受 debug 配置影响
🐛 修复 Web Worker 在 npm 包中无法加载的问题:
- 使用
?worker语法正确导入 Worker - 配置
base: './'使用相对路径解析 - Worker 文件正确打包到
dist/assets/目录 - 修复了之前 404 错误和 MIME 类型错误的问题
- 使用
API 变更:
// 旧 API (v1.4.1 及之前)
const loader = useGLTFLoader();
const model = await loader.asyncFetch(url, version, null, { debug: true });
// 新 API (v1.4.2+)
const loader = useGLTFLoader({ debug: true });
const model = await loader.asyncFetch(url, version);
// 新增内存缓存管理
const info = loader.getMemoryCacheInfo();
loader.clearMemoryCache();
loader.deleteMemoryCache(url, version);性能提升:
加载场景 (v1.4.1):
- 首次: ~2s (网络 + 解析)
- 二次: ~200ms (IndexedDB 读取 + 解析)
加载场景 (v1.4.2+):
- 首次: ~2s (网络 + 解析)
- 二次: ~2ms (内存缓存克隆)
- 提升: 100倍v1.4.1 (2025-XX-XX)
- 🎯 优化
setView方法的 scale 参数计算逻辑 - 🔧 修复水平 FOV 超过 180° 导致的负数距离问题
- ✨ 移除 FRONT/BACK/BOTTOM 视角,保留 TOP/RIGHT/LEFT/ISO
- ⚡ 优化 OrbitControls 距离限制的自动处理
- 🐛 修复相机 near/far 平面裁剪问题
- ⚠️ 弃用:
useThreeJs()Hook - 推荐使用 ThreeIns 类 (功能更完整,不限框架) - ⚠️ 弃用:
frameArea()方法 - 推荐使用 setView() 方法 (支持多视角) - ✨ 新增:
asyncFetch支持自动材质优化 (optimizeMaterials) - ✨ 新增:
asyncFetch支持几何体简化 (simplifyGeometry) - ✨ 新增:
modelOptimizer.simplifyModel()方法 - 整体模型简化 - 📦 增强: 完整的模型优化工作流,支持加载时自动优化
v1.3.2 (2025-01-05)
- ✨ 新增 Web Worker 支持,优化 GLTF 解析性能
- 📦 增强模型缓存机制
- 🎨 新增
modelOptimizer工具 - ⚡ 性能优化:减少重复计算
v1.3.0 (2024-12-XX)
- ✨ 新增
useBatchGLTFLoader批量加载 Hook - 🎯 新增
setView方法,支持多视角切换 - 📊 新增
CacheMonitor缓存监控工具 - 🔧 新增
RetryHelper重试机制
完整更新日志请查看 CHANGELOG.md
许可证
MIT License
支持
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 📖 文档: 在线文档
