05 相机与控制器
学习 Three.js 中的相机类型和交互控制
相机类型
PerspectiveCamera(透视相机)
模拟人眼视觉效果,近大远小。
js
const camera = new THREE.PerspectiveCamera(
75, // 垂直视野角度(FOV)
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近裁切面
1000 // 远裁切面
)
camera.position.set(0, 0, 5)1
2
3
4
5
6
7
2
3
4
5
6
7
OrthographicCamera(正交相机)
物体大小固定,不受距离影响。
js
const aspect = window.innerWidth / window.innerHeight
const frustumSize = 10
const camera = new THREE.OrthographicCamera(
-frustumSize * aspect / 2, // 左
frustumSize * aspect / 2, // 右
frustumSize / 2, // 上
-frustumSize / 2, // 下
0.1, // 近
1000 // 远
)1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
相机位置与朝向
js
// 设置位置
camera.position.set(0, 2, 10)
// 设置朝向(看向原点)
camera.lookAt(0, 0, 0)
// 朝向某个物体
camera.lookAt(targetObject.position)
// 或者用 setFromMatrixPosition
camera.lookAt(mesh.position)
// 相机在物体上方俯视
camera.position.set(0, 10, 0)
camera.lookAt(0, 0, 0)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
相机控制
OrbitControls(轨道控制器)
鼠标控制相机旋转、缩放、平移。最常用的控制器。
bash
pnpm add three1
js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
const controls = new OrbitControls(camera, renderer.domElement)
// 目标点(相机围绕哪个点旋转)
controls.target.set(0, 0, 0)
// 开启阻尼(惯性效果)
controls.enableDamping = true
controls.dampingFactor = 0.05
// 禁用某些操作
controls.enablePan = true // 启用右键拖拽平移
controls.enableRotate = true // 启用左键旋转
controls.enableZoom = true // 启用滚轮缩放
// 缩放范围
controls.minDistance = 2
controls.maxDistance = 50
// 旋转角度限制
controls.minPolarAngle = 0 // 最小俯仰角
controls.maxPolarAngle = Math.PI / 2 // 最大俯仰角(90°,不进入地下)
// 自动旋转
controls.autoRotate = true
controls.autoRotateSpeed = 2
// 必须在动画循环中调用 update
function animate() {
requestAnimationFrame(animate)
controls.update() // 配合阻尼和自动旋转
renderer.render(scene, camera)
}
animate()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
TrackballControls(追踪球控制器)
类似 OrbitControls,但没有极角限制,可以 360° 旋转。
MapControls(大地图专用)
适合地球仪、大地图场景,水平和垂直旋转有限制。
js
import { MapControls } from 'three/addons/controls/MapControls.js'
const controls = new MapControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.051
2
3
4
5
2
3
4
5
FirstPersonControls(第一人称控制器)
类似游戏中的 WASD + 鼠标视角控制。
PointerLockControls(锁指针控制)
点击后鼠标被锁定,适合第一人称射击游戏风格。
相机跟随物体
让相机始终跟随一个移动的物体:
js
const target = new THREE.Object3D()
scene.add(target)
// 相机跟随
function animate() {
requestAnimationFrame(animate)
// 让目标跟随物体位置(偏移)
target.position.copy(mesh.position)
target.position.y += 2 // 在物体上方2个单位
// 相机朝向目标
camera.lookAt(target.position)
renderer.render(scene, camera)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
相机抖动问题
响应窗口大小变化
js
window.addEventListener('resize', () => {
// 更新相机宽高比
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix() // 重要!
// 更新渲染器大小
renderer.setSize(window.innerWidth, window.innerHeight)
})1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
渲染器像素比
js
// 高性能设备用 1x,省电
// 高清屏用 window.devicePixelRatio
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) // 最高2倍1
2
3
2
3
射线(Raycaster)- 交互选中物体
js
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()
// 鼠标移动时更新 mouse 坐标
window.addEventListener('mousemove', (event) => {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
})
// 点击选中
window.addEventListener('click', (event) => {
// 更新射线
raycaster.setFromCamera(mouse, camera)
// 检测与物体的交叉
const intersects = raycaster.intersectObjects(scene.children)
if (intersects.length > 0) {
const hit = intersects[0]
console.log('选中了:', hit.object.name)
console.log('距离:', hit.distance)
console.log('交点位置:', hit.point)
// 高亮效果
hit.object.material.emissive.setHex(0xff0000)
}
})1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
鼠标悬停效果
js
let hoveredObject = null
window.addEventListener('mousemove', (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 (hoveredObject) {
hoveredObject.material.emissive.setHex(0x000000)
hoveredObject = null
}
// 设置新的悬停物体
if (intersects.length > 0) {
hoveredObject = intersects[0].object
hoveredObject.material.emissive.setHex(0x444444)
document.body.style.cursor = 'pointer'
} else {
document.body.style.cursor = 'default'
}
})1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
相机平滑移动
使用 TWEEN 平滑过渡
js
import * as TWEEN from '@tweenjs/tween.js'
// 平滑移动相机到目标位置
new TWEEN.Tween(camera.position)
.to({ x: 10, y: 5, z: 10 }, 1000) // 1秒
.easing(TWEEN.Easing.Quadratic.Out)
.start()
function animate(time) {
requestAnimationFrame(animate)
TWEEN.update(time)
renderer.render(scene, camera)
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
双相机切换
js
let currentCamera = perspectiveCamera
function switchToOrtho() {
currentCamera = orthographicCamera
// 更新控制器目标相机
controls.object = currentCamera
}
function switchToPerspective() {
currentCamera = perspectiveCamera
controls.object = currentCamera
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
相机抖动(jittering)解决方案
js
// 1. 使用物理正确的时间步
let lastTime = performance.now()
function animate() {
const now = performance.now()
const delta = (now - lastTime) / 1000
lastTime = now
// 用 delta 控制动画速度
mesh.rotation.y += 1.0 * delta
}
// 2. 或使用 setAnimationLoop(Three.js r152+)
renderer.setAnimationLoop(animate)1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14