在DirectX中取消投影屏幕坐标时出现意外结果

ack*_*ckh 11 c++ directx 3d projection raycasting

为了能够确定用户是否点击了我的任何3D对象,我试图将点击的屏幕坐标转换为矢量,然后我用它来检查我的三角形是否被击中.为此,我使用DirectX提供的XMVector3Unproject方法,并且我在C++/CX中实现了所有内容.

我面临的问题是,未预测屏幕坐标导致的矢量根本不像我预期的那样.下图说明了这一点:

由非投影产生的矢量 点击发生时的光标位置(以黄色突出显示)在左侧的等轴测视图中可见.一旦我点击,由于未投影而产生的矢量出现在图像中指示的模型后面,因为白线穿透模型.因此,它不是始于光标位置而是在等轴测视图中进入屏幕,而是出现在完全不同的位置.

当我在等轴测视图中水平移动鼠标时,单击并在此之后垂直移动鼠标并单击下面的图案.两个图像中的所有线代表由点击产生的矢量.该模型已被删除,以获得更好的可见性.

由非投影产生的矢量源自相同位置 从上面的图像可以看出,所有矢量似乎都来自相同的位置.如果我更改视图并重复该过程,则会出现相同的模式但具有不同的矢量原点.

不同的视角,不同的载体起源

以下是我用来提出这个问题的代码片段.首先,我使用下面的代码接收光标位置,并将其与绘图区域的宽度和高度一起传递给我的"SelectObject"方法:

void Demo::OnPointerPressed(Object^ sender, PointerEventArgs^ e)
{
  Point currentPosition = e->CurrentPoint->Position;

  if(m_model->SelectObject(currentPosition.X, currentPosition.Y, m_renderTargetWidth, m_renderTargetHeight))
  {
    m_RefreshImage = true;
  }
}
Run Code Online (Sandbox Code Playgroud)

"SelectObject"方法如下所示:

bool Model::SelectObject(float screenX, float screenY, float screenWidth, float screenHeight)
{
  XMMATRIX projectionMatrix = XMLoadFloat4x4(&m_modelViewProjectionConstantBufferData->projection);
  XMMATRIX viewMatrix       = XMLoadFloat4x4(&m_modelViewProjectionConstantBufferData->view);
  XMMATRIX modelMatrix      = XMLoadFloat4x4(&m_modelViewProjectionConstantBufferData->model);

  XMVECTOR v = XMVector3Unproject(XMVectorSet(screenX, screenY, 5.0f, 0.0f),
                                  0.0f,
                                  0.0f,
                                  screenWidth,
                                  screenHeight,
                                  0.0f,
                                  1.0f,
                                  projectionMatrix,
                                  viewMatrix,
                                  modelMatrix);

  XMVECTOR rayOrigin = XMVector3Unproject(XMVectorSet(screenX, screenY, 0.0f, 0.0f),
                                          0.0f,
                                          0.0f,
                                          screenWidth,
                                          screenHeight,
                                          0.0f,
                                          1.0f,
                                          projectionMatrix,
                                          viewMatrix,
                                          modelMatrix);

  // Code to retrieve v0, v1 and v2 is omitted

  if(Intersects(rayOrigin, XMVector3Normalize(v - rayOrigin), v0, v1, v2, depth))
  {
    return true;
  }
}
Run Code Online (Sandbox Code Playgroud)

最终,计算的向量由DirectX :: TriangleTests命名空间的Intersects方法使用,以检测三角形是否被击中.我省略了上面剪辑中的代码,因为它与此问题无关.

为了渲染这些图像,我使用正交投影矩阵和可以围绕其局部x轴和y轴旋转的相机,其产生视图矩阵.世界矩阵总是保持不变,即它只是一个单位矩阵.

视图矩阵计算如下(基于Frank Luna的书3D游戏编程中的示例):

void Camera::SetViewMatrix()
{
  XMFLOAT3 cameraPosition;
  XMFLOAT3 cameraXAxis;
  XMFLOAT3 cameraYAxis;
  XMFLOAT3 cameraZAxis;

  XMFLOAT4X4 viewMatrix;

  // Keep camera's axes orthogonal to each other and of unit length.
  m_cameraZAxis = XMVector3Normalize(m_cameraZAxis);
  m_cameraYAxis = XMVector3Normalize(XMVector3Cross(m_cameraZAxis, m_cameraXAxis));

  // m_cameraYAxis and m_cameraZAxis are already normalized, so there is no need
  // to normalize the below cross product of the two.
  m_cameraXAxis = XMVector3Cross(m_cameraYAxis, m_cameraZAxis);

  // Fill in the view matrix entries.
  float x = -XMVectorGetX(XMVector3Dot(m_cameraPosition, m_cameraXAxis));
  float y = -XMVectorGetX(XMVector3Dot(m_cameraPosition, m_cameraYAxis));
  float z = -XMVectorGetX(XMVector3Dot(m_cameraPosition, m_cameraZAxis));

  XMStoreFloat3(&cameraPosition, m_cameraPosition);
  XMStoreFloat3(&cameraXAxis   , m_cameraXAxis);
  XMStoreFloat3(&cameraYAxis   , m_cameraYAxis);
  XMStoreFloat3(&cameraZAxis   , m_cameraZAxis);

  viewMatrix(0, 0) = cameraXAxis.x;
  viewMatrix(1, 0) = cameraXAxis.y;
  viewMatrix(2, 0) = cameraXAxis.z;
  viewMatrix(3, 0) = x;

  viewMatrix(0, 1) = cameraYAxis.x;
  viewMatrix(1, 1) = cameraYAxis.y;
  viewMatrix(2, 1) = cameraYAxis.z;
  viewMatrix(3, 1) = y;

  viewMatrix(0, 2) = cameraZAxis.x;
  viewMatrix(1, 2) = cameraZAxis.y;
  viewMatrix(2, 2) = cameraZAxis.z;
  viewMatrix(3, 2) = z;

  viewMatrix(0, 3) = 0.0f;
  viewMatrix(1, 3) = 0.0f;
  viewMatrix(2, 3) = 0.0f;
  viewMatrix(3, 3) = 1.0f;

  m_modelViewProjectionConstantBufferData->view = viewMatrix;
}
Run Code Online (Sandbox Code Playgroud)

它受两种方法的影响,这些方法围绕摄像机的x轴和y轴旋转摄像机:

void Camera::ChangeCameraPitch(float angle)
{
  XMMATRIX rotationMatrix = XMMatrixRotationAxis(m_cameraXAxis, angle);

  m_cameraYAxis = XMVector3TransformNormal(m_cameraYAxis, rotationMatrix);
  m_cameraZAxis = XMVector3TransformNormal(m_cameraZAxis, rotationMatrix);
}

void Camera::ChangeCameraYaw(float angle)
{
  XMMATRIX rotationMatrix = XMMatrixRotationAxis(m_cameraYAxis, angle);

  m_cameraXAxis = XMVector3TransformNormal(m_cameraXAxis, rotationMatrix);
  m_cameraZAxis = XMVector3TransformNormal(m_cameraZAxis, rotationMatrix);
}
Run Code Online (Sandbox Code Playgroud)

世界/模型矩阵和投影矩阵计算如下:

void Model::SetProjectionMatrix(float width, float height, float nearZ, float farZ)
{
  XMMATRIX orthographicProjectionMatrix = XMMatrixOrthographicRH(width, height, nearZ, farZ);

  XMFLOAT4X4 orientation = XMFLOAT4X4
  (
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
  );

  XMMATRIX orientationMatrix = XMLoadFloat4x4(&orientation);

  XMStoreFloat4x4(&m_modelViewProjectionConstantBufferData->projection, XMMatrixTranspose(orthographicProjectionMatrix * orientationMatrix));
}

void Model::SetModelMatrix()
{
  XMFLOAT4X4 orientation = XMFLOAT4X4
  (
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
  );

  XMMATRIX orientationMatrix = XMLoadFloat4x4(&orientation);

  XMStoreFloat4x4(&m_modelViewProjectionConstantBufferData->model, XMMatrixTranspose(orientationMatrix));
}
Run Code Online (Sandbox Code Playgroud)

坦率地说,我还没有理解我面临的问题.如果有更深入见解的人能给我一些关于我需要应用更改的提示,以便从非投影计算的向量从光标位置开始并移动到屏幕中,我将不胜感激.

编辑1:

我认为这与我的相机在世界坐标中位于(0,0,0)的事实有关.相机围绕其局部x轴和y轴旋转.根据我的理解,相机创建的视图矩阵构建了投影图像的平面.如果是这种情况,它将解释为什么光线处于某种"意外"位置.

我的假设是我需要将相机移出中心,使其位于物体外.但是,如果只是修改m_cameraPosition相机的成员变量,我的模型会完全失真.

那里的任何人都能够并愿意提供帮助吗?

ack*_*ckh 6

谢谢你的提示,卡皮尔.我尝试了这种XMMatrixLookAtRH方法,但无法使用这种方法改变相机的俯仰/偏航,所以我放弃了这种方法,并想出了自己生成矩阵.

解决了我的问题是XMMatrixTranspose在传递模型,视图和投影矩阵之前使用它们XMVector3Unproject.因此,而不是如下代码

  XMMATRIX projectionMatrix = XMLoadFloat4x4(&m_modelViewProjectionConstantBufferData->projection);
  XMMATRIX viewMatrix       = XMLoadFloat4x4(&m_modelViewProjectionConstantBufferData->view);
  XMMATRIX modelMatrix      = XMLoadFloat4x4(&m_modelViewProjectionConstantBufferData->model);

  XMVECTOR rayBegin = XMVector3Unproject(XMVectorSet(screenX, screenY, -m_boundingSphereRadius, 0.0f),
                                         0.0f,
                                         0.0f,
                                         screenWidth,
                                         screenHeight,
                                         0.0f,
                                         1.0f,
                                         projectionMatrix,
                                         viewMatrix,
                                         modelMatrix);
Run Code Online (Sandbox Code Playgroud)

它需要

  XMMATRIX projectionMatrix = XMMatrixTranspose(XMLoadFloat4x4(&m_modelViewProjectionConstantBufferData->projection));
  XMMATRIX viewMatrix       = XMMatrixTranspose(XMLoadFloat4x4(&m_modelViewProjectionConstantBufferData->view));
  XMMATRIX modelMatrix      = XMMatrixTranspose(XMLoadFloat4x4(&m_modelViewProjectionConstantBufferData->model));

  XMVECTOR rayBegin = XMVector3Unproject(XMVectorSet(screenX, screenY, -m_boundingSphereRadius, 0.0f),
                                         0.0f,
                                         0.0f,
                                         screenWidth,
                                         screenHeight,
                                         0.0f,
                                         1.0f,
                                         projectionMatrix,
                                         viewMatrix,
                                         modelMatrix);
Run Code Online (Sandbox Code Playgroud)

我不清楚为什么在将矩阵传递给unproject方法之前需要转置矩阵.但是,我怀疑它与我移动相机时遇到的问题有关.这个帖子已经在StackOverflow上描述了这个问题.

我还没有设法解决这个问题.简单地转置视图矩阵并不能解决它.但是,我的主要问题已经解决,我的模型终于可以点击了.

如果有人有任何需要添加的东西,并阐明为什么矩阵需要转置或为什么移动相机扭曲模型,请继续发表评论或答案.


ack*_*ckh 0

如前所述,即使单击“立即”有效,问题也尚未完全解决。移动相机时模型变形的问题(我怀疑与此相关)仍然存在。我所说的“模型扭曲”的意思可以从下图中看出:

在此输入图像描述

左图显示了当相机位于世界中心(即 (0, 0, 0))时模型的外观,而右图显示了当我沿负 y 轴方向移动相机时会发生什么。可以看出,模型底部变宽,顶部变小,这与我上面提供的链接中描述的行为相同。

我最终为解决这两个问题所做的是:

  1. 在将矩阵传递给 XMVector3Unproject 之前对其进行转置(如上所述)
  2. 通过更改方法的代码来转置我的视图矩阵SetViewMatrix(代码见下文)

SetViewMatrix方法现在如下所示:

void Camera::SetViewMatrix()
{
  XMFLOAT3 cameraPosition;
  XMFLOAT3 cameraXAxis;
  XMFLOAT3 cameraYAxis;
  XMFLOAT3 cameraZAxis;

  XMFLOAT4X4 viewMatrix;

  // Keep camera's axes orthogonal to each other and of unit length.
  m_cameraZAxis = XMVector3Normalize(m_cameraZAxis);
  m_cameraYAxis = XMVector3Normalize(XMVector3Cross(m_cameraZAxis, m_cameraXAxis));

  // m_cameraYAxis and m_cameraZAxis are already normalized, so there is no need
  // to normalize the below cross product of the two.
  m_cameraXAxis = XMVector3Cross(m_cameraYAxis, m_cameraZAxis);

  // Fill in the view matrix entries.
  float x = -XMVectorGetX(XMVector3Dot(m_cameraPosition, m_cameraXAxis));
  float y = -XMVectorGetX(XMVector3Dot(m_cameraPosition, m_cameraYAxis));
  float z = -XMVectorGetX(XMVector3Dot(m_cameraPosition, m_cameraZAxis));

  //XMStoreFloat3(&cameraPosition, m_cameraPosition);
  XMStoreFloat3(&cameraXAxis, m_cameraXAxis);
  XMStoreFloat3(&cameraYAxis, m_cameraYAxis);
  XMStoreFloat3(&cameraZAxis, m_cameraZAxis);

  viewMatrix(0, 0) = cameraXAxis.x;
  viewMatrix(0, 1) = cameraXAxis.y;
  viewMatrix(0, 2) = cameraXAxis.z;
  viewMatrix(0, 3) = x;

  viewMatrix(1, 0) = cameraYAxis.x;
  viewMatrix(1, 1) = cameraYAxis.y;
  viewMatrix(1, 2) = cameraYAxis.z;
  viewMatrix(1, 3) = y;

  viewMatrix(2, 0) = cameraZAxis.x;
  viewMatrix(2, 1) = cameraZAxis.y;
  viewMatrix(2, 2) = cameraZAxis.z;
  viewMatrix(2, 3) = z;

  viewMatrix(3, 0) = 0.0f;
  viewMatrix(3, 1) = 0.0f;
  viewMatrix(3, 2) = 0.0f;
  viewMatrix(3, 3) = 1.0f;

  m_modelViewProjectionConstantBufferData->view = viewMatrix;
}
Run Code Online (Sandbox Code Playgroud)

所以我只是交换了行和列坐标。请注意,我必须确保在我的ChangeCameraYaw方法之前调用我的方法ChangeCameraPitch。这是必要的,因为否则模型的方向不是我想要的。

还有另一种方法可以使用。row_major我可以将顶点着色器中的关键字与视图矩阵一起使用,而不是通过交换行和列坐标并在将其传递给 XMVector3Unproject 之前转置来转置视图矩阵:

cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
  matrix model;
  row_major matrix view;
  matrix projection;
};
Run Code Online (Sandbox Code Playgroud)

我在这篇博文中想到了这个想法。关键字row_major影响着色器编译器如何解释内存中的矩阵。也可以通过改变顶点着色器中向量*矩阵乘法的顺序来实现相同的效果,即使用pos = mul(view, pos);而不是pos = mul(pos, view);

差不多就这样了。这两个问题确实是相互关联的,但是使用我在这个问题中发布的内容,我能够解决这两个问题,所以我接受我自己的回复作为这个问题的答案。希望它对将来的人有所帮助。