Three.js 相机视角的平滑过渡与点击模型切换视角

在 Three.js 中,实现相机视角的平滑过渡和点击模型切换到查看模型视角是一个常见且有用的功能。这种效果不仅能提升用户体验,还能为场景互动添加更多的动态元素。

1. 基本设置

首先,我们需要创建一个基本的 Three.js 场景,包括相机、渲染器、光源以及一些示例模型。

创建场景和相机
复制代码
// 创建场景
const scene = new THREE.Scene();
​
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 10);
​
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
​
// 创建光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 10, 10);
scene.add(light);
添加示例模型
复制代码
// 创建一个简单的几何体
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.position.set(0, 1, 0);
scene.add(cube);
​
// 创建另一个几何体
const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(2, 1, 0);
scene.add(sphere);

2. 引入动画库

为了实现平滑过渡,我们引入 tween.js 动画库。

复制代码
npm install @tweenjs/tween.js

import * as TWEEN from '@tweenjs/tween.js'

3. 实现相机视角的平滑切换

定义相机切换函数
复制代码
function smoothCameraTransition(targetPosition, targetLookAt) {
    // 保存当前相机的位置和朝向
    const startPosition = camera.position.clone();
    const startLookAt = new THREE.Vector3();
    camera.getWorldDirection(startLookAt);
​
    // 创建 tween 动画
    new TWEEN.Tween(startPosition)
        .to(targetPosition, 2000) // 动画持续时间为2000毫秒
        .easing(TWEEN.Easing.Quadratic.InOut) // 使用缓动函数
        .onUpdate(() => {
            camera.position.copy(startPosition);
        })
        .start();
​
    new TWEEN.Tween(startLookAt)
        .to(targetLookAt, 2000)
        .easing(TWEEN.Easing.Quadratic.InOut)
        .onUpdate(() => {
            camera.lookAt(startLookAt);
        })
        .start();
}
更新渲染循环

确保在渲染循环中更新 tween 动画。

复制代码
function animate() {
    requestAnimationFrame(animate);
    TWEEN.update();
    renderer.render(scene, camera);
}
animate();

4. 实现点击模型切换视角

添加射线投射器

我们需要添加射线投射器来检测用户点击的模型。

复制代码
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
​
function onMouseClick(event) {
    // 将鼠标点击位置转换为标准化设备坐标
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
​
    // 更新射线投射器
    raycaster.setFromCamera(mouse, camera);
​
    // 计算交互对象
    const intersects = raycaster.intersectObjects(scene.children);
​
    if (intersects.length > 0) {
        const intersectedObject = intersects[0].object;
        // 切换相机视角到点击的模型
        const targetPosition = new THREE.Vector3().copy(intersectedObject.position).add(new THREE.Vector3(0, 2, 5));
        const targetLookAt = intersectedObject.position.clone();
        smoothCameraTransition(targetPosition, targetLookAt);
    }
}
​
window.addEventListener('click', onMouseClick, false);

5. 完整代码示例

将上述代码片段整合在一起,形成一个完整的示例。

复制代码
<template>
  <div ref="rendererContainer" class="renderer-container"></div>
</template>

<script setup>
import { onMounted, ref } from 'vue';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import TWEEN from '@tweenjs/tween.js';

// 引用模板中的 DOM 元素
const rendererContainer = ref(null);

// 初始化场景、相机和渲染器
onMounted(() => {
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(0, 5, 10);

  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  rendererContainer.value.appendChild(renderer.domElement);

  const light = new THREE.DirectionalLight(0xffffff, 1);
  light.position.set(0, 10, 10);
  scene.add(light);

  // 添加示例模型
  const geometry = new THREE.BoxGeometry();
  const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
  const cube = new THREE.Mesh(geometry, material);
  cube.position.set(0, 1, 0);
  scene.add(cube);

  const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
  const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  sphere.position.set(2, 1, 0);
  scene.add(sphere);

  // 相机平滑过渡函数
  function smoothCameraTransition(targetPosition, targetLookAt) {
    const startPosition = camera.position.clone();
    const startLookAt = new THREE.Vector3();
    camera.getWorldDirection(startLookAt);

    new TWEEN.Tween(startPosition)
      .to(targetPosition, 2000)
      .easing(TWEEN.Easing.Quadratic.InOut)
      .onUpdate(() => {
        camera.position.copy(startPosition);
      })
      .start();

    new TWEEN.Tween(startLookAt)
      .to(targetLookAt, 2000)
      .easing(TWEEN.Easing.Quadratic.InOut)
      .onUpdate(() => {
        camera.lookAt(startLookAt);
      })
      .start();
  }

  function animate() {
    requestAnimationFrame(animate);
    TWEEN.update();
    renderer.render(scene, camera);
  }
  animate();

  // 添加射线投射器
  const raycaster = new THREE.Raycaster();
  const mouse = new THREE.Vector2();

  function onMouseClick(event) {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(scene.children);

    if (intersects.length > 0) {
      const intersectedObject = intersects[0].object;
      const targetPosition = new THREE.Vector3().copy(intersectedObject.position).add(new THREE.Vector3(0, 2, 5));
      const targetLookAt = intersectedObject.position.clone();
      smoothCameraTransition(targetPosition, targetLookAt);
    }
  }

  window.addEventListener('click', onMouseClick, false);
});
</script>

<style>
.renderer-container {
  width: 100%;
  height: 100vh;
}
</style>
相关推荐
沧澜sincerely9 小时前
WebSocket 实时聊天功能
网络·websocket·vue·springboot
苏打水com9 小时前
第二十篇:Day58-60 前端性能优化进阶——从“能用”到“好用”(对标职场“体验优化”需求)
前端·css·vue·html·js
Jeking2179 小时前
进阶流程图绘制工具 Unione Flow Editor-- 直击行业痛点:高扩展性解决方案解析
vue·流程图·workflow·unione flow·flow editor·unione cloud
叫我詹躲躲9 小时前
基于 Three.js 的 3D 地图可视化:核心原理与实现步骤
前端·three.js
旧梦星轨9 小时前
掌握 Vite 环境配置:从 .env 文件到运行模式的完整实践
前端·前端框架·node.js·vue·react
sg_knight10 小时前
模块热替换 (HMR):前端开发的“魔法”与提速秘籍
前端·javascript·vue·浏览器·web·模块化·hmr
李慕婉学姐20 小时前
【开题答辩过程】以《基于Android的出租车运行监测系统设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·后端·vue
map_3d_vis1 天前
JSAPIThree 加载单体三维模型学习笔记:SimpleModel 简易加载方式
学习笔记·three.js·gltf·glb·初学者·三维模型·mapvthree·jsapithree·simplemodel
苏打水com1 天前
第十五篇:Day43-45 前端性能优化进阶——从“可用”到“极致”(对标职场“高并发场景优化”需求)
前端·css·vue·html·js
半桶水专家1 天前
vue3事件处理详解
vue