在数字化浪潮中,3D可视化技术正逐渐成为智慧社区建设的标配。本文将带你从零开始,完整实现一个基于Vue3和Three.js的智慧社区3D地图项目。不同于简单的代码展示,我们将重点探讨工程化思维,解决实际开发中遇到的各类问题。
现代前端3D可视化开发需要一套完整的工具链支持:
bash复制# 项目初始化
npm create vite@latest smart-community-3d --template vue-ts
cd smart-community-3d
npm install three @tweenjs/tween.js
在将模型导入网页前,需要特别注意以下优化点:
| 优化项 | 建议值 | 说明 |
|---|---|---|
| 面数 | ≤50万 | 确保浏览器流畅渲染 |
| 材质 | 共享材质 | 减少draw call |
| 纹理 | 2048x2048 | 平衡质量与性能 |
| 导出格式 | GLTF/GLB | 二进制格式更高效 |
提示:在Blender中执行"Ctrl+A"应用全部变换,避免导入后出现缩放问题
将Three.js初始化逻辑封装为可复用的函数:
javascript复制// threeBaseConfig.ts
export function initBaseConfig(container: HTMLElement) {
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x333333)
// 相机配置
const camera = new THREE.PerspectiveCamera(
75,
container.clientWidth / container.clientHeight,
0.1,
1000
)
camera.position.set(0, 150, 300)
// 渲染器配置
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(container.clientWidth, container.clientHeight)
renderer.shadowMap.enabled = true
return { scene, camera, renderer }
}
模型加载是性能关键点,需要特别注意:
javascript复制// modelImport.js
export async function loadingModel() {
const loader = new GLTFLoader()
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
loader.setDRACOLoader(dracoLoader)
const { scene } = await loader.loadAsync('/models/community.glb')
// 统一缩放模型
scene.traverse(obj => {
if (obj.isMesh) {
obj.castShadow = true
obj.receiveShadow = true
// 保存原始颜色便于交互时恢复
obj.color = obj.material.color.clone()
}
})
return scene
}
Three.js通过Raycaster实现3D拾取:
javascript复制function setupRaycaster() {
const raycaster = new THREE.Raycaster()
const pointer = new THREE.Vector2()
container.addEventListener('pointermove', (event) => {
// 将鼠标坐标归一化为-1到1
pointer.x = (event.clientX / window.innerWidth) * 2 - 1
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1
// 更新射线
raycaster.setFromCamera(pointer, camera)
// 检测相交
const intersects = raycaster.intersectObjects(model.children, true)
if (intersects.length > 0) {
// 高亮选中物体
intersects[0].object.material.emissive.setHex(0x00BFFF)
}
})
}
将DOM元素作为3D场景中的标注:
javascript复制function createBuildingLabel(buildingName: string) {
const labelDiv = document.createElement('div')
labelDiv.className = 'building-label'
labelDiv.textContent = buildingName
const label = new CSS2DObject(labelDiv)
label.position.set(0, 10, 0) // 相对于建筑顶部偏移
building.add(label)
return {
element: labelDiv,
object: label
}
}
使用Tween.js实现专业级相机动画:
javascript复制function flyTo(targetPosition: THREE.Vector3) {
const duration = 1500
const startPosition = camera.position.clone()
const startTarget = controls.target.clone()
new TWEEN.Tween({ t: 0 })
.to({ t: 1 }, duration)
.easing(TWEEN.Easing.Quadratic.InOut)
.onUpdate(({ t }) => {
camera.position.lerpVectors(startPosition, targetPosition, t)
controls.target.lerpVectors(startTarget, targetPosition, t)
controls.update()
})
.start()
}
结合模型元数据实现智能搜索:
javascript复制const buildingIndex = new Fuse(buildings, {
keys: ['name', 'type'],
threshold: 0.3
})
function handleSearch(query) {
const results = buildingIndex.search(query)
searchResults.value = results.map(r => r.item)
if (results.length) {
const target = results[0].item
flyTo(target.position)
highlightBuilding(target.mesh)
}
}
关键性能指标监控:
| 指标 | 健康值 | 优化手段 |
|---|---|---|
| FPS | ≥30 | 减少draw call |
| 内存 | <500MB | 及时dispose |
| 加载时间 | <5s | 模型压缩 |
javascript复制// 性能监控
const stats = new Stats()
stats.showPanel(0) // 0: fps
document.body.appendChild(stats.dom)
function animate() {
stats.begin()
renderer.render(scene, camera)
stats.end()
requestAnimationFrame(animate)
}
通过智能渲染模式节省资源:
javascript复制let renderRequested = false
function requestRender() {
if (!renderRequested) {
renderRequested = true
requestAnimationFrame(() => {
renderer.render(scene, camera)
renderRequested = false
})
}
}
// 只在需要时触发渲染
controls.addEventListener('change', requestRender)
window.addEventListener('resize', requestRender)
在项目开发过程中,最大的挑战往往不是技术实现,而是性能与体验的平衡。例如,当社区模型包含超过100栋建筑时,直接全量渲染会导致移动端崩溃。通过实现动态加载和LOD策略,我们最终将内存占用降低了70%。