本章主要学习知识点
- 了解正投相机概念
- 掌握如何监听窗口变化而同步更新Canavs更新
- 学会为模型添加可视化包围盒
- 练习创建地图模型,并可视化包围盒
正投影相机
正投影相机(OrthographicCamera)可以理解为一种「无透视」的观察方式,类似工程制图或平面设计中的等比例投影。
使用场景
- 工程制图:CAD设计、机械零件图等需要精确尺寸的场景
- 2.5D游戏:如俯视角游戏,保持物体比例不变
- UI界面:需要固定比例显示的3D控件或文字
与透视相机的区别
| 特性 | 正投影相机 | 透视相机 |
|---|---|---|
| 近大远小效果 | 无 | 有(模拟人眼) |
| 适用场景 | 工程、平面设计 | 游戏、真实场景 |
| 参数意义 | 定义长方体范围 | 定义视场角(FOV) |
正投影相机通过6个参数定义一个长方体范围(视景体),只有这个范围内的物体会被渲染
const cameraConfig = {
fov: 1,
far: 8000,
}
const k = window.innerWidth / window.innerHeight; // 窗口宽高比
const s = 10;
// 正投影相机
camera = new THREE.OrthographicCamera(-s*k, s*k, s, -s, cameraConfig.fov, cameraConfig.far); // 左 右 上 下 近 远
camera.position.set(200, 200, 600); // 设置相机位置
camera.lookAt(0,0,0); // 设置相机方向(指向的场景对象)
正投影Canvas尺寸变化
正投影相机(OrthographicCamera)的Canvas尺寸变化处理,可以理解为「保持画面比例不变,动态调整观察范围」。
如果我们不处理尺寸变化,那么当浏览器窗口或Canvas画布尺寸变化时,会导致画面拉伸或挤压,内容显示不全等问题
通过监听窗口尺寸的变化,来动态修改相机参数
window.addEventListener('resize', () => {
// 更新摄像头宽高比
camera.aspect = window.innerWidth / window.innerHeight;
// 设置相机左边界
camera.left = -s * camera.aspect;
// 设置相机右边界
camera.right = s * camera.aspect;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize( window.innerWidth, window.innerHeight );
// 设置渲染器的像素比
renderer.setPixelRatio( window.devicePixelRatio );
})
这里通过 aspect宽高比,保证画面比例始终与窗口一致,避免拉伸
包围盒
包围盒可以理解为给3D模型「套一个虚拟盒子」,用来快速判断模型的位置、碰撞等交互逻辑。
想象我们网购了一个形状不规则的玩具,快递盒却是一个标准的长方体纸箱。这个纸箱就是「包围盒」——它包裹着物体,简化了复杂形状的计算。
常见包围盒类型
-
AABB(轴对齐包围盒)
- 盒子始终与坐标轴对齐,不随物体旋转
- Three.js 实现:
THREE.Box3类,通过模型顶点坐标计算最小/最大边界
-
OBB(有向包围盒)
- 盒子方向随模型旋转,包裹更紧密(但计算复杂)
- Three.js 实现:需引入
OBB.js扩展库
适用场景
-
碰撞检测
- 游戏角色与障碍物接触时,先检测包围盒是否重叠,再精确计算
-
物体交互
- 鼠标点击模型时,通过射线与包围盒的交集判断选中对象
-
性能优化
- 相机视锥体剔除:只渲染视锥体内的包围盒对应物体
实践一下
expandByObject用于计算包围盒
const box3 = new THREE.Box3();
box3.expandByObject(child);
// 获取包围盒尺寸
const scale = new THREE.Vector3();
box3.getSize(scale)
// 获取包围盒中心
const center = new THREE.Vector3();
box3.getCenter(center)
// 使用box盒子包裹模型,具象化包围盒
var mesh = new THREE.Mesh(
new THREE.BoxGeometry(box3.max.x - box3.min.x,
box3.max.y - box3.min.y,
box3.max.z - box3.min.z),
new THREE.MeshBasicMaterial({ color: "rgba(255,255,255,.1)", transparent: true, opacity: 0.5 })
);
mesh.position.copy(center)
scene.add(mesh)
// 构建包围盒
const box = new THREE.BoxHelper(child,'deepskyblue')
scene.add(box)
地图包围盒
地图包围盒可以理解为「给3D地图或地图元素套一个虚拟立方体盒子」,用来快速控制地图的显示范围、碰撞检测等操作。
实际使用场景
- 自适应显示范围
当用户缩放或平移地图时,根据包围盒动态调整相机参数,保证地图内容始终居中且完整显示
- 碰撞检测
游戏角色在地图上移动时,通过检测角色包围盒与建筑包围盒是否重叠,实现「穿墙」或「绕行」的逻辑
实践一下
网上找个json格式的省份地图数据,拿到相应的坐标
const mapDatas = fujianMap.features[0].geometry.coordinates[0][0]
const pointsArr = [];
mapDatas.forEach( e => {
// 将mapDatas中的经纬度转换为Vector2构成的顶点数组
const v2 = new THREE.Vector2(e[0], e[1])
pointsArr.push(v2)
})
创建一个模型,导入坐标组
const shape = new THREE.Shape(pointsArr)
const geometry = new THREE.ShapeGeometry(shape)
geometry.center()
const material = new THREE.MeshBasicMaterial({
color: 'deeppink',
opacity: 0.5
})
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
这里我们还是使用expandByObject来查看包围盒
const box3 = new THREE.Box3()
// 扩展box3以包含mesh
box3.expandByObject(mesh)
// 获取box3的大小
const size = box3.getSize(new THREE.Vector3())
// 获取box3的中心点
const center = box3.getCenter(new THREE.Vector3())
const box = new THREE.BoxHelper(mesh,'deepskyblue')
scene.add(box)