很多軟件中(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
平移前后的邏輯示意:
-
縮放前鼠標像素位置對應一個場景空間位置
curScenePos
(為我們聚焦的位置); -
放大后,
curScenePos
不可見了,而鼠標像素位置對應了另一個場景空間位置nextScenePos
了; -
我們把
Camera.Position
移動(curScenePos
?-?nextScenePos
)向量,鼠標像素位置重新對應到了原本的curScenePos
; -
這樣就實現?了保持聚焦點位置不變的以鼠標點為中心縮放。
31.3.關鍵代碼
void ProcessMouseScroll(float?yoffset)
{
// ?scale by the mouse hover point if it's pixes is validQVector3D 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 pixesQVector3D nextPt;GetScenePoint(ViewerSetting::currentMousePos[0], ViewerSetting::currentMousePos[1], depth, nextPt);// ?move PositionPosition += (curPt - nextPt);}
}
思考
為什么在計算
nextPt
時的depth
參數要用此前curPt
對應的值呢?讀者可自行思考。
31.4.效果
效果視頻:https://www.bilibili.com/video/BV15zG3zzEgK/
也可在《課程視頻》中進行觀看,有詳細的講解~
學習!《從零開發一款三維CAD軟件(OpenGL/QT/C++)》課程上線啦
專注于圖形學(渲染和幾何算法)、數據處理、并行計算相關研究和研發,歡迎交流~
學習!《從零開發一款三維CAD軟件(OpenGL/QT/C++)》課程上線啦
系列課程已上線,詳細的視頻講解,打下扎實的圖形學基礎,歡迎大家觀看和支持~
往期文章:
-
GLViewer:添加ViewCube
-
學習!《從零開發一款三維CAD軟件(OpenGL/QT/C++)》課程上線啦
-
OpenGL模板緩沖:實現亮顯外輪廓效果
-
2025 想從事工業軟件開發要掌握哪些知識?
-
30.抗鋸齒(anti aliasing):使用OpenGL+QT開發三維CAD
-
MSAA抗鋸齒技術的不足和優化(PPAA)
-
相機:Camera原理講解(使用OpenGL+QT開發三維CAD)
-
開發三維CAD:實現框選和反選功能
-
圖形學:一分鐘看懂網格剖分原理(耳切法)
-
視圖立方體:ViewCube的繪制(使用OpenGL+QT開發三維CAD)