很多软件中(Auto CAD、ODA等)支持以鼠标点为中心进行放缩操作,有什么黑科技吗?
本章节为相机原理和实现的补充内容,支持鼠标放缩时以鼠标点为中心进行放缩。
对应视频课程已上线,欢迎观看和支持~
https://www.bilibili.com/cheese/play/ss168681371
学习!《从零开发一款三维CAD软件(OpenGL/QT/C++)》课程上线啦

31.相机:缩放时以鼠标点为中心
在三维软件中,以鼠标点为中心缩放可以做到保持聚焦点位置不变,达到视觉上的"中心点聚焦"。这种技术常用于电影镜头语言和游戏场景设计中,通过动态调整视角和物体大小,使用户始终关注特定区域。
31.1.思考和讨论
通常我们会通过鼠标滚轮事件实现场景缩放,而在场景中进行缩放时,会以相机当前的Position
(也就是视点)为观察位置进行缩放,通过滚轮滑动的方向和幅度来更新相机的Zoom
。
回顾
你应该还记得在
顶点着色器
中将坐标点转换为裁剪坐标
过程中需要经过modelMatrix
、viewMatrix
和projectionMatrix
的处理,而projectionMatrix
的构造与Zoom
有关系,当然还与近平面
、远平面
、宽高比
有关。
在不考虑鼠标位置进行缩放时,会以固定的Position
进行观察,而仅仅改变Zoom
的大小,这样会出现无论鼠标在场景中任何位置进行滚动,缩放行为都不会考虑鼠标位置,也就是不会考虑我们当前关注的位置。为了实现以鼠标点为中心的缩放,我们还需要更新相机的Position
来实现聚焦点的"固定",这也意味着我们需要同时更新viewMatrix
和projectionMatrix
。
想象和思考
在原理和实现讲解之前,我们先一起想象一下。
- 鼠标在
(curPx,curPy)
像素位置进行滚轮放大,当前视角下场景会放大,我们现在看到的范围更小了(也更清晰了),这也意味着(curPx,curPy)
像素原本对应的场景位置(curScenePos
)可能移出我们屏幕范围了!怎么样让它固定在
(curPx,curPy)
像素位置而不是移动呢?
在上述情景想象中,
-
观察矩阵
viewMatrix
没有任何变化(也就是观察空间中的效果没有变化); -
而由于
Zoom
的变化,透视平截头体
的范围变小了,可见的场景空间变小了(近平面
和远平面
尺寸变小了); -
(curPx,curPy)
像素原本对应的场景位置(
curScenePos
)可能已经不在透视平截头体
的范围内了,当然该像素现在对应到另一个场景位置(nextScenePos
)了;
我们需要把(curPx,curPy)
像素位置固定在对应的场景位置上,那么移动相机的Position
就好了,让它靠近原本聚焦点(对应的场景位置)。是的,其实逻辑挺简单的,至于要移动多少?那就移动curScenePos
- nextScenePos
。
nextScenePos
怎么计算?以相同的(curPx,curPy)
和depth
来计算场景空间坐标系对应位置就好了。
31.2.原理
我们先不考虑透视投影或者正交投影的概念(这和当前的逻辑原理没有什么关系)。在Zoom
更新后,平截头体
的范围变化了,(curPx,curPy)
屏幕像素对应了新的场景位置(nextScenePos
),我们只需要(通过平移Position
)把这个位置"平移"到原本对应的场景位置(curScenePos
),这样聚焦的目标就"固定"住了。
图:偏移Position,实现鼠标点的聚焦
上图展示了缩放前后、Position
平移前后的逻辑示意:
-
缩放前鼠标像素位置对应一个场景空间位置
curScenePos
(为我们聚焦的位置); -
放大后,
curScenePos
不可见了,而鼠标像素位置对应了另一个场景空间位置nextScenePos
了; -
我们把
Camera.Position
移动(curScenePos
-nextScenePos
)向量,鼠标像素位置重新对应到了原本的curScenePos
; -
这样就实现了保持聚焦点位置不变的以鼠标点为中心缩放。
31.3.关键代码
cpp
void ProcessMouseScroll(float yoffset)
{
// scale by the mouse hover point if it's pixes is valid
QVector3D curPt;
float depth;
bool hoverValid = ViewerSetting::mouseScaleByCenter && GetScenePoint(ViewerSetting::currentMousePos[0], ViewerSetting::currentMousePos[1], curPt, depth);
// modify zoom
float downValue = 1.0f;
float upValue = 89.f/*45.0f*/;
Zoom -= (float)yoffset;
if (Zoom < downValue)
Zoom = downValue;
if (Zoom > upValue)
Zoom = upValue;
if (hoverValid)
{
// cal point of current pixes
QVector3D nextPt;
GetScenePoint(ViewerSetting::currentMousePos[0], ViewerSetting::currentMousePos[1], depth, nextPt);
// move Position
Position += (curPt - nextPt);
}
}
思考
为什么在计算
nextPt
时的depth
参数要用此前curPt
对应的值呢?读者可自行思考。
31.4.效果
效果视频:https://www.bilibili.com/video/BV15zG3zzEgK/
也可在《课程视频》中进行观看,有详细的讲解~
学习!《从零开发一款三维CAD软件(OpenGL/QT/C++)》课程上线啦
专注于图形学(渲染和几何算法)、数据处理、并行计算相关研究和研发,欢迎交流~
学习!《从零开发一款三维CAD软件(OpenGL/QT/C++)》课程上线啦
系列课程已上线,详细的视频讲解,打下扎实的图形学基础,欢迎大家观看和支持~
往期文章:
