簡體   English   中英

在 OpenGL 中使用透視 VS 正交投影時的光線投射(鼠標拾取)

[英]Raycasting (Mouse Picking) while using an Perspective VS Orthographic Projection in OpenGL

我正在努力理解如何使用透視投影和正交投影來更改我的算法以處理光線投射(用於鼠標拾取)。

目前我有一個帶有 AxisAligned 邊界框的 3D 對象的場景。

在使用透視投影(使用 glm::perspective 創建)渲染場景時,我可以成功地使用光線投射和我的鼠標“選擇”場景中的不同對象。 這是一個演示

如果我渲染相同的場景,但使用正交投影,並將相機定位在面朝下的上方(向下看 Y 軸,想象像游戲的關卡編輯器)我無法從用戶單擊的位置正確地進行光線投射屏幕,所以我可以在使用正交投影渲染時讓鼠標拾取工作。 這是它不起作用演示

我的算法在高層次上:

auto const coords = mouse.coords();
glm::vec2 const mouse_pos{coords.x, coords.y};

glm::vec3 ray_dir, ray_start;
if (perspective) { // This "works"
      auto const ar  = aspect_rate;
      auto const fov = field_of_view;

      glm::mat4 const proj_matrix = glm::perspective(fov, ar, f.near, f.far);
      auto const& target_pos      =  camera.target.get_position();
      glm::mat4 const view_matrix = glm::lookAt(target_pos, target_pos, glm::vec3{0, -1, 0});

      ray_dir   = Raycast::calculate_ray_into_screen(mouse_pos, proj_matrix, view_matrix, view_rect);
      ray_start = camera.world_position();
}
else if (orthographic) { // This "doesn't work"
      glm::vec3 const POS     = glm::vec3{50};
      glm::vec3 const FORWARD = glm::vec3{0, -1, 0};
      glm::vec3 const UP      = glm::vec3{0, 0, -1};

      // 1024, 768 with NEAR 0.001 and FAR 10000
      //glm::mat4 proj_matrix = glm::ortho(0, 1024, 0, 768, 0.0001, 10000);
      glm::mat4 proj_matrix = glm::ortho(0, 1024, 0, 768, 0.0001, 100);
      // Look down at the scene from above  
      glm::mat4 view_matrix = glm::lookAt(POS, POS + FORWARD, UP);
      // convert the mouse screen coordinates into world coordinates for the cube/ray test
      auto const p0 = screen_to_world(mouse_pos, view_rect, proj_matrix, view_matrix, 0.0f);
      auto const p1 = screen_to_world(mouse_pos, view_rect, proj_matrix, view_matrix, 1.0f);

      ray_start = p0;
      ray_dir = glm::normalize(p1 - p0);
    }
bool const intersects = ray_intersects_cube(logger, ray_dir, ray_start,
                                                eid, tr, cube, distances);

在透視模式下,我們將光線投射到場景中,看看它是否與圍繞對象的立方體相交。

在正交模式下,我從屏幕投射兩條光線(一條在 z=0,另一條在 z=1)並在這兩點之間創建一條光線。 我將光線起點設置為鼠標指針所在的位置(z=0),並使用剛剛計算的光線方向作為同一 ray_cube_intersection 算法的輸入。

我的問題是這個

由於 MousePicking 使用 Perspective 投影工作,但不使用 Orthographic 投影:

  1. 假設相同的 ray_cube 相交算法可用於透視/正交投影是否合理?
  2. 我考慮在正交情況下設置 ray_start 和 ray_dir 變量是否正確?

是正在使用的射線/立方體碰撞算法的來源

glm::vec3
Raycast::calculate_ray_into_screen(glm::vec2 const& point, glm::mat4 const& proj,
                                   glm::mat4 const& view, Rectangle const& view_rect)
{
  // When doing mouse picking, we want our ray to be pointed "into" the screen
  float constexpr Z            = -1.0f;
  return screen_to_world(point, view_rect, proj, view, Z);
}

bool
ray_cube_intersect(Ray const& r, Transform const& transform, Cube const& cube,
    float& distance)
{
  auto const& cubepos = transform.translation;

  glm::vec3 const                minpos = cube.min * transform.scale;
  glm::vec3 const                maxpos = cube.max * transform.scale;
  std::array<glm::vec3, 2> const bounds{{minpos + cubepos, maxpos + cubepos}};

  float txmin = (bounds[    r.sign[0]].x - r.orig.x) * r.invdir.x;
  float txmax = (bounds[1 - r.sign[0]].x - r.orig.x) * r.invdir.x;
  float tymin = (bounds[    r.sign[1]].y - r.orig.y) * r.invdir.y;
  float tymax = (bounds[1 - r.sign[1]].y - r.orig.y) * r.invdir.y;

  if ((txmin > tymax) || (tymin > txmax)) {
    return false;
  }
  if (tymin > txmin) {
    txmin = tymin;
  }
  if (tymax < txmax) {
    txmax = tymax;
  }

  float tzmin = (bounds[    r.sign[2]].z - r.orig.z) * r.invdir.z;
  float tzmax = (bounds[1 - r.sign[2]].z - r.orig.z) * r.invdir.z;

  if ((txmin > tzmax) || (tzmin > txmax)) {
    return false;
  }

  distance = tzmin;
  return true;
}

編輯:我正在使用的數學空間轉換函數:

namespace boomhs::math::space_conversions
{

inline glm::vec4
clip_to_eye(glm::vec4 const& clip, glm::mat4 const& proj_matrix, float const z)
{
  auto const      inv_proj   = glm::inverse(proj_matrix);
  glm::vec4 const eye_coords = inv_proj * clip;
  return glm::vec4{eye_coords.x, eye_coords.y, z, 0.0f};
}

inline glm::vec3
eye_to_world(glm::vec4 const& eye, glm::mat4 const& view_matrix)
{
  glm::mat4 const inv_view  = glm::inverse(view_matrix);
  glm::vec4 const ray       = inv_view * eye;
  glm::vec3 const ray_world = glm::vec3{ray.x, ray.y, ray.z};
  return glm::normalize(ray_world);
}

inline constexpr glm::vec2
screen_to_ndc(glm::vec2 const& scoords, Rectangle const& view_rect)
{
  float const x = ((2.0f * scoords.x) / view_rect.right()) - 1.0f;
  float const y = ((2.0f * scoords.y) / view_rect.bottom()) - 1.0f;

  auto const assert_fn = [](float const v) {
    assert(v <= 1.0f);
    assert(v >= -1.0f);
  };
  assert_fn(x);
  assert_fn(y);
  return glm::vec2{x, -y};
}

inline glm::vec4
ndc_to_clip(glm::vec2 const& ndc, float const z)
{
  return glm::vec4{ndc.x, ndc.y, z, 1.0f};
}

inline glm::vec3
screen_to_world(glm::vec2 const& scoords, Rectangle const& view_rect, glm::mat4 const& proj_matrix,
                glm::mat4 const& view_matrix, float const z)
{
  glm::vec2 const ndc   = screen_to_ndc(scoords, view_rect);
  glm::vec4 const clip  = ndc_to_clip(ndc, z);
  glm::vec4 const eye   = clip_to_eye(clip, proj_matrix, z);
  glm::vec3 const world = eye_to_world(eye, view_matrix);
  return world;
}

} // namespace boomhs::math::space_conversions

我為此工作了幾天,因為我遇到了同樣的問題。 我們用來使用的非投影方法在這里也 100% 正確 - 即使使用正交投影。 但是對於正交投影,從相機位置到屏幕的方向向量總是相同的。 因此,在這種情況下,以相同的方式取消投影光標不會按預期工作。

您想要做的是按原樣獲取相機方向矢量,但為了獲得光線原點,您需要根據屏幕上的當前鼠標位置移動相機位置。

我的方法(C#,但你會明白的):

Vector3 worldUpDirection = new Vector3(0, 1, 0); // if your world is y-up

// Get mouse coordinates (2d) relative to window position:
Vector2 mousePosRelativeToWindow = GetMouseCoordsRelativeToWindow(); // (0,0) would be top left window corner

// get camera direction vector:
Vector3 camDirection = Vector3.Normalize(cameraTarget - cameraPosition);

// get x and y coordinates relative to frustum width and height.
// glOrthoWidth and glOrthoHeight are the sizeX and sizeY values 
// you created your projection matrix with. If your frustum has a width of 100, 
// x would become -50 when the mouse is left and +50 when the mouse is right.
float x = +(2.0f * mousePosRelativeToWindow .X / viewportWidth  - 1) * (glOrthoWidth  / 2);
float y = -(2.0f * mousePosRelativeToWindow .Y / viewPortHeight - 1) * (glOrthoHeight / 2);

// Now, you want to calculate the camera's local right and up vectors 
// (depending on the camera's current view direction):
Vector3 cameraRight = Vector3.Normalize(Vector3.Cross(camDirection, worldUpDirection));
Vector3 cameraUp = Vector3.Normalize(Vector3.Cross(cameraRight, camDirection));

// Finally, calculate the ray origin:
Vector3 rayOrigin = cameraPosition + cameraRight * x + cameraUp * y;
Vector3 rayDirection = camDirection;

現在您有了正交投影的射線原點和射線方向。 有了這些,您可以像往常一樣運行任何光線平面/體積相交。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM