Чтобы иметь возможность определить, щелкнул ли пользователь какой-либо из моих 3D-объектов, я пытаюсь преобразовать экранные координаты щелчка в вектор, который затем использую для проверки того, попал ли какой-либо из моих треугольников. Для этого я использую XMVector3Unproject, предоставляемый DirectX, и я реализую все на 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;
}
}
Метод 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;
}
}
В конечном итоге рассчитанный вектор используется Intersects пространства имен DirectX::TriangleTests для определения попадания в треугольник. Я опустил код в приведенном выше фрагменте, потому что он не имеет отношения к этой проблеме.
Для рендеринга этих изображений я использую матрицу ортогональной проекции и камеру, которую можно вращать вокруг обеих осей x и y, что создает матрицу вида. Матрица мира всегда остается неизменной, то есть это просто единичная матрица.
Матрица вида рассчитывается следующим образом (на примере книги Фрэнка Луны «Программирование 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;
}
На него влияют два метода, которые вращают камеру вокруг осей 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);
}
Матрица мира/модели и матрица проекции вычисляются следующим образом:
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));
}
Честно говоря, я пока не понимаю проблемы, с которой столкнулся. Я был бы признателен, если бы кто-нибудь с более глубоким пониманием мог дать мне несколько советов относительно того, где мне нужно применить изменения, чтобы вектор, вычисленный из непроекции, начинался с позиции курсора и перемещался на экран.
Изменить 1:
Я предполагаю, что это связано с тем, что моя камера находится в точке (0, 0, 0) в мировых координатах. Камера вращается вокруг своих локальных осей x и y. Насколько я понимаю, матрица обзора, созданная камерой, строит плоскость, на которую проецируется изображение. Если это так, то это объясняет, почему луч находится в каком-то «неожиданном» месте.
Мое предположение состоит в том, что мне нужно сместить камеру из центра, чтобы она находилась за пределами объекта. Однако, если просто изменить переменную-член m_cameraPosition
камеры, моя модель будет полностью искажена.
Кто-нибудь может и хочет помочь?