核心是将鼠标屏幕坐标转为世界空间射线,再计算该射线到各Gizmo轴线段的最短距离;若距离小于容忍半径且交点参数t∈[0,1],则判定点击该轴。需手动构造射线、统一右手系、处理Y轴翻转,并确保axisWorld为单位向量。

如何用射线检测判断鼠标点击了哪个Gizmo轴
核心是把鼠标屏幕坐标转成世界空间的射线,再与每条Gizmo轴(视为无限长直线或带长度的线段)做相交测试。不能直接用 glm::unProject 算单个点——得生成方向向量构成射线:origin 是近裁剪面中心点,direction 是归一化后的视线方向。
常见错误是只用 glm::unProject(x, y, 0.0) 和 glm::unProject(x, y, 1.0) 得到两点后直接当线段处理,但没考虑视口翻转(尤其是 Y 轴朝下)、深度缓冲精度导致的远点漂移。稳妥做法是手动构造射线:
- 用
glm::inverse(view * projection)变换归一化设备坐标(2*x/width-1, 1-2*y/height, 0)得到近点,同理算出远点,再减法得方向 - Gizmo 各轴(X/Y/Z)用局部坐标系单位向量(如
glm::vec3(1,0,0))乘以变换矩阵转到世界空间,再平移到 Gizmo 位置gizmoPos - 对每条轴线段(例如从
gizmoPos到gizmoPos + axisWorld * length),调用线段-射线最近点距离算法(如glm::distance配合参数化求解),而非简单求交——因为用户点击的是“轴的视觉区域”,有容忍半径
为什么用线段-射线距离而非严格相交
真实交互中,Gizmo 轴是细长圆柱体(或带宽的线段),用户不可能精确点中数学意义上的直线。硬算射线与无限直线交点会因浮点误差和视角倾斜导致大量误判。
更可靠的做法是:对每个候选轴,计算射线到该轴线段的最短距离 d,若 d (如 5~10 像素对应的世界空间距离),再检查交点是否落在轴线段范围内、且在摄像机前方。
立即学习“C++免费学习笔记(深入)”;
-
pickRadius必须随 Gizmo 到摄像机距离缩放——离得远时视觉宽度变小,固定像素半径会导致远处无法选中;建议用worldPickRadius = screenPickRadius * (distanceToCamera / nearPlane) - 避免用
glm::intersectRayTriangle去模拟圆柱——开销大且不必要;线段+距离阈值已足够稳定 - Z 轴常被 X/Y 轴遮挡,需按深度排序候选结果,取最近的有效轴
如何计算鼠标点击在选中轴上的相对偏移比例
一旦确定点击了某条轴(比如 X 轴),需要知道“沿该轴拖动时,位移应映射为多少世界单位”——这取决于鼠标在轴上的投影位置,即从点击点向轴作垂足,再将垂足映射到轴的局部参数空间 [0, 1]。
关键不是屏幕坐标差,而是世界空间中沿轴方向的标量投影:
- 设轴世界端点为
A = gizmoPos,B = gizmoPos + axisWorld * length,射线最近点为P - 计算
t = glm::dot(P - A, axisWorld) / glm::dot(axisWorld, axisWorld),这就是归一化偏移(t ∈ [0,1]表示在线段内,t 或 <code>t > 1表示超出范围) - 注意:
axisWorld必须是单位向量,否则分母不是 1;若非单位,改用glm::length(axisWorld)归一化后再算 - 这个
t值可直接用于拖拽反馈(如显示当前缩放比例、高亮轴段),也可作为后续位移插值的基准
OpenGL/DirectX 下容易忽略的坐标系陷阱
左手系(DirectX)和右手系(OpenGL 默认)会影响 view 矩阵构造和射线方向符号。若混用 glm::lookAt(右手系)和 DirectX 投影矩阵,射线会反向。
- 统一用右手系:投影矩阵用
glm::perspective+glm::lookAt,z值范围设为[0.0, 1.0](匹配 OpenGL 深度范围) - 若必须用 DirectX 左手系,改用
glm::perspectiveLH_ZO并确保view矩阵 z 轴朝前(非朝后) - 窗口 Y 坐标翻转:GLFW 默认
y=0在底部,但多数 GUI 库(如 imgui)和鼠标事件返回的y是顶部为 0,必须先做y = height - 1 - y - 多视口或 VR 场景下,每个眼的
projection和view不同,射线必须用对应眼的矩阵重算
轴的视觉长度、拾取容差、坐标系一致性,这三个点任一出错都会让 Gizmo 交互显得“不跟手”——问题往往不出在算法本身,而在矩阵链路中某个环节的隐式假设被打破了。


















