Math 三维透视图';抓斗';使用DirectX进行平移

Math 三维透视图';抓斗';使用DirectX进行平移,math,3d,directx,direct3d,Math,3d,Directx,Direct3d,我正在我们软件的3D视图中实现一个pan工具,该工具的工作原理与Photoshop或Acrobat Reader的抓取工具非常相似。也就是说,当鼠标移动时,用户用鼠标抓住的点(单击并按住,然后移动鼠标)保持在鼠标光标下 这是一个常见的范例,也是一个以前被问及的范例。是的,我一直在读这篇文章。(它没有解释许多代码示例的变量等,但通过阅读本文,我认为我理解了这项技术。)但是,我有一些实现问题,因为我的3D环境的导航设置与那些文章完全不同,我正在寻求一些指导 我的技术——这可能有根本性的缺陷,所以请这

我正在我们软件的3D视图中实现一个pan工具,该工具的工作原理与Photoshop或Acrobat Reader的抓取工具非常相似。也就是说,当鼠标移动时,用户用鼠标抓住的点(单击并按住,然后移动鼠标)保持在鼠标光标下

这是一个常见的范例,也是一个以前被问及的范例。是的,我一直在读这篇文章。(它没有解释许多代码示例的变量等,但通过阅读本文,我认为我理解了这项技术。)但是,我有一些实现问题,因为我的3D环境的导航设置与那些文章完全不同,我正在寻求一些指导

我的技术——这可能有根本性的缺陷,所以请这么说——是:

  • 场景“摄影机”存储为两个
    D3DXVECTOR3
    点:眼睛位置和一个观察点。视图矩阵是使用D3DXMatrixLookAtLH构建的,如下所示:

    const D3DXVECTOR3 oUpVector(0.0f, 1.0f, 0.0f); // Keep up "up", always.
    D3DXMatrixLookAtLH(&m_oViewMatrix, &m_oEyePos, &m_oLook, &oUpVector);
    
  • 当按下鼠标按钮时,通过该像素发射光线并找到:单击的像素的坐标(在未投影的场景/世界空间中);射线与近平面的交点;近平面点和对象之间的距离,即这两个点之间的长度。存储此和鼠标位置,以及原始导航(眼睛和外观)

    Get3DPositionAtMouse
    是一种已知的ok方法,可在鼠标下拾取3D坐标
    CalculateRayFromPixel
    是一种已知的ok方法,它接受屏幕空间鼠标坐标并投射光线,并使用近平面(Z=0)处的光线交点和归一化光线向量填充其他两个参数

  • 当鼠标移动时,在新位置投射另一条光线,但使用旧(原始)视图矩阵。(感谢下面的Nico指出这一点。)通过将光线从近平面延伸到近平面来计算对象的位置对象和近平面之间的距离(这样,原始对象和新对象点应该与近平面平行。)将眼睛和视线坐标移动这么多。“眼睛”和“外观”是根据其原始(平移开始时)值设置的,不同之处在于原始鼠标位置和新鼠标位置。这是为了减少由于鼠标移动时的颗粒(整数)像素移动而增加或减少的任何精度损失,即它每次都计算导航的整体差异

    // Set navigation back to original (as it was when started panning) and cast a ray for the mouse
    m_oEyePos = m_oPanOriginalEyePos;
    m_oLook = m_oPanOriginalLook;
    UpdateView();
    D3DXVECTOR3 oRayVector;
    D3DXVECTOR3 oNewPlaneZPos;
    CalculateRayFromPixel(roMousePos, oNewPlaneZPos, oRayVector);
    
    // Now intersect that ray (ray through the mouse pixel, using the original navigation)
    // to hit the plane the object is in.  Function uses a "line", so start at near plane
    // and the line is of the length of the far plane away
    D3DXVECTOR3 oNew3DPos;
    D3DXPlaneIntersectLine(&oNew3DPos, &m_oPanObjectPlane, &oNewPlaneZPos, &(oRayVector * GetScene().GetFarPlane()));
    
    // The eye/look difference /should/ be as simple as:
    //  const D3DXVECTOR3 oDiff = (m_oPanOriginalObjectPos - oNew3DPos);
    // But that lags and is slow, ie the objects trail behind.  I don't know why.  What does
    // work is to scale the from-to difference by the distance from the camera relative to
    // the whole scene distance
    const double dDist = D3DXVec3Length(&(oNew3DPos - m_oPanOriginalEyePos));
    const double dTotalDist = GetScene().GetFarPlane() - GetScene().GetNearPlane();
    const D3DXVECTOR3 oDiff = (m_oPanOriginalObjectPos - oNew3DPos) * (1.0 + (dDist / dTotalDist));
    
    // Adjust the eye and look points by the same amount, so orthogonally changed
    m_oEyePos = m_oPanOriginalEyePos + oDiff;
    m_oLook = m_oPanOriginalLook + oDiff;
    
图解 此图是我实现此功能的工作示意图:

希望能比文本更简单地解释上述内容。您可以看到一个移动点,以及相机必须移动的位置,以使该点保持在相同的相对位置。单击的点(从摄影机到对象的光线)正好位于表示中心像素的正前方光线的右侧

问题 但是,正如你可能猜到的,这并不像我希望的那样有效。我想看到的是被点击的物体随着鼠标光标移动。我实际上看到的是,对象沿鼠标方向移动,但这还不够,即它没有将单击的点保持在光标下。其次,运动会闪烁和跳跃,有时会抖动20或30个像素,然后再闪烁回来。如果我用某个常量替换
oDiff
,则不会发生这种情况

如果您有任何想法或代码示例说明如何使用DirectX(D3DX、DX矩阵顺序等)实现此功能,我们将不胜感激

编辑
下面的评论员Nico指出,当使用鼠标光标的移动位置计算新位置时,我需要使用原始视图矩阵。这样做很有帮助,而且对象保持在鼠标位置附近。然而,这仍然不准确。我注意到,在屏幕中央,它是精确的;随着鼠标离中心越来越远,它会越来越多地离开。这似乎也随着物体的距离而改变。通过纯粹的“我不知道我在做什么”的猜测,我通过近/远平面的因子和对象的距离来缩放它,这使它非常接近鼠标光标,但仍然有几个像素的距离(例如,屏幕最边缘的1到30像素,这足以让它感觉不对劲)一个问题是计算新的3d位置。我不确定这是否是根本原因,但你可以试试。如果没有帮助,就发表评论

问题是偏移向量与Z线性平面不平行。这是因为两条光线不平行。因此,如果znear后面的长度相同,则端点到znear平面的距离不能相等

可以使用相交线定理计算偏移向量。如果zNearA和zNearB分别是znear平面与光线A和光线B的交点,则定理说明:

Length(original_position - cam_position) / Length(offset_vector) = Length(zNearA - cam_position) / Length(zNearB - zNearA)
因此

offset_vector = Length(original_position - cam_position) / Length(zNearA - cam_position) * (zNearB - zNearA)
然后可以确保在平行于Z轴平面的直线上移动


试试看是否有帮助。

下面是我解决这个问题的方法

float fieldOfView = 45.0f;

float halfFOV = (fieldOfView / 2.0f) * (DEGREES_TO_RADIANS);
float distanceToObject = // compute the world space distance from the camera to the object you want to pan
float projectionToWorldScale = distanceToObject * tan( halfFov );
Vector mouseDeltaInScreenSpace = // the delta mouse in pixels that we want to pan

Vector mouseDeltaInProjectionSpace = Vector( mouseDeltaInScreenSpace.x * 2 / windowPixelSizeX, mouseDeltaInScreenSpace.y * 2 / windowPixelSizeY ); // ( the "*2" is because the projection space is from -1 to 1)

// go from normalized device coordinate space to world space (at origin)
Vector cameraDelta = -mouseDeltaInProjectionSpace * projectionToWorldScale;

// now translate your camera by "cameraDelta".
请注意,这适用于视场apsect比率为1的情况,我认为如果垂直视场与水平视场不同,则必须将“比例”分解为单独的x和y分量


另外,你提到了一个“看”向量。我不确定我的数学需要如何改变,因为我的相机总是沿着z轴向下看。

谢谢Nico,但这导致眼睛和视线向量很快变成奇怪的值(最终是NAN)。从您的变量描述中,我不太确定它们是如何映射到我的变量的,所以这可能是错误的来源。但是,我现在已经将其编码为使用D3DXPLANE作为对象的平面,并与该平面相交,因此最终的3D位置肯定在
float fieldOfView = 45.0f;

float halfFOV = (fieldOfView / 2.0f) * (DEGREES_TO_RADIANS);
float distanceToObject = // compute the world space distance from the camera to the object you want to pan
float projectionToWorldScale = distanceToObject * tan( halfFov );
Vector mouseDeltaInScreenSpace = // the delta mouse in pixels that we want to pan

Vector mouseDeltaInProjectionSpace = Vector( mouseDeltaInScreenSpace.x * 2 / windowPixelSizeX, mouseDeltaInScreenSpace.y * 2 / windowPixelSizeY ); // ( the "*2" is because the projection space is from -1 to 1)

// go from normalized device coordinate space to world space (at origin)
Vector cameraDelta = -mouseDeltaInProjectionSpace * projectionToWorldScale;

// now translate your camera by "cameraDelta".