Three.js 透视相机完全指南:从入门到精通
什么是透视相机?
生活中的类比
想象你拿着一部手机摄像头对着一个房间:
- 📱 手机摄像头 = Three.js 中的相机
- 🎬 摄像头能看到的范围 = 视锥体(Frustum)
- 📐 摄像头的视角宽度 = 视野角度(FOV)
透视相机就像真实的摄像头一样,离物体越近,看到的范围越小;离物体越远,看到的范围越大。这就是"透视"的含义。
代码示例
import * as THREE from 'three';
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
40, // 视野角度(FOV)
2, // 宽高比(aspect ratio)(一般3D游戏中的屏幕适配都是在调整这个值)
0.1, // 近裁剪面(near)
1000 // 远裁剪面(far)
);
camera.position.z = 120; // 相机位置
四个关键参数详解
参数 1️⃣:视野角度(FOV - Field of View)
定义:相机能看到的垂直视角范围,单位是度数(°)。
直观理解:
- 🔭 FOV = 10° → 像用望远镜看,视角很窄,看到的东西很小但很清晰
- 📷 FOV = 40° → 像用普通手机摄像头,视角适中
- 🐟 FOV = 90° → 像用鱼眼镜头,视角很宽,看到很多东西但会变形
// 小视角 - 看得清楚但范围小
const camera1 = new THREE.PerspectiveCamera(20, 2, 0.1, 1000);
// 中等视角 - 平衡
const camera2 = new THREE.PerspectiveCamera(40, 2, 0.1, 1000);
// 大视角 - 看得多但容易变形
const camera3 = new THREE.PerspectiveCamera(75, 2, 0.1, 1000);
实际应用:
- 🎮 第一人称游戏:FOV 通常 60-90°
- 🏢 建筑可视化:FOV 通常 40-50°
- 🎥 电影效果:FOV 通常 24-35°
参数 2️⃣:宽高比(Aspect Ratio)
定义:相机画面的宽度与高度的比例。
计算方式:
const aspect = window.innerWidth / window.innerHeight;
// 例如:1920 / 1080 = 1.777...
为什么重要?
- ✅ 如果 aspect 与实际画布比例一致,画面不会被拉伸
- ❌ 如果不一致,圆形会变成椭圆,正方形会变成矩形
// 假设窗口是 1920×1080
const aspect = 1920 / 1080; // ≈ 1.78
const camera = new THREE.PerspectiveCamera(40, aspect, 0.1, 1000);
响应式设计:
function onWindowResize() {
const width = window.innerWidth;
const height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix(); // 重要!更新投影矩阵
renderer.setSize(width, height);
}
window.addEventListener('resize', onWindowResize);
参数 3️⃣:近裁剪面(Near)
定义:相机能看到的最近距离。比这个距离更近的物体不会被显示。
为什么需要它?
- 🎯 性能优化:不渲染相机背后的物体
- 🔍 避免穿模:防止相机进入物体内部时看到内部结构
// near = 0.1 意味着距离相机 0.1 单位以内的物体看不到
const camera = new THREE.PerspectiveCamera(40, 2, 0.1, 1000);
// 如果物体在 z = 0.05,它会被裁剪掉
const cube = new THREE.Mesh(geometry, material);
cube.position.z = 0.05; // ❌ 看不到
参数 4️⃣:远裁剪面(Far)
定义:相机能看到的最远距离。比这个距离更远的物体不会被显示。
为什么需要它?
- 🎯 性能优化:不渲染太远的物体
- 🔍 深度精度:提高深度缓冲的精度
// far = 1000 意味着距离相机 1000 单位以外的物体看不到
const camera = new THREE.PerspectiveCamera(40, 2, 0.1, 1000);
// 如果物体在 z = 1500,它会被裁剪掉
const star = new THREE.Mesh(geometry, material);
star.position.z = 1500; // ❌ 看不到
视锥体的可视范围计算
什么是视锥体?
视锥体是一个四棱锥形的空间,只有在这个空间内的物体才能被看到。
计算可视范围的公式
在距离相机 distance 处,可视范围的大小为:
垂直高度 = 2 × tan(FOV/2) × distance
水平宽度 = 垂直高度 × aspect
实际计算示例
假设我们有这样的相机配置:
const fov = 40; // 视野角度
const aspect = 2; // 宽高比(2:1)
const distance = 120; // 相机距离(z = 120)
// 计算垂直可视高度
const vFOV = fov * Math.PI / 180; // 转换为弧度
const height = 2 * Math.tan(vFOV / 2) * distance;
// height = 2 × tan(20°) × 120
// height = 2 × 0.364 × 120
// height ≈ 87.2
// 计算水平可视宽度
const width = height * aspect;
// width = 87.2 × 2
// width ≈ 174.4
结论:在 z=120 处,相机能看到的范围是:
- 📏 宽度:174.4 单位
- 📏 高度:87.2 单位
坐标范围的确定
场景布局示例
假设我们要在场景中放置一个 5×4 的网格(5列 4行),每个物体之间间距为 15 单位:
const spread = 15; // 间距
// 物体位置计算
for (let x = -2; x <= 2; x++) {
for (let y = -1; y <= 2; y++) {
const obj = createObject();
obj.position.x = x * spread; // x: -30, -15, 0, 15, 30
obj.position.y = y * spread; // y: -15, 0, 15, 30
scene.add(obj);
}
}
对象分布范围:
- X 轴:-30 到 30(宽度 60)
- Y 轴:-15 到 30(高度 45)
检查是否超出可视范围
// 相机可视范围
const visibleWidth = 174.4;
const visibleHeight = 87.2;
// 对象范围
const objectWidth = 60;
const objectHeight = 45;
// 检查
console.log(`宽度是否超出: ${objectWidth > visibleWidth}`); // false ✅
console.log(`高度是否超出: ${objectHeight > visibleHeight}`); // false ✅
如果超出范围怎么办?
问题:当 spread=20 时,对象范围变为 80×60,超出了可视范围。
解决方案:
方案 1:增加相机距离
// 原来
camera.position.z = 120;
// 改为
camera.position.z = 160; // 距离越远,看到的范围越大
方案 2:增加视野角度
// 原来
const fov = 40;
// 改为
const fov = 60; // 视角越大,看到的范围越大
方案 3:减小间距
// 原来
const spread = 20;
// 改为
const spread = 15; // 物体靠得更近
方案 4:使用正交相机
如果你不需要透视效果,可以使用正交相机(OrthographicCamera),它的可视范围不会随距离变化:
const camera = new THREE.OrthographicCamera(
-100, // left
100, // right
50, // top
-50, // bottom
0.1, // near
1000 // far
);
实战代码
完整的响应式相机设置
import * as THREE from 'three';
class ResponsiveCamera {
constructor() {
this.fov = 40;
this.near = 0.1;
this.far = 1000;
this.distance = 120;
this.updateCamera();
}
updateCamera() {
const aspect = window.innerWidth / window.innerHeight;
this.camera = new THREE.PerspectiveCamera(
this.fov,
aspect,
this.near,
this.far
);
this.camera.position.z = this.distance;
}
// 计算指定深度处的可视范围
getVisibleRange(depth = null) {
const vFOV = (this.fov * Math.PI) / 180;
// 如果没有指定深度,使用相机的默认距离
const distance = depth !== null ? depth : this.distance;
const height = 2 * Math.tan(vFOV / 2) * distance;
const width = height * (window.innerWidth / window.innerHeight);
return { width, height };
}
// 检查物体是否在可视范围内
isObjectVisible(obj) {
const pos = obj.position;
// 计算物体相对于相机的距离(沿着相机的视线方向)
const distanceFromCamera = this.camera.position.z - pos.z;
// 计算物体所在深度的可视范围
const range = this.getVisibleRange(distanceFromCamera);
console.log('物体距离相机:', distanceFromCamera, '该深度的可视范围:', range);
return (
Math.abs(pos.x) <= range.width / 2 &&
Math.abs(pos.y) <= range.height / 2 &&
distanceFromCamera >= this.near &&
distanceFromCamera <= this.far
);
}
// 窗口大小改变时更新
onWindowResize() {
this.updateCamera();
this.camera.updateProjectionMatrix();
}
}
// 使用示例
// 使用示例
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);
// 4. 创建渲染器:将3D场景渲染到网页上
const renderer = new THREE.WebGLRenderer({ antialias: true }); // antialias开启抗锯齿
renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染尺寸
// 将渲染器的画布添加到网页中
document.body.appendChild(renderer.domElement);
const camera = new ResponsiveCamera();
window.addEventListener('resize', () => camera.onWindowResize());
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 50, 50);
scene.add(directionalLight);
// 检查物体是否可见
const cube = new THREE.Mesh(new THREE.BoxGeometry(8, 8, 8), createMaterial());
cube.position.set(20, 20, -10);
scene.add(cube);
function animate(time) {
renderer.render(scene, camera.getCamera());
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
console.log(camera.isObjectVisible(cube)); // true or false
小结
-
FOV 和 distance 决定可视范围
- FOV 越大,看到的范围越大
- distance 越大,看到的范围越大
-
aspect 必须与画布比例一致
- 否则画面会被拉伸变形
-
near 和 far 定义了深度范围
- 在这个范围外的物体看不到
📂 核心代码与完整示例: my-three-app
总结
如果你喜欢本教程,记得点赞+收藏!关注我获取更多Three.js开发干货