大家在开发 3D 应用时,是不是经常遇到这样的场景:想从不同角度观察模型,却只能反复调整相机角度,来回切换视角特别麻烦?尤其是做建模、BIM 或者 CAD 相关项目时,单视角简直是 “灾难”—— 前一秒还在看整体结构,下一秒想检查细节就得重新调整,效率低到想哭。
最近我就踩了这个坑,于是索性基于 Three.js 搞了个多视图切换功能,能同时显示透视图、剖面图、顶视图和前视图,鼠标移到哪个视图就能直接操作哪个,用起来那叫一个丝滑!今天就来分享下实现思路,新手朋友可以抄作业,老司机也欢迎交流优化方案~
先看看最终效果:多视图到底香在哪?
先上张效果图(大家可以脑补下动态切换的画面):

- 透视图(左上):看整体 3D 效果,自由旋转缩放
- 剖面图(右上):切个剖面看内部结构,建筑模型必备
- 顶视图(左下):上帝视角看平面布局
- 前视图(右下):正前方观察立面细节
最方便的是视图可以自由切换显示状态:比如只看透视图 + 顶视图,或者同时开启四个视图,布局会自动调整,鼠标移到哪个视图,控制器就自动激活哪个,操作完全不冲突~
核心实现思路:多视图的 “幕后功臣”
实现多视图的核心是 “多相机 + 视口隔离 + 动态控制器”,听起来复杂,拆开来其实很简单:
1. 多相机管理:不同视角各司其职
Three.js 里相机分两种:透视相机(PerspectiveCamera)适合看 3D 透视效果(透视图、剖面图用这个),正交相机(OrthographicCamera)没有近大远小,适合顶视图、前视图这种平面观察。
我初始化了 4 个相机,分别对应四个视图:
// 透视图相机(透视相机)
this.camera1 = config.camera1;
// 剖面图相机(透视相机,方便看剖面细节)
this.camera2 = new THREE.PerspectiveCamera(
this.camera1.fov,
this.camera1.aspect,
this.camera1.near,
1000
);
// 顶视图相机(正交相机,平面无透视)
this.camera3 = new THREE.OrthographicCamera(
-this.viewSize, this.viewSize,
this.viewSize, -this.viewSize,
0.1, 1000
);
// 前视图相机(正交相机)
this.camera4 = new THREE.OrthographicCamera(...);
每个相机都设置了固定的初始位置和目标点,比如顶视图相机放在模型正上方,永远看向原点,保证平面观察角度正确。
2. 视图切换:用 toggle 实现自由显隐
用户需要能自由开关视图,所以搞了个 toggleView 方法,用 Set 存储当前可见的视图:
// 切换视图显示状态
toggleView(view) {
if (this.visibleViews.has(view)) {
this.visibleViews.delete(view); // 隐藏视图
} else {
this.visibleViews.add(view); // 显示视图
}
// 透视图必须始终可见,不然就没主视角了
this.visibleViews.add(1);
// 更新布局和渲染
this.updateViewVisibility();
this.onWindowResize();
}
HTML 里加几个按钮绑定这个方法,点击就能切换,还能加个 “active” 样式提示当前状态,用户体验拉满~
3. 视口渲染:让每个视图 “各占一块地”
最关键的一步来了:怎么让多个相机的画面同时显示在画布上?答案是 setViewport + setScissor。
简单说,就是把画布分成几块,每个视图只在自己的 “地盘” 里渲染:
renderViews() {
const width = this.renderer.domElement.width;
const height = this.renderer.domElement.height;
const viewCount = this.visibleViews.size;
// 清除画布
this.renderer.setViewport(0, 0, width, height);
this.renderer.clear();
switch (viewCount) {
case 1: // 仅透视图,占满整个画布
this.renderer.setViewport(0, 0, width, height);
this.renderer.setScissor(0, 0, width, height);
this.renderer.render(this.scene, this.camera1);
break;
case 2: // 左半透视图,右半其他视图
// 透视图渲染
this.renderer.setViewport(0, 0, width/2, height);
this.renderer.setScissor(0, 0, width/2, height);
this.renderer.render(this.scene, this.camera1);
// 右侧视图渲染(剖面图/顶视图/前视图)
this.renderer.setViewport(width/2, 0, width/2, height);
this.renderer.setScissor(width/2, 0, width/2, height);
if (this.visibleViews.has(2)) {
this.renderer.render(this.scene, this.camera2);
}
break;
// 3个视图、4个视图的布局逻辑类似...
}
}
这里有个坑:Three.js 的视口 Y 轴是向上的,而浏览器鼠标坐标 Y 轴是向下的,计算视图位置时一定要转换坐标,不然鼠标移到视图上会 “串位”!
4. 控制器激活:鼠标在哪,操作权就在哪
多视图的精髓是 “所见即所得”—— 鼠标移到哪个视图,就能直接操作哪个视图的相机。实现起来也不难:
- 监听鼠标移动事件,判断当前鼠标在哪个视图区域(用
getViewFromPosition方法); - 根据当前视图,启用对应的控制器,禁用其他控制器。
onMouseMove(event) {
// 计算鼠标在画布中的坐标
const rect = this.renderer.domElement.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// 判断当前鼠标所在视图
const newView = this.getViewFromPosition(x, y);
// 切换控制器激活状态
if (newView !== this.activeView && this.visibleViews.has(newView)) {
this.activeView = newView;
// 启用当前视图控制器,禁用其他
this.controls1.enabled = this.activeView === 1;
this.controls2.enabled = this.activeView === 2 && this.visibleViews.has(2);
// ...其他控制器
}
}
这样鼠标移到哪个视图,哪个视图的旋转、缩放就会响应,操作起来就像在操作独立的 3D 窗口,完全不冲突~
踩坑记录:这些细节让我调试到脱发
-
正交相机比例问题:窗口 resize 时,正交相机的 left/right/top/bottom 必须重新计算,不然视图会拉伸变形,记得在
onWindowResize里更新投影矩阵! -
剖切功能冲突:剖面图需要剖切平面,但透视图不需要,所以要动态开关
renderer.localClippingEnabled,避免剖切影响其他视图。 -
控制器 dispose 问题:切换视图时如果不 dispose 旧控制器,会导致内存泄漏和事件冲突,一定要在初始化新控制器前调用
controls.dispose()。
最后:互动时间到!
这个多视图功能我已经用在自己的 BIM 模型查看器里了,确实比单视图效率高很多~ 大家在开发 3D 应用时,有没有遇到过视图相关的痛点?或者有更好的实现方案?
比如:
- 你们觉得多视图用快捷键切换方便,还是鼠标点击方便?
- 有没有必要加个 “视图联动” 功能(拖动一个视图,其他视图同步更新)?
欢迎在评论区交流,代码里的核心逻辑都贴出来了,完整代码可以根据思路自己补全,有问题随时问我哦~ 觉得有用的话别忘了点赞收藏,下次开发多视图直接抄作业!
Three.js实现多视图切换功能分享

被折叠的 条评论
为什么被折叠?



