Skip to content

十、相机基础

更新: 12/19/2025字数: 0 字阅读: 0 分钟

正投影相机

  • 正投影相机的长方体可视化空间和透视投影PerspectiveCamera视锥体相似,只是形状不同。
  • 正投影相机OrthographicCamera和透视投影相机PerspectiveCamera的区别就是,透视投影可以模拟人眼观察世界的视觉效果(没有了近大远小的效果),正投影相机不会。
  • 一些不需要透视的场景你可以选择使用正投影相机,比如整体预览一个中国地图的效果,或者一个2D可视化的效果。
javascript
// 构造函数格式
OrthographicCamera( left, right, top, bottom, near, far )
参数(属性)含义
left渲染空间的左边界
right渲染空间的右边界
top渲染空间的上边界
bottom渲染空间的下边界
nearnear属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。 默认值0.1
farfar属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到。 默认值2000

创建正投影相机

javascript
// 正投影相机
const width = window.innerWidth; //canvas画布宽度
const height = window.innerHeight; //canvas画布高度
const k = width / height; //canvas画布宽高比
const s = 600;//控制left, right, top, bottom范围大小
// 按canvas画布宽高比设置相机参数
const camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 8000);

camera.position.set(0, 2000, 0);//相机放在了y轴上
camera.lookAt(0, 0, 0);//指向坐标原点

注意.position位置和far参数的设置,确保你想看到的物体能够包含在far之内,超出far的不会渲染。

Canvas画布跟随窗口变化

正投影相机OrthographicCameraleftright属性受到canvas画布宽高比影响,所以需要随着canvas画布更新。

javascript
// Canvas画布跟随窗口变化
window.onresize = function () {
    const width = window.innerWidth; //canvas画布宽度
    const height = window.innerHeight; //canvas画布高度
    // 1. WebGL渲染器渲染的Cnavas画布尺寸更新
    renderer.setSize(width, height);
    // 2.1.更新相机参数
    const k = width / height; //canvas画布宽高比
    camera.left = -s*k;
    camera.right = s*k;
    // 2.2.相机的left, right, top, bottom属性变化了,通知threejs系统
    camera.updateProjectionMatrix();
};

包围盒Box3

所谓包围盒Box3,就是一个长方体空间,把模型的所有顶点数据包围在一个最小的长方体空间中,这个最小长方体空间就是该模型的包围盒Box3

包围盒Box3表示三维长方体包围区域,使用min和max两个属性来描述该包围区域,Box3的min和max属性值都是三维向量对象Vector3。

  • 描述包围盒区域.min.max
    • 描述一个长方体包围盒需要通过xyz坐标来表示,X范围[Xmin,Xmax],Y范围[Ymin,Ymax],Z范围[Zmin,Zmax]
    • .min属性值是Vector3(Xmin, Ymin, Zmin),.max属性值是Vector3(Xmax, Ymax, Zmax).
  • 计算模型最小包围盒.expandByObject()
    • 模型对象,比如mesh或group,作为.expandByObject()的参数,可以计算该模型的包围盒。
    • 可以通过.min.max属性查看模型的包围盒信息。
  • 包围盒尺寸.getSize()

返回包围盒具体的长宽高尺寸

  • 包围盒几何中心.getCenter()
javascript
const geometry = new THREE.BoxGeometry(50, 50, 50);
const material = new THREE.MeshLambertMaterial({
    color: 0x00ffff,
});
const mesh = new THREE.Mesh(geometry, material);

// 包围盒计算模型对象的大小和位置
const box3 = new THREE.Box3();
box3.expandByObject(mesh); // 计算模型包围盒
console.log('查看包围盒',box3);

const size = new THREE.Vector3();
box3.getSize(size); // 计算包围盒尺寸
console.log('模型包围盒尺寸',size);

const center = new THREE.Vector3();
box3.getCenter(center); // 计算包围盒中心坐标
console.log('模型中心坐标',center);

通过包围盒和正投影相机实现2D地图效果

  1. 获取河南边界轮廓边界经纬度坐标
javascript
import data from './data.js';// 河南省边界轮廓数据

const pointsArr = [];// 一组二维向量表示一个多边形轮廓坐标
data.forEach(function(e){
    // data坐标数据转化为Vector2构成的顶点数组
    const v2 = new THREE.Vector2(e[0],e[1])
    pointsArr.push(v2);
})
  1. 通过ShapeGeometry渲染河南省地图轮廓
javascript
// Shape表示一个平面多边形轮廓,参数是二维向量构成的数组pointsArr
const shape = new THREE.Shape(pointsArr);
// 多边形shape轮廓作为ShapeGeometry参数,生成一个多边形平面几何体
const geometry = new THREE.ShapeGeometry(shape);
const material = new THREE.MeshLambertMaterial({
    color: 0x00ffff,
    side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry, material);
  1. 使用正投影相机OrthographicCamera

默认情况下,你运行案例源码,可以看到河南地图并没有居中显示,在canvas画布上显示的效果也比较小。

如果你想地图全屏最大化居中显示,可以通过包围盒来辅助设置计算参数。

  1. 包围盒Box3计算模型的中心位置和尺寸
javascript
// 包围盒计算模型对象的大小和位置
const box3 = new THREE.Box3();
box3.expandByObject(mesh); // 计算模型包围盒
const size = new THREE.Vector3();
box3.getSize(size); // 计算包围盒尺寸
console.log('模型包围盒尺寸',size);
const center = new THREE.Vector3();
box3.getCenter(center); // 计算包围盒中心坐标
console.log('模型中心坐标',center);
  1. 通过包围盒计算数据重新调整相机参数,使地图以适合的大小居中显示
javascript
// 1.通过包围盒获得模型中心坐标
const x = 113.51,y = 33.88;

// 正投影相机
const width = window.innerWidth; //canvas画布宽度
const height = window.innerHeight; //canvas画布高度
const k = width / height; //canvas画布宽高比
// const s = 50; //控制left, right, top, bottom范围大小
// 2.根据包围盒大小调整s,包围盒y方向尺寸的一半左右
const s = 2.5;
const camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 8000);
// 3.地图平行于canvas画布
camera.position.set(x, y, 300);//与观察点x、y一样,地图平行与canvas画布
// 4.地图居中显示
camera.lookAt(x, y, 0);
  1. 如果使用了OrbitControls,需要设置.target.lookAt()参数是相同坐标。
javascript
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(x, y, 0); //与lookAt参数保持一致
controls.update(); //update()函数内会执行camera.lookAt(controls.target)
  1. 如果使用了辅助观察的坐标系,需要修改position
javascript
//辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(1000);
scene.add(axesHelper);
axesHelper.position.set(x, y, 0);

相机动画

直线运动

如果连续改变相机的位置.position,就可以获得一个动画效果。

javascript
// 渲染循环
function render() {
    camera.position.z -= 0.3;//相机直线运动动画
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

圆周运动

改变.position属性后,如果不执行.lookAt()方法,相机的观察方向默认不变。

如果你希望相机圆周运动的同时,改变相机视线方向,保持相机镜头始终指向坐标原点或其它位置,需要每次改变.position属性后,重新执行一遍.lookAt()方法

javascript
// 渲染循环
let angle = 0; //用于圆周运动计算的角度值
const R = 100; //相机圆周运动的半径
function render() {
    angle += 0.01;
    // 相机y坐标不变,在XOZ平面上做圆周运动
    camera.position.x = R * Math.cos(angle);
    camera.position.z = R * Math.sin(angle);
    // .position改变,重新执行lookAt(0,0,0)计算相机视线方向
    camera.lookAt(0,0,0);
    requestAnimationFrame(render);
}
render();

旋转渲染结果(.up相机上方向)

管道漫游案例

  1. 通过曲线路径生成管道TubeGeometry
javascript
// 三维样条曲线
const path = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-50, 20, 90),
    new THREE.Vector3(-10, 40, 40),
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(60, -60, 0),
    new THREE.Vector3(90, -40, 60),
    new THREE.Vector3(120, 30, 30),
]);
// 样条曲线path作为TubeGeometry参数生成管道
const geometry = new THREE.TubeGeometry(path, 200, 5, 30);
  1. 通过纹理对象Texture阵列给管道贴图
javascript
const texLoader = new THREE.TextureLoader(); 
const texture = texLoader.load('./diffuse.jpg');//纹理贴图
texture.wrapS = THREE.RepeatWrapping;//UV坐标U方向阵列模式
texture.repeat.x = 10;//纹理沿着管道方向阵列(UV坐标U方向)
const material = new THREE.MeshLambertMaterial({
    map: texture,
    side: THREE.DoubleSide, //双面显示看到管道内壁
});
const mesh = new THREE.Mesh(geometry, material);
  1. 获得运动轨迹上的顶点

通过曲线的.getSpacedPoints()方法可以从轨迹线上均匀的获得一系列顶点坐标数据,然后你可以用这些轨迹线上顶点坐标设置相机位置。

javascript
// 从曲线轨迹线上等间距获取一定数量点坐标
const pointsArr = path.getSpacedPoints(500);
  1. 根据获取的顶点数据循环设置相机位置
  2. 设置相机视线

.lookAt()设置相机观察点为当前点pointsArr[i]的下一个点pointsArr[i + 1],使相机视线和曲线上当前点切线重合。

  1. 改变视场角度fov调节渲染效果
javascript
// fov:90度
const camera = new THREE.PerspectiveCamera(90, width / height, 1, 3000);

相机动画:

javascript
// 渲染循环
let i = 0; //在渲染循环中累加变化
function render() {
    if (i < pointsArr.length - 1) {
        // 相机位置设置在当前点位置
        camera.position.copy(pointsArr[i]);
        // 曲线上当前点pointsArr[i]和下一个点pointsArr[i+1]近似模拟当前点曲线切线
        // 设置相机观察点为当前点的下一个点,相机视线和当前点曲线切线重合
        camera.lookAt(pointsArr[i + 1]);
        i += 1; //调节速度
    } else {
        i = 0
    }
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

OrbitControls旋转缩放限制

相机控件MapControls

相机控件类型旋转缩放平移适用场景
OrbitControls拖动鼠标左键滚动鼠标中键拖动鼠标右键3D 场景
MapControls拖动鼠标右键滚动鼠标中键拖动鼠标左键2D 地图或平面图
javascript
import {
  MapControls
} from 'three/addons/controls/OrbitControls.js';

const controls = new MapControls(camera, renderer.domElement);

controls.addEventListener('change', function () {
  // 鼠标右键旋转时候,查看.position变化
  // 鼠标左键拖动的时候,查看.position、.target的位置会变化
  console.log('camera.position',camera.position);
  console.log('controls.target',controls.target);
});