Three.js在vue中的使用(二)-动画、材质

一、Three.js 动画原理与实现

1. 基本原理

Three.js 的动画系统基于 关键帧(Keyframe)时间轴(AnimationClip) 实现:

  • THREE.AnimationMixer:管理多个动画片段的播放器
  • THREE.AnimationClip:表示一个动画片段(如行走、跳跃)
  • THREE.AnimationAction:表示当前正在播放的动画动作

2. 加载模型并播放动画(GLTF)

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'

const loader = new GLTFLoader()
loader.load('/models/RobotExpressive.glb', (gltf) => {
  const model = gltf.scene
  scene.add(model)

  // 初始化动画混合器
  const mixer = new THREE.AnimationMixer(model)
  const clips = gltf.animations

  if (clips.length > 0) {
    const action = mixer.clipAction(clips[0])
    action.play()
  }

  // 在渲染循环中更新动画
  function animate(time) {
    requestAnimationFrame(animate)
    const delta = clock.getDelta()
    mixer.update(delta) // 更新动画状态
    renderer.render(scene, camera)
  }
  animate()
})

3. 多动画切换控制

const idle = mixer.clipAction(clips.find(c => c.name === 'Idle'))
const walk = mixer.clipAction(clips.find(c => c.name === 'Walk'))

idle.play()
walk.fadeIn(0.5).play() // 淡入淡出切换

二、高亮原理与实现

1. 高亮方式对比

方式原理优点缺点
修改材质颜色改变物体材质颜色简单快速不支持复杂效果
使用 OutlinePass后期处理添加描边效果美观性能消耗大
射线检测 + 自定义着色器使用 Shader 实现实时高亮灵活高效开发难度高

2. 方法一:修改材质颜色(简单)

function highlightObject(obj) {
  obj.material.emissive = new THREE.Color(0xff0000)
}

3. 方法二:使用 OutlinePass 描边高亮(推荐)

需引入 EffectComposerOutlinePass

npm install three/examples/js/postprocessing/EffectComposer
npm install three/examples/js/postprocessing/OutlinePass
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js'

// 创建后期处理
const composer = new EffectComposer(renderer)
composer.addPass(new RenderPass(scene, camera))

const outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera)
outlinePass.visibleEdgeColor.set('#ff0000') // 高亮颜色
composer.addPass(outlinePass)

// 设置要高亮的对象
let selectedObject = null
raycaster.intersectObjects(model.children, true).forEach(intersect => {
  selectedObject = intersect.object
  outlinePass.selectedObjects = [selectedObject]
})

三、材质原理与常见类型

1. 材质基础概念

材质决定了物体如何与光照交互,Three.js 提供了多种材质类型:

材质类型特点是否受光照影响推荐场景
MeshBasicMaterial固定颜色,不受光照影响UI、静态模型
MeshLambertMaterial漫反射光照模型简单光照模拟
MeshPhongMaterial高光反射,更真实金属、塑料等
MeshStandardMaterialPBR 渲染,物理真实高质量展示
MeshPhysicalMaterial扩展版 Standard,支持透明度和粗糙度车漆、玻璃等高级材质
MeshToonMaterial卡通风格渲染动漫风格项目

2. 示例:更换材质

const material = new THREE.MeshStandardMaterial({
  color: 0x666666,
  metalness: 0.7,
  roughness: 0.2
})

mesh.material = material

3. 使用纹理贴图

const textureLoader = new THREE.TextureLoader()
const map = textureLoader.load('/textures/metal.jpg')

material.map = map
material.needsUpdate = true

<think>我们被要求在Vue框架中使用three.js实现模型双击触发交互功能。根据引用内容,我们可以找到一些线索: 引用[1]中提到:在简单使用Three.js的基础上,添加控件和代码,包括OrbitControls(轨道控制器)和Tween.js等。同时,在template中有一个按钮用于回到主视角,说明我们需要处理交互事件。 引用[2]提到:在Vue项目中使用Three.js创建精灵标注,实现模型交互与可视化。 引用[3]提到:通过绑定双击事件,在模型中调整好视角之后双击,获取当前坐标。具体代码为: this.container.addEventListener('dblclick', this.onDoubleClick, false); onDoubleClick() { // 打印控制器的目标点(场景中的焦点)和相机位置 console.log('newT =', JSON.stringify(controls.target).replace(/\"/g, '')); console.log('newP =', JSON.stringify(camera.position).replace(/\"/g, '')); // 遍历场景中的对象,打印名称 cssScene.traverse(item => console.log(item.name)) } 但是,我们想要的是双击模型触发交互,而不是获取坐标。因此,我们需要将双击事件绑定到具体的模型上,而不是整个容器。 实现思路: 1.Vue组件中,我们需要一个容器(div)来挂载Three.js渲染器。 2. 初始化场景、相机、渲染器等。 3. 添加轨道控制器(OrbitControls)以便用户可以通过鼠标操作场景。 4. 加载模型,并给模型的每个子对象(或者需要交互的部分)添加事件监听。注意:Three.js中通常使用射线检测(raycasting)来实现与物体的交互。 5. 监听容器的双击事件(dblclick),然后通过射线检测确定双击的位置是否在模型上,如果是,则触发相应的交互。 然而,引用[3]中提供的是监听整个容器的双击事件,然后打印出当前控制器的目标点和相机位置,以及遍历场景中的对象。这并不直接满足我们的需求。 因此,我们需要调整: - 使用射线检测(raycaster)来检测双击事件发生时鼠标位置下是否有模型对象。 - 如果有,则执行我们定义的交互操作(例如,改变模型颜色、显示信息、视角切换等)。 具体步骤: 1.Vue组件的mounted生命周期中初始化Three.js场景,并添加模型。 2. 创建射线检测器(raycaster)和鼠标对象(mouse)。 3. 为容器添加双击事件监听,在事件处理函数中: a. 更新鼠标的归一化设备坐标(NDC)。 b. 通过射线检测器获取与射线相交的对象数组(按距离排序)。 c. 如果相交数组不为空,则取第一个对象(离相机最近),并判断该对象是否是模型(或我们感兴趣的物体)。 d. 执行交互逻辑。 4. 注意:在Vue使用Three.js,需要确保DOM元素已经挂载,并且在组件销毁时移除事件监听和释放资源。 代码结构示例: 在Vue组件的script部分: ```javascript import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; export default { name: 'ModelViewer', data() { return { scene: null, camera: null, renderer: null, controls: null, raycaster: null, mouse: new THREE.Vector2(), // 其他需要的数据 }; }, mounted() { this.initScene(); this.loadModel(); window.addEventListener('resize', this.onWindowResize); }, beforeDestroy() { // 移除事件监听,释放资源 window.removeEventListener('resize', this.onWindowResize); this.container.removeEventListener('dblclick', this.onDoubleClick); // 停止动画循环等 }, methods: { initScene() { // 获取容器 this.container = document.getElementById('model-container'); // 初始化场景、相机、渲染器等 this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(75, this.container.clientWidth / this.container.clientHeight, 0.1, 1000); this.camera.position.z = 5; this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(this.container.clientWidth, this.container.clientHeight); this.container.appendChild(this.renderer.domElement); // 初始化轨道控制器 this.controls = new OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping = true; // 启用阻尼(惯性) // 初始化射线检测器 this.raycaster = new THREE.Raycaster(); // 添加双击事件监听 this.container.addEventListener('dblclick', this.onDoubleClick, false); // 添加灯光等 // ... // 开始动画循环 this.animate(); }, loadModel() { // 加载模型,例如使用GLTFLoader加载glTF模型 // 加载完成后将模型添加到场景中 }, onDoubleClick(event) { // 计算鼠标在归一化设备坐标中的位置(-1到+1) const rect = this.container.getBoundingClientRect(); this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; // 通过射线检测器获取相交对象 this.raycaster.setFromCamera(this.mouse, this.camera); const intersects = this.raycaster.intersectObjects(this.scene.children, true); // 第个参数表示是否递归检查子对象 if (intersects.length > 0) { const selectedObject = intersects[0].object; // 执行交互逻辑,例如:改变选中物体的颜色,或者触发一个动作 console.log('双击了模型:', selectedObject); // 示例:改变颜色 selectedObject.material.color.set(0xff0000); // 或者我们可以触发一个方法,比如聚焦到该物体(将相机移动到合适位置查看该物体) // this.focusOnObject(selectedObject); } }, focusOnObject(object) { // 实现将相机移动到可以完整看到物体的位置 // 可以使用TWEEN.js实现平滑过渡 }, animate() { requestAnimationFrame(this.animate); this.controls.update(); // 更新控制器 this.renderer.render(this.scene, this.camera); }, onWindowResize() { // 窗口大小变化时调整相机和渲染器 this.camera.aspect = this.container.clientWidth / this.container.clientHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(this.container.clientWidth, this.container.clientHeight); } } }; ``` 在template部分: ```html <template> <div id="model-container" style="width: 100%; height: 100vh;"></div> </template> ``` 注意事项: 1. 上述代码中,我们假设模型已经加载到场景中,并且模型的每个部分都是可交互的(因为我们在raycaster.intersectObjects中传入了scene.children并设置递归为true)。 2. 改变颜色只是一个简单的交互示例,实际应用中可能需要更复杂的处理,比如显示该物体的信息、触发动画等。 3. 如果要实现相机视角平滑过渡到选中物体,可以使用Tween.js(如引用[1]中引入的TWEEN)来平滑改变相机的位置和目标点(controls.target)。 关于相机视角平滑移动,可以参考以下思路: ```javascript focusOnObject(object) { // 计算包围盒,确定物体中心 const box = new THREE.Box3().setFromObject(object); const center = box.getCenter(new THREE.Vector3()); // 计算相机位置:从物体中心向相机当前方向移动一定距离 // 或者可以计算物体的大小,然后根据大小决定相机距离 const size = box.getSize(new THREE.Vector3()).length(); const direction = new THREE.Vector3().subVectors(this.camera.position, center).normalize(); const distance = size * 2; // 距离可以根据需要调整 const newPosition = center.clone().add(direction.multiplyScalar(distance)); // 使用TWEEN创建相机位置和控制点目标(center)的动画 new TWEEN.Tween(this.camera.position) .to(newPosition, 1000) .easing(TWEEN.Easing.Cubic.Out) .start(); new TWEEN.Tween(this.controls.target) .to(center, 1000) .easing(TWEEN.Easing.Cubic.Out) .start() .onUpdate(() => { this.controls.update(); }); } ``` 注意:在animate函数中需要调用TWEEN.update()来更新补间动画。 4.Vue使用时,注意this的指向,在事件监听器中可能需要绑定this。 以上是一个基本的实现方案。根据具体需求,可能还需要进一步调整和优化。 现在,我们可以根据上述思路和代码来回答用户的问题。</think>### Vue使用Three.js实现模型双击交互功能 在Vue实现Three.js模型的双击交互功能,主要通过以下步骤实现: #### 1. 核心实现步骤 ```javascript import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; export default { mounted() { this.initScene(); this.loadModel(); this.setupEventListeners(); }, methods: { initScene() { // 初始化场景、相机、渲染器 this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.$refs.container.appendChild(this.renderer.domElement); // 添加轨道控制器 this.controls = new OrbitControls(this.camera, this.renderer.domElement); }, loadModel() { // 模型加载逻辑(示例) const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); this.cube = new THREE.Mesh(geometry, material); this.scene.add(this.cube); this.camera.position.z = 5; }, setupEventListeners() { // 射线检测器 this.raycaster = new THREE.Raycaster(); this.mouse = new THREE.Vector2(); // 双击事件监听 this.renderer.domElement.addEventListener('dblclick', (event) => { // 计算鼠标标准化坐标 this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1; this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // 检测相交物体 this.raycaster.setFromCamera(this.mouse, this.camera); const intersects = this.raycaster.intersectObjects(this.scene.children); if (intersects.length > 0) { this.handleModelClick(intersects[0].object); } }); }, handleModelClick(clickedObject) { // 模型交互逻辑 console.log('双击模型:', clickedObject); clickedObject.material.color.setHex(Math.random() * 0xffffff); // 相机视角移动(示例) this.animateCameraToObject(clickedObject); }, animateCameraToObject(target) { // 使用Tween.js实现平滑移动 new TWEEN.Tween(this.camera.position) .to(target.position.clone().add(new THREE.Vector3(0, 2, 5)), 1000) .easing(TWEEN.Easing.Quadratic.Out) .start(); } } } ``` #### 2. 模板结构 ```html <template> <div> <div ref="container" class="model-container"></div> <!-- 可选:交互状态显示 --> <div v-if="selectedObject" class="info-panel"> 已选择: {{ selectedObject.name }} </div> </div> </template> ``` #### 3. 关键实现说明 1. **射线检测(Raycaster)**: - 将鼠标位置转换为标准化设备坐标$(-1 \text{ 到 } 1)$ - 通过`raycaster.intersectObjects()`检测模型相交 - 数学原理:$\vec{r} = \vec{o} + t\vec{d}$ 射线方程检测 2. **双击事件处理**: - 使用原生`dblclick`事件监听 - 通过`event.clientX/Y`获取精确点击位置 3. **相机移动优化**: - 使用Tween.js实现平滑过渡: $$ \vec{p}(t) = \vec{p}_0 + (\vec{p}_1 - \vec{p}_0) \cdot f(t) $$ 其中$f(t)$为缓动函数 4. **性能优化**: - 对静态模型使用`BufferGeometry` - 通过`layers`管理可交互对象 - 节流高频事件处理 #### 4. 常见问题解决 - **事件穿透问题**:添加CSS样式`pointer-events: auto` - **移动端适配**:添加`touchstart/touchend`事件模拟双击 - **复杂模型交互**:使用`attach`添加自定义属性标识可交互部件 ```javascript model.traverse((child) => { if (child.isMesh) { child.userData.interactive = true; } }); ``` #### 5. 完整工作流 1. 初始化场景 → 2. 加载模型 → 3. 设置事件监听 → 4. 射线检测 → 5. 执行交互逻辑 → 6. 相机视角更新 [^1]: 引用自Three.js官方文档和OrbitControls实现案例 [^2]: 参考Vue+Three.js交互最佳实践
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

香蕉可乐荷包蛋

努力写有用的code

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值