0

我正在尝试通过本网站的说明来实现拾取射线。

现在我基本上只希望能够点击地面,命令我的小人向这个点走。由于我的地平面是平的、不旋转的和不平移的,所以当 y 达到 0 时,我必须找到我的拾取射线的 x 和 z 坐标。

到目前为止一切顺利,这就是我想出的:

//some constants
float HEIGHT = 768.f;
float LENGTH = 1024.f;
float fovy = 45.f;
float nearClip = 0.1f;

//mouse position on screen
float x = MouseX;
float y = HEIGHT - MouseY;

//GetView() returns the viewing direction, not the lookAt point.
glm::vec3 view = cam->GetView();
glm::normalize(view);

glm::vec3 h = glm::cross(view, glm::vec3(0,1,0) ); //cameraUp
glm::normalize(h);

glm::vec3 v = glm::cross(h, view);
glm::normalize(v);

// convert fovy to radians 
float rad = fovy * 3.14 / 180.f; 
float vLength = tan(rad/2) * nearClip; //nearClippingPlaneDistance
float hLength = vLength * (LENGTH/HEIGHT);

v *= vLength;
h *= hLength;

// translate mouse coordinates so that the origin lies in the center
// of the view port
x -= LENGTH / 2.f;
y -= HEIGHT / 2.f;

// scale mouse coordinates so that half the view port width and height
// becomes 1
x /= (LENGTH/2.f);
y /= (HEIGHT/2.f);

glm::vec3 cameraPos = cam->GetPosition();

// linear combination to compute intersection of picking ray with
// view port plane
glm::vec3 pos = cameraPos + (view*nearClip) + (h*x) + (v*y);

// compute direction of picking ray by subtracting intersection point
// with camera position
glm::vec3 dir = pos - cameraPos;

//Get intersection between ray and the ground plane
pos -= (dir * (pos.y/dir.y));

在这一点上,我希望pos成为我的拾取射线击中我的地平面的点。然而,当我尝试它时,我得到了这样 错误 的结果:(鼠标光标没有被记录下来)因为地面没有纹理,所以很难看到,但是相机是倾斜的,就像在大多数 RTS 游戏中一样。根据我的计算,我可怜地尝试在 Blender 中模拟一个看起来很远的人,这标志着交叉点发生。

所以看起来和之间的view转换dir某处之间的转换搞砸了,我的光线最终指向了错误的方向。计算位置和实际位置之间的差距会随着鼠标远离屏幕中心的距离而增加。

我发现:

  • 高度和长度不是准确的。由于 Windows 为边框切掉了几个像素,因此使用 1006,728 作为窗口分辨率会更准确。我想这可能会造成小的差异。
  • 如果我将 fovy 从 45 增加到大约 78,我会得到相当准确的光线。所以也许我用作fovy的东西有问题。我明确地调用glm::perspective(45.f, 1.38f, 0.1f, 500.f)(分别为fovy、纵横比、fNear、fFar)。

所以这就是我迷路的地方。为了获得准确的光线,我必须做什么?

PS:我知道有一些函数和库已经实现了这个,但为了学习目的,我尽量远离这些东西。

4

1 回答 1

0

这是使用深度缓冲区信息将光标转换为 3D 的工作代码:

  glGetIntegerv(GL_VIEWPORT, @fViewport);
  glGetDoublev(GL_PROJECTION_MATRIX, @fProjection);
  glGetDoublev(GL_MODELVIEW_MATRIX, @fModelview);

  //fViewport already contains viewport offsets
  PosX := X;
  PosY := ScreenY - Y; //In OpenGL Y axis is inverted and starts from bottom

  glReadPixels(PosX, PosY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, @vz);

  gluUnProject(PosX, PosY, vz, fModelview, fProjection, fViewport, @wx, @wy, @wz);
  XYZ.X := wx;
  XYZ.Y := wy;
  XYZ.Z := wz;

如果您只测试光线/平面相交,这是没有 DepthBuffer 的第二部分:

  gluUnProject(PosX, PosY, 0, fModelview, fProjection, fViewport, @x1, @y1, @z1); //Near
  gluUnProject(PosX, PosY, 1, fModelview, fProjection, fViewport, @x2, @y2, @z2); //Far

  //No intersection
  Result := False;
  XYZ.X := 0;
  XYZ.Y := 0;
  XYZ.Z := aZ;

  if z2 < z1 then
    SwapFloat(z1, z2);

  if (z1 <> z2) and InRange(aZ, z1, z2) then
  begin
    D := 1 - (aZ - z1) / (z2 - z1);
    XYZ.X := Lerp(x1, x2, D);
    XYZ.Y := Lerp(y1, y2, D);
    Result := True;
  end;

我发现它与您正在做的事情有很大不同,但也许这会更有意义。

于 2012-11-21T13:25:35.253 回答