three.js 模型适应窗口调整显示大小代码解读

直接看效果

调整前 调整后

调整后模型可以全部展示,不会超出显示区域,有一些比较小的模型也是可以放大显示的

直接贴代码

js 复制代码
function fitOnScreen() {
  const box = new THREE.Box3().setFromObject(bldg);
  const boxSize = box.getSize(new THREE.Vector3()).length();
  const boxCenter = box.getCenter(new THREE.Vector3());

  frameArea(boxSize * 1.5, boxSize, boxCenter);

  controls.maxDistance = boxSize * 10;
  controls.target.copy(boxCenter);
  controls.update();
}

function frameArea(sizeToFitOnScreen, boxSize, boxCenter) {
  const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
  const halfFovY = THREE.MathUtils.degToRad(camera.fov * 0.5);
  const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);

  const direction = new THREE.Vector3()
    .subVectors(camera.position, boxCenter)
    .multiply(new THREE.Vector3(1, 0, 1))
    .normalize();

  camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));
  
  camera.near = boxSize / 100;
  camera.far = boxSize * 100;
  camera.updateProjectionMatrix();
  camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
}

首页声明,上面代码是我在three.js官网demo里面复制,这里只是做代码解读

上面的代码是一个用于调整相机位置以适应场景中对象的函数。

  • 函数fitOnScreen用于计算场景中对象的包围盒大小,并根据大小和中心点调整相机和控制器的位置。
  • 函数frameArea用于根据给定的尺寸,计算相机的位置和截锥体的近平面和远平面。

fitOnScreen

1. 使用Box3对象获取场景中所有对象的包围盒。

js 复制代码
  // 计算包围盒大小及中心点
  const box = new THREE.Box3().setFromObject(scene);

  // 添加包围盒的辅助对象
  const boxHelper = new THREE.BoxHelper(bldg, 0xffff00);
  scene.add(boxHelper);

2. 计算包围盒的尺寸和中心点

js 复制代码
  const boxSize = box.getSize(new THREE.Vector3()).length();
  const boxCenter = box.getCenter(new THREE.Vector3());

getSize 函数用于获取物体的尺寸(即包围盒的宽度、高度和深度),并返回一个新的 Vector3 向量,向量的三个分量分别表示包围盒在X、Y和Z轴上的尺寸。

然后,.length() 函数用于计算向量的长度(模)。对于 Vector3 向量,长度可以通过计算其三个分量的平方和的平方根来获取。即 length = √(x^2 + y^2 + z^2)

在给定的代码中,getSize(new THREE.Vector3()) 返回的是一个 Vector3 向量,表示包围盒的尺寸。然后,.length() 函数应用于该向量,计算并返回包围盒尺寸的长度,即包围盒的对角线的长度。这个长度可以用于表示包围盒的大小或用于其他计算任务。

对于一些gemoetry可以使用其它方法处理

js 复制代码
geometry.computeBoundingBox();
let { min, max } = geometry.boundingBox;
// 获取物体的高度差
let uHeight = max.y - min.y;
let uWidth = max.y - min.x;

3. 调用frameArea函数,传入包围盒的尺寸以及中心点。

这里先不展开解释

4. 设置控制器的maxDistance属性,限制相机与场景中心的最大距离。

controls.maxDistance = boxSize * 10;

通俗点,就是不能让用户把场景模型缩得太小

5. 将控制器的target属性设置为包围盒的中心点,并且更新控制器。

js 复制代码
  // 控制器将以 boxCenter 为中心进行旋转和缩放操作。
  controls.target.copy(boxCenter);
  // 摄像机的变换发生任何手动改变后调用
  controls.update();

frameArea

传入三个参数

  • sizeToFitOnScreen 需要适应的屏幕大小
  • boxSize 模型大小
  • boxCenter 模型中心坐标

1. 根据计算出的尺寸,将其一半设置为适应屏幕的一半尺寸

2. 使用相机的垂直视角一半以及尺寸计算相机与目标之间的距离

js 复制代码
  // 一半,看下面
  const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
  // 角度转弧度
  const halfFovY = THREE.MathUtils.degToRad(camera.fov * 0.5);
  // 求得相机到模型的距离
  const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);

3. 计算一个单位向量,指向相机在xz平面上的方向。

js 复制代码
  const direction = new THREE.Vector3()
    .subVectors(camera.position, boxCenter)
    .multiply(new THREE.Vector3(1, 0, 1))
    .normalize();
  1. new THREE.Vector3()创建一个新的三维向量对象
  2. .subVectors(camera.position, boxCenter) 计算两个向量的差值,即从包围盒中心指向相机位置的向量
  1. .multiply(new THREE.Vector3(1, 0, 1)) 其实就是把y轴置零,x z轴不变
  2. .normalize(); 得到一个表示方向的单位向量

向量归一化,就是等比例缩放向量的xyz三个分量,缩放到向量长度.length()为1。

js 复制代码
const dir = new THREE.Vector3(1, 1, 0);
dir.normalize(); //向量归一化
console.log('dir',dir);
//Vector3(√2/2, √2/2, 0)   Vector3(0.707, 0.707, 0)

4. 根据距离和中心点,设置相机的位置

camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));

  1. direction.multiplyScalar(distance) 单位向量乘以距离
  2. add(boxCenter) 起点平移到boxCenter的位置
  3. camera.position.copy()最后,我们将相机的位置设置为新计算得到的向量。使用 copy 函数可以将新向量的值复制到 camera.position 中,从而将相机移动到新的位置。

5. 计算适当的近平面和远平面的值

js 复制代码
  camera.near = boxSize / 100;
  camera.far = boxSize * 100;

6. 更新相机的投影矩阵

camera.updateProjectionMatrix(); 更新摄像机投影矩阵。在任何参数被改变以后必须被调用。

7. 将相机的视角对准包围盒的中心。

camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z); 外层控制器调整target也会调整相机焦点的

总结

其实也没什么好总结的,个人理解,这代码只要针对加载的模型有大有小,要动态调整相机及控制器,达到比较好的显示效果。

相关推荐
大圣编程33 分钟前
Python中continue语句的用法是什么?
开发语言·前端·python
yuhaiqiang34 分钟前
随手 vibecoding 的浏览器插件已经 6000 多次下载,聊聊他的产品设计
前端·后端·面试
之歆1 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
再吃一根胡萝卜2 小时前
如何把小米 MiMo 接入 CodeBuddy,打造私有 Agent
前端
负责的蛋挞3 小时前
异步HttpModule的实现方式
java·服务器·前端
丹宇码农6 小时前
把 HLS 字幕玩出花:zwPlayer 如何让 M3U8 视频支持全文搜索、翻译与码率自适应
前端·javascript·音视频·hls·视频播放器
2501_943782356 小时前
【共创季稿事节】猜数字游戏:二分法思维与交互式反馈
前端·游戏·microsoft·harmonyos·鸿蒙·鸿蒙系统
GV191rLvq6 小时前
基于Socket实现的最简单的Web服务器【ASP.NET原理分析】
服务器·前端·asp.net
吠品6 小时前
LangChain 里 tool_call_id 为空?一次 MCP 工具集成的排查记录
前端