Three.js-硬要自学系列20 (正投影相机、Canvas尺寸变化、包围盒、地图包围盒)

417 阅读4分钟

本章主要学习知识点

  • 了解正投相机概念
  • 掌握如何监听窗口变化而同步更新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); // 设置相机方向(指向的场景对象)

image.png

正投影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)

345.gif

地图包围盒

地图包围盒可以理解为「给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)

image.png

这里我们还是使用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)

image.png

以上案例均可在案例中心查看体验

THREE 案例中心

image.png