十、相机基础
正投影相机
- 正投影相机的长方体可视化空间和透视投影
PerspectiveCamera视锥体相似,只是形状不同。 - 正投影相机
OrthographicCamera和透视投影相机PerspectiveCamera的区别就是,透视投影可以模拟人眼观察世界的视觉效果(没有了近大远小的效果),正投影相机不会。 - 一些不需要透视的场景你可以选择使用正投影相机,比如整体预览一个中国地图的效果,或者一个2D可视化的效果。
javascript
// 构造函数格式
OrthographicCamera( left, right, top, bottom, near, far )| 参数(属性) | 含义 |
|---|---|
| left | 渲染空间的左边界 |
| right | 渲染空间的右边界 |
| top | 渲染空间的上边界 |
| bottom | 渲染空间的下边界 |
| near | near属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。 默认值0.1 |
| far | far属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到。 默认值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画布跟随窗口变化
正投影相机OrthographicCamera的left、right属性受到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属性查看模型的包围盒信息。
- 模型对象,比如mesh或group,作为
- 包围盒尺寸
.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地图效果
- 获取河南边界轮廓边界经纬度坐标
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);
})- 通过
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);- 使用正投影相机
OrthographicCamera
默认情况下,你运行案例源码,可以看到河南地图并没有居中显示,在canvas画布上显示的效果也比较小。
如果你想地图全屏最大化居中显示,可以通过包围盒来辅助设置计算参数。
- 包围盒
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);- 通过包围盒计算数据重新调整相机参数,使地图以适合的大小居中显示
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);- 如果使用了
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)- 如果使用了辅助观察的坐标系,需要修改
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相机上方向)
管道漫游案例
- 通过曲线路径生成管道
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);- 通过纹理对象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);- 获得运动轨迹上的顶点
通过曲线的.getSpacedPoints()方法可以从轨迹线上均匀的获得一系列顶点坐标数据,然后你可以用这些轨迹线上顶点坐标设置相机位置。
javascript
// 从曲线轨迹线上等间距获取一定数量点坐标
const pointsArr = path.getSpacedPoints(500);- 根据获取的顶点数据循环设置相机位置
- 设置相机视线
.lookAt()设置相机观察点为当前点pointsArr[i]的下一个点pointsArr[i + 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);
});
