ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

WebGL与WebGPU实战:Three.js性能优化与交互开发全解析

WebGL与WebGPU实战:Three.js性能优化与交互开发全解析 大家好我是长期关注前端图形技术的CSDN博主。在Web3D开发中WebGL和WebGPU作为浏览器端两大图形API经常让开发者面临技术选型和性能优化的挑战。特别是随着Three.js等框架的普及如何在实际项目中合理运用这些技术成为关键问题。本文将围绕WebGL/WebGPU的实战案例展开通过六个典型场景的完整实现帮助读者掌握从基础渲染到高级优化的全流程解决方案。无论是刚接触Web3D的新手还是有一定经验的开发者都能从本文找到可直接复用的代码示例和工程实践。我们将重点解决Three.js项目中的常见痛点模型加载优化、内存管理、交互功能实现等并提供经过验证的避坑方案。1. WebGL与WebGPU技术背景解析1.1 WebGL技术特点与应用场景WebGL是基于OpenGL ES的Web图形标准允许在浏览器中实现硬件加速的3D渲染。它通过JavaScript API直接操作GPU为网页游戏、数据可视化、在线展览等场景提供强大的图形能力。WebGL 1.0支持基本的3D渲染功能WebGL 2.0则引入了更多高级特性如变换反馈、实例化渲染等。在实际项目中WebGL的优势在于兼容性广泛几乎所有现代浏览器都支持WebGL 1.0。但其编程模型相对底层开发者需要手动管理着色器、缓冲区等资源这也是Three.js等封装框架流行的主要原因。1.2 WebGPU的技术革新与优势WebGPU是新一代Web图形API旨在提供更接近现代GPU架构的编程模型。与WebGL相比WebGPU具有更好的多线程支持、更高效的资源管理和更 predictable的性能表现。它采用WGSL着色语言支持计算着色器为复杂的图形计算和通用GPU计算打开新局面。目前WebGPU已在Chrome、Edge等浏览器中逐步支持虽然兼容性不如WebGL但在性能要求高的项目中有明显优势。特别是对于需要大量计算的任务如物理模拟、实时光线追踪等WebGPU能够提供数倍的性能提升。1.3 Three.js框架的桥梁作用Three.js作为最流行的Web3D库在WebGL和WebGPU之间架起了桥梁。最新版本的Three.js已经支持WebGPU后端开发者可以用相同的Three.js API同时 targeting两种底层API。这种设计让项目迁移更加平滑也降低了学习成本。对于大多数业务场景建议优先使用Three.js进行开发仅在性能瓶颈明显时考虑直接使用底层API。Three.js提供了丰富的材质系统、几何体工具和加载器能够满足90%的Web3D需求。2. 开发环境搭建与版本控制2.1 基础环境配置本文所有示例基于以下环境开发建议读者配置相似环境以保证代码正常运行操作系统Windows 10/11 或 macOS 12浏览器Chrome 115支持WebGPU或 Firefox 100Node.js18.0用于构建工具和本地服务器开发工具VS Code 或 WebStormThree.js版本选择至关重要本文使用r158版本这是目前最稳定且功能完整的版本。避免使用过老的版本以免缺失重要特性或存在已知bug。2.2 项目初始化与依赖管理创建新的Three.js项目时推荐使用官方提供的构建工具。首先初始化npm项目mkdir threejs-project cd threejs-project npm init -y npm install three npm install --save-dev types/three vite创建基础的HTML文件!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleThree.js项目模板/title style body { margin: 0; overflow: hidden; } canvas { display: block; } /style /head body script typemodule src/src/main.js/script /body /html2.3 WebGPU环境检测与回退策略在实际项目中需要检测浏览器对WebGPU的支持情况并提供适当的回退方案// 检查WebGPU支持 async function checkWebGPUSupport() { if (!navigator.gpu) { console.warn(WebGPU不被支持将回退到WebGL); return false; } const adapter await navigator.gpu.requestAdapter(); if (!adapter) { console.warn(无法获取WebGPU适配器); return false; } return true; } // 根据支持情况选择渲染器 async function createRenderer() { const webGPUSupported await checkWebGPUSupport(); if (webGPUSupported) { // 使用WebGPU渲染器 const renderer new THREE.WebGPURenderer({ antialias: true, alpha: true }); return renderer; } else { // 回退到WebGL渲染器 const renderer new THREE.WebGLRenderer({ antialias: true, alpha: true }); return renderer; } }3. 模型加载与内存优化实战3.1 模型格式选择与压缩策略在WebGL环境下模型加载的优化至关重要。针对网络热词中提到的压缩问题这里给出具体解决方案import { GLTFLoader } from three/examples/jsm/loaders/GLTFLoader.js; import { DRACOLoader } from three/examples/jsm/loaders/DRACOLoader.js; class OptimizedModelLoader { constructor() { this.gltfLoader new GLTFLoader(); this.dracoLoader new DRACOLoader(); // 配置DRACO解码器路径 this.dracoLoader.setDecoderPath(https://www.gstatic.com/draco/v1/decoders/); this.gltfLoader.setDRACOLoader(this.dracoLoader); } // 异步加载模型并返回Promise loadModel(url) { return new Promise((resolve, reject) { this.gltfLoader.load( url, (gltf) { // 模型加载成功后的优化处理 this.optimizeModel(gltf.scene); resolve(gltf); }, (progress) { console.log(加载进度: ${(progress.loaded / progress.total * 100).toFixed(2)}%); }, (error) { console.error(模型加载失败:, error); reject(error); } ); }); } // 模型优化处理 optimizeModel(scene) { scene.traverse((child) { if (child.isMesh) { // 合并几何体减少draw call if (child.geometry) { child.geometry.computeVertexNormals(); } // 优化材质 if (child.material) { child.material.needsUpdate true; } } }); } }3.2 内存管理与资源释放WebGL应用常见的内存问题往往源于资源未及时释放。以下是完整的内存管理方案class MemoryManager { constructor() { this.textures new Set(); this.geometries new Set(); this.materials new Set(); } // 注册资源以便统一管理 registerTexture(texture) { this.textures.add(texture); return texture; } registerGeometry(geometry) { this.geometries.add(geometry); return geometry; } registerMaterial(material) { this.materials.add(material); return material; } // 释放单个资源 disposeResource(resource) { if (resource typeof resource.dispose function) { resource.dispose(); } // 从对应的集合中移除 this.textures.delete(resource); this.geometries.delete(resource); this.materials.delete(resource); } // 批量释放资源 disposeAll() { this.textures.forEach(texture this.disposeResource(texture)); this.geometries.forEach(geometry this.disposeResource(geometry)); this.materials.forEach(material this.disposeResource(material)); this.textures.clear(); this.geometries.clear(); this.materials.clear(); } // 内存使用情况监控 getMemoryUsage() { let totalMemory 0; this.geometries.forEach(geometry { if (geometry.attributes.position) { totalMemory geometry.attributes.position.array.byteLength; } }); this.textures.forEach(texture { if (texture.image) { totalMemory texture.image.width * texture.image.height * 4; // 假设RGBA } }); return { textureCount: this.textures.size, geometryCount: this.geometries.size, materialCount: this.materials.size, estimatedMemory: totalMemory }; } }3.3 模型压缩格式实战对比针对网络热词中提到的LZMA与LZ4压缩问题这里给出具体测试代码// 测试不同压缩格式的性能影响 class CompressionTester { async testCompressionPerformance() { const testModelUrl /models/test-model.glb; // 测试无压缩 console.time(无压缩加载); await this.loadModel(testModelUrl); console.timeEnd(无压缩加载); // 测试LZ4压缩 console.time(LZ4压缩加载); await this.loadModel(testModelUrl ?compressionlz4); console.timeEnd(LZ4压缩加载); // 内存使用对比 this.measureMemoryUsage(); } measureMemoryUsage() { if (performance.memory) { const usedJSHeapSize performance.memory.usedJSHeapSize; const totalJSHeapSize performance.memory.totalJSHeapSize; console.log(内存使用: ${(usedJSHeapSize / 1024 / 1024).toFixed(2)}MB / ${(totalJSHeapSize / 1024 / 1024).toFixed(2)}MB); } } // 实际项目中的压缩选择建议 getCompressionRecommendation() { return { recommendation: 在WebGL环境下优先使用LZ4压缩, reasons: [ LZ4解压速度快内存占用低, 适合Web环境的流式解压需求, 与DRACO几何压缩配合效果更好, 避免LZMA在移动设备上的内存峰值问题 ], implementation: // 在构建流程中配置压缩 // webpack配置示例 module.exports { module: { rules: [ { test: /\.(glb|gltf)$/, use: { loader: gltf-loader, options: { compression: lz4 } } } ] } } }; } }4. Three.js高级交互功能实现4.1 交互式盒式剖切技术盒式剖切是3D模型查看中的重要功能以下是完整实现class BoxClippingHelper { constructor(scene, camera, renderer) { this.scene scene; this.camera camera; this.renderer renderer; this.clippingPlanes []; this.boxHelper null; this.initBoxHelper(); this.setupEventListeners(); } initBoxHelper() { // 创建可视化剖切框 const boxGeometry new THREE.BoxGeometry(2, 2, 2); const boxMaterial new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true, transparent: true, opacity: 0.5 }); this.boxHelper new THREE.Mesh(boxGeometry, boxMaterial); this.scene.add(this.boxHelper); // 初始化剖切平面 this.updateClippingPlanes(); } updateClippingPlanes() { // 清空现有剖切平面 this.clippingPlanes []; if (!this.boxHelper) return; const box new THREE.Box3().setFromObject(this.boxHelper); const min box.min; const max box.max; // 创建六个剖切平面 this.clippingPlanes.push(new THREE.Plane(new THREE.Vector3(1, 0, 0), -max.x)); // 右平面 this.clippingPlanes.push(new THREE.Plane(new THREE.Vector3(-1, 0, 0), min.x)); // 左平面 this.clippingPlanes.push(new THREE.Plane(new THREE.Vector3(0, 1, 0), -max.y)); // 上平面 this.clippingPlanes.push(new THREE.Plane(new THREE.Vector3(0, -1, 0), min.y)); // 下平面 this.clippingPlanes.push(new THREE.Plane(new THREE.Vector3(0, 0, 1), -max.z)); // 前平面 this.clippingPlanes.push(new THREE.Plane(new THREE.Vector3(0, 0, -1), min.z)); // 后平面 // 应用到场景中的所有材质 this.scene.traverse((object) { if (object.isMesh object.material) { if (Array.isArray(object.material)) { object.material.forEach(material { material.clippingPlanes this.clippingPlanes; material.needsUpdate true; }); } else { object.material.clippingPlanes this.clippingPlanes; object.material.needsUpdate true; } } }); // 启用渲染器的剖切功能 this.renderer.localClippingEnabled true; } setupEventListeners() { const raycaster new THREE.Raycaster(); const mouse new THREE.Vector2(); let isDragging false; let selectedFace null; const onMouseDown (event) { mouse.x (event.clientX / window.innerWidth) * 2 - 1; mouse.y -(event.clientY / window.innerHeight) * 2 1; raycaster.setFromCamera(mouse, this.camera); const intersects raycaster.intersectObject(this.boxHelper); if (intersects.length 0) { isDragging true; selectedFace this.getSelectedFace(intersects[0].face); } }; const onMouseMove (event) { if (!isDragging || !selectedFace) return; mouse.x (event.clientX / window.innerWidth) * 2 - 1; mouse.y -(event.clientY / window.innerHeight) * 2 1; // 根据选中的面更新剖切框位置 this.updateBoxPosition(mouse, selectedFace); this.updateClippingPlanes(); }; const onMouseUp () { isDragging false; selectedFace null; }; this.renderer.domElement.addEventListener(mousedown, onMouseDown); this.renderer.domElement.addEventListener(mousemove, onMouseMove); this.renderer.domElement.addEventListener(mouseup, onMouseUp); } getSelectedFace(face) { // 简化实现根据法向量判断选中的面 const normal face.normal; return { normal: normal.clone(), originalNormal: normal.clone() }; } updateBoxPosition(mouse, selectedFace) { // 根据鼠标位置更新剖切框 const vector new THREE.Vector3(mouse.x, mouse.y, 0.5); vector.unproject(this.camera); const direction vector.sub(this.camera.position).normalize(); const distance -this.camera.position.z / direction.z; const pos this.camera.position.clone().add(direction.multiplyScalar(distance)); // 更新剖切框位置 this.boxHelper.position.copy(pos); } }4.2 复杂模型层级处理与对象选取针对Blender导出模型的三级空物体问题提供专门的解决方案class HierarchyAwarePicker { constructor(scene, camera) { this.scene scene; this.camera camera; this.raycaster new THREE.Raycaster(); this.mouse new THREE.Vector2(); // 存储原始层级关系 this.originalHierarchy new Map(); this.setupHierarchyMapping(); } // 建立对象层级映射 setupHierarchyMapping() { this.scene.traverse((object) { if (object.isMesh) { // 记录每个网格的完整层级路径 const path this.getObjectPath(object); this.originalHierarchy.set(object.uuid, { object: object, path: path, parent: object.parent }); } }); } // 获取对象的完整层级路径 getObjectPath(object) { const path []; let current object; while (current current ! this.scene) { path.unshift(current.name || current.type); current current.parent; } return path.join(/); } // 智能对象选取考虑层级关系 intelligentPick(mouseEvent) { this.mouse.x (mouseEvent.clientX / window.innerWidth) * 2 - 1; this.mouse.y -(mouseEvent.clientY / window.innerHeight) * 2 1; this.raycaster.setFromCamera(this.mouse, this.camera); // 获取所有相交对象 const intersects this.raycaster.intersectObjects(this.scene.children, true); if (intersects.length 0) return null; // 分析相交对象的层级关系 const hierarchyAnalysis this.analyzeIntersectionHierarchy(intersects); // 根据业务逻辑选择最合适的对象 return this.selectMostRelevantObject(hierarchyAnalysis); } analyzeIntersectionHierarchy(intersects) { const analysis { directMeshes: [], // 直接命中的网格 parentGroups: [], // 父级组 rootObjects: [], // 根级对象 hierarchyLevels: new Map() // 各层级的命中统计 }; intersects.forEach(intersect { const object intersect.object; analysis.directMeshes.push(object); // 分析层级关系 let current object; let level 0; while (current current ! this.scene) { if (!analysis.hierarchyLevels.has(level)) { analysis.hierarchyLevels.set(level, new Set()); } analysis.hierarchyLevels.get(level).add(current); if (level 1) { analysis.parentGroups.push(current); } if (current.parent this.scene) { analysis.rootObjects.push(current); } current current.parent; level; } }); return analysis; } selectMostRelevantObject(analysis) { // 业务逻辑1优先选择有名称的叶子节点 const namedMeshes analysis.directMeshes.filter(mesh mesh.name mesh.name ! ); if (namedMeshes.length 0) { return namedMeshes[0]; } // 业务逻辑2选择层级最深的组 const maxLevel Math.max(...analysis.hierarchyLevels.keys()); if (maxLevel 0) { const deepestObjects Array.from(analysis.hierarchyLevels.get(maxLevel)); return deepestObjects[0]; } // 默认返回第一个命中的对象 return analysis.directMeshes[0]; } // 修复Blender导出模型的层级问题 fixBlenderHierarchy() { this.scene.traverse((object) { // 检测空物体只有子节点没有几何体的对象 if (object.children.length 0 !object.isMesh) { const hasMeshChildren object.children.some(child child.isMesh); if (hasMeshChildren) { // 简化层级将网格提升一级 this.flattenHierarchy(object); } } }); } flattenHierarchy(parentObject) { const grandchildren []; parentObject.children.forEach(child { if (child.children.length 0) { grandchildren.push(...child.children); child.children []; // 清空子节点 } }); // 将孙节点直接添加到父节点 grandchildren.forEach(grandchild { parentObject.add(grandchild); }); } }4.3 路径规划与绕路生成算法实现Three.js中的自动绕路功能适用于导航、游戏等场景class PathPlanner { constructor(scene) { this.scene scene; this.navigationMesh null; this.obstacles []; this.graph null; } // 创建导航网格 createNavigationMesh(groundGeometry, cellSize 1) { const navMesh new THREE.Group(); navMesh.name navigationMesh; // 将地面几何体划分为网格 const bounds new THREE.Box3().setFromObject(groundGeometry); const width bounds.max.x - bounds.min.x; const depth bounds.max.z - bounds.min.z; const rows Math.floor(depth / cellSize); const cols Math.floor(width / cellSize); for (let row 0; row rows; row) { for (let col 0; col cols; col) { const x bounds.min.x col * cellSize cellSize / 2; const z bounds.min.z row * cellSize cellSize / 2; // 检查该网格是否可通行 if (this.isCellWalkable(x, z, cellSize)) { const cellGeometry new THREE.PlaneGeometry(cellSize, cellSize); const cellMaterial new THREE.MeshBasicMaterial({ color: 0x00ff00, transparent: true, opacity: 0.3, side: THREE.DoubleSide }); const cell new THREE.Mesh(cellGeometry, cellMaterial); cell.position.set(x, bounds.max.y 0.1, z); cell.rotation.x -Math.PI / 2; navMesh.add(cell); } } } this.navigationMesh navMesh; this.scene.add(navMesh); this.buildGraph(); return navMesh; } isCellWalkable(x, z, cellSize) { // 简化的可通行性检查 // 实际项目中需要与障碍物进行碰撞检测 const checkPoints [ { x: x - cellSize/3, z: z - cellSize/3 }, { x: x cellSize/3, z: z - cellSize/3 }, { x: x - cellSize/3, z: z cellSize/3 }, { x: x cellSize/3, z: z cellSize/3 } ]; return checkPoints.every(point { return !this.obstacles.some(obstacle { const obstacleBox new THREE.Box3().setFromObject(obstacle); return obstacleBox.containsPoint(new THREE.Vector3(point.x, 0, point.z)); }); }); } // 构建路径规划图 buildGraph() { if (!this.navigationMesh) return; this.graph { nodes: [], edges: [] }; // 将导航网格单元格转换为图节点 this.navigationMesh.children.forEach((cell, index) { this.graph.nodes.push({ id: index, position: cell.position.clone(), neighbors: [] }); }); // 构建邻接关系 this.graph.nodes.forEach((node, index) { const cellSize 1; // 假设单元格大小 const neighborPositions [ { x: node.position.x cellSize, z: node.position.z }, // 右 { x: node.position.x - cellSize, z: node.position.z }, // 左 { x: node.position.x, z: node.position.z cellSize }, // 上 { x: node.position.x, z: node.position.z - cellSize }, // 下 { x: node.position.x cellSize, z: node.position.z cellSize }, // 右上 { x: node.position.x - cellSize, z: node.position.z cellSize }, // 左上 { x: node.position.x cellSize, z: node.position.z - cellSize }, // 右下 { x: node.position.x - cellSize, z: node.position.z - cellSize } // 左下 ]; neighborPositions.forEach(neighborPos { const neighborNode this.findNodeAtPosition(neighborPos.x, neighborPos.z); if (neighborNode neighborNode.id ! node.id) { // 计算移动成本距离 const distance node.position.distanceTo(neighborNode.position); node.neighbors.push({ nodeId: neighborNode.id, cost: distance }); } }); }); } findNodeAtPosition(x, z) { return this.graph.nodes.find(node { return Math.abs(node.position.x - x) 0.1 Math.abs(node.position.z - z) 0.1; }); } // A*路径规划算法 findPath(startPos, endPos) { if (!this.graph) return null; const startNode this.findNearestNode(startPos); const endNode this.findNearestNode(endPos); if (!startNode || !endNode) return null; const openSet new Set([startNode.id]); const cameFrom new Map(); const gScore new Map(); // 从起点到当前节点的成本 const fScore new Map(); // gScore 启发式估计 // 初始化分数 this.graph.nodes.forEach(node { gScore.set(node.id, Infinity); fScore.set(node.id, Infinity); }); gScore.set(startNode.id, 0); fScore.set(startNode.id, this.heuristic(startNode, endNode)); while (openSet.size 0) { // 选择fScore最小的节点 let currentId null; let lowestFScore Infinity; openSet.forEach(nodeId { if (fScore.get(nodeId) lowestFScore) { lowestFScore fScore.get(nodeId); currentId nodeId; } }); if (currentId endNode.id) { return this.reconstructPath(cameFrom, currentId); } openSet.delete(currentId); const currentNode this.graph.nodes.find(n n.id currentId); currentNode.neighbors.forEach(neighbor { const tentativeGScore gScore.get(currentId) neighbor.cost; if (tentativeGScore gScore.get(neighbor.nodeId)) { cameFrom.set(neighbor.nodeId, currentId); gScore.set(neighbor.nodeId, tentativeGScore); fScore.set(neighbor.nodeId, tentativeGScore this.heuristic(this.graph.nodes.find(n n.id neighbor.nodeId), endNode)); if (!openSet.has(neighbor.nodeId)) { openSet.add(neighbor.nodeId); } } }); } return null; // 没有找到路径 } heuristic(nodeA, nodeB) { // 使用欧几里得距离作为启发式函数 return nodeA.position.distanceTo(nodeB.position); } reconstructPath(cameFrom, currentId) { const path [this.graph.nodes.find(n n.id currentId).position]; while (cameFrom.has(currentId)) { currentId cameFrom.get(currentId); path.unshift(this.graph.nodes.find(n n.id currentId).position); } return path; } findNearestNode(position) { let nearestNode null; let minDistance Infinity; this.graph.nodes.forEach(node { const distance node.position.distanceTo(position); if (distance minDistance) { minDistance distance; nearestNode node; } }); return nearestNode; } // 可视化路径 visualizePath(path) { if (!path || path.length 2) return; const points path.map(point new THREE.Vector3(point.x, point.y 0.5, point.z)); const geometry new THREE.BufferGeometry().setFromPoints(points); const material new THREE.LineBasicMaterial({ color: 0xff0000 }); const line new THREE.Line(geometry, material); this.scene.add(line); return line; } }5. Three.js与Vue3集成实战5.1 Vue3组件化Three.js开发将Three.js场景封装为Vue3组件实现响应式开发template div refcontainer classthree-container/div /template script import { onMounted, onUnmounted, ref } from vue; import * as THREE from three; import { OrbitControls } from three/examples/jsm/controls/OrbitControls; export default { name: ThreeScene, props: { backgroundColor: { type: String, default: #000011 }, enableControls: { type: Boolean, default: true } }, setup(props) { const container ref(null); let scene, camera, renderer, controls; let animationId; const initScene () { // 创建场景 scene new THREE.Scene(); scene.background new THREE.Color(props.backgroundColor); // 创建相机 camera new THREE.PerspectiveCamera( 75, container.value.clientWidth / container.value.clientHeight, 0.1, 1000 ); camera.position.z 5; // 创建渲染器 renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.value.clientWidth, container.value.clientHeight); renderer.setPixelRatio(window.devicePixelRatio); container.value.appendChild(renderer.domElement); // 添加轨道控制器 if (props.enableControls) { controls new OrbitControls(camera, renderer.domElement); controls.enableDamping true; } // 添加基础灯光 const ambientLight new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight new THREE.DirectionalLight(0xffffff, 0.5); directionalLight.position.set(1, 1, 1); scene.add(directionalLight); // 添加示例几何体 const geometry new THREE.BoxGeometry(1, 1, 1); const material new THREE.MeshPhongMaterial({ color: 0x00ff00 }); const cube new THREE.Mesh(geometry, material); scene.add(cube); // 启动动画循环 animate(); }; const animate () { animationId requestAnimationFrame(animate); if (controls) { controls.update(); } // 旋转立方体 const cube scene.getObjectByName(exampleCube); if (cube) { cube.rotation.x 0.01; cube.rotation.y 0.01; } renderer.render(scene, camera); }; const handleResize () { if (!container.value) return; camera.aspect container.value.clientWidth / container.value.clientHeight; camera.updateProjectionMatrix(); renderer.setSize(container.value.clientWidth, container.value.clientHeight); }; const cleanup () { if (animationId) { cancelAnimationFrame(animationId); } if (controls) { controls.dispose(); } if (renderer) { renderer.dispose(); } window.removeEventListener(resize, handleResize); }; onMounted(() { initScene(); window.addEventListener(resize, handleResize); }); onUnmounted(() { cleanup(); }); return { container }; } }; /script style scoped .three-container { width: 100%; height: 100%; position: relative; } /style5.2 Vue3响应式Three.js状态管理使用Pinia进行Three.js场景状态管理// stores/threeStore.js import { defineStore } from pinia; import { ref, computed } from vue; export const useThreeStore defineStore(three, () { // 状态 const objects ref([]); const selectedObject ref(null); const cameraPosition ref({ x: 0, y: 0, z: 5 }); const sceneBackground ref(#000011); const animationEnabled ref(true); // Getter const objectCount computed(() objects.value.length); const hasSelectedObject computed(() selectedObject.value ! null); const sceneInfo computed(() ({ objectCount: objectCount.value, background: sceneBackground.value, cameraPosition: cameraPosition.value })); // Actions const addObject (objectData) { const newObject { id: Date.now().toString(), ...objectData, position: objectData.position || { x: 0, y: 0, z: 0 }, rotation: objectData.rotation || { x: 0, y: 0, z: 0 }, scale: objectData.scale || { x: 1, y: 1, z: 1 } }; objects.value.push(newObject); return newObject.id; }; const removeObject (objectId) { const index objects.value.findIndex(obj obj.id objectId); if (index ! -1) { objects.value.splice(index, 1); if (selectedObject.value?.id objectId) { selectedObject.value null; } } }; const selectObject (objectId) { selectedObject.value objects.value.find(obj obj.id objectId) || null; }; const updateObjectProperty (objectId, property, value) { const object objects.value.find(obj obj.id objectId); if (object) { if (property in object) { object[property] value; } else if (property in object.position) { object.position[property] value; } else if (property in object.rotation) { object.rotation[property] value; } else if (property in object.scale) { object.scale[property] value; } } }; const setCameraPosition (position) { cameraPosition.value { ...position }; }; const setBackground (color) { sceneBackground.value color
返回列表