如何将鼠标单击转换为射线?


18

我有一个透视投影。当用户单击屏幕时,我想计算从鼠标点投射的近平面和远平面之间的射线,因此我可以对自己的世界进行一些射线相交的代码。

我正在使用自己的矩阵以及矢量和射线类,它们均按预期工作。

但是,当我尝试将射线转换为世界坐标时,我的远端总是以0,0,0结尾,因此射线从鼠标单击转到对象空间的中心,而不是通过对象。(near和far的x和y坐标相同,只是z坐标互为负数而不同)

GLint vp[4];
glGetIntegerv(GL_VIEWPORT,vp);
matrix_t mv, p;
glGetFloatv(GL_MODELVIEW_MATRIX,mv.f);
glGetFloatv(GL_PROJECTION_MATRIX,p.f);
const matrix_t inv = (mv*p).inverse();
const float
    unit_x = (2.0f*((float)(x-vp[0])/(vp[2]-vp[0])))-1.0f,
    unit_y = 1.0f-(2.0f*((float)(y-vp[1])/(vp[3]-vp[1])));
const vec_t near(vec_t(unit_x,unit_y,-1)*inv);
const vec_t far(vec_t(unit_x,unit_y,1)*inv);
ray = ray_t(near,far-near);

我怎么了?(如何解开鼠标点?)


出于好奇,您是否没有使用内置的GL_SELECTION模式?(此处的信息)
Ricket

@Ricket不赞成使用吗?不知道,但是很确定。
Notabene 2011年

x和y以像素为单位吗?那将是问题所在……
Notabene

您将vec3乘以matrix4。那不好。。。当您执行vec_t(1st,2nd,3th,4th)时,哪个数字进入第四位置?
Notabene 2011年

@notebene哇,我想是,我一无所知。这个讨论很好,提供了我必须研究的替代方法,现在我真的很希望为这个问题提供一个好的答案。
Ricket

Answers:


14

我最近不得不自己解决WebGL应用程序的问题。我已经附上了完整的源代码,但是如果您不立即使用它,以下是一些调试提示:

  1. 不要在游戏中调试非项目方法。如果可能的话,请尝试编写单元测试样式的测试,以便更容易地找出问题所在。
  2. 确保打印近和远剪切平面的输出光线。
  3. 请记住,矩阵数学不是可交换的。A x C!= C xA。仔细检查数学。

另外,要回复上面的一些评论,您几乎永远都不想使用OpenGL的选择API。这可以帮助您选择现有项目,例如创建菜单,但是它无法执行大多数实际场景,例如3D模型编辑。单击后需要在其中添加几何体的位置。

这是我的实现。这里没有任何魔术。只是JavaScript和Google的Closure库。

gluUnProject

/**
 * Port of gluUnProject. Unprojects a 2D screen coordinate into the model-view
 * coordinates.
 * @param {Number} winX The window point for the x value.
 * @param {Number} winY The window point for the y value.
 * @param {Number} winZ The window point for the z value. This should range
 *    between 0 and 1. 0 meaning the near clipping plane and 1 for the far.
 * @param {goog.math.Matrix} modelViewMatrix The model-view matrix.
 * @param {goog.math.Matrix} projectMatrix The projection matrix.
 * @param {Array.<Number>} view the viewport coordinate array.
 * @param {Array.<Number>} objPos the model point result.
 * @return {Boolean} Whether or not the unprojection was successful.
 */
octorok.math.Matrix.gluUnProject = function(winX, winY, winZ,
                        modelViewMatrix, projectionMatrix,
                        viewPort, objPos) {
  // Compute the inverse of the perspective x model-view matrix.
  /** @type {goog.math.Matrix} */
  var transformMatrix =
    projectionMatrix.multiply(modelViewMatrix).getInverse();

  // Transformation of normalized coordinates (-1 to 1).
  /** @type {Array.<Number>} */
  var inVector = [
    (winX - viewPort[0]) / viewPort[2] * 2.0 - 1.0,
    (winY - viewPort[1]) / viewPort[3] * 2.0 - 1.0,
    2.0 * winZ - 1.0,
    1.0 ];

  // Now transform that vector into object coordinates.
  /** @type {goog.math.Matrix} */
  // Flip 1x4 to 4x1. (Alternately use different matrix ctor.
  var inMatrix = new goog.math.Matrix([ inVector ]).getTranspose();
  /** @type {goog.math.Matrix} */
  var resultMtx = transformMatrix.multiply(inMatrix);
  /** @type {Array.<Number>} */
  var resultArr = [
    resultMtx.getValueAt(0, 0),
    resultMtx.getValueAt(1, 0),
    resultMtx.getValueAt(2, 0),
    resultMtx.getValueAt(3, 0) ];

  if (resultArr[3] == 0.0) {
    return false;
  }

  // Invert to normalize x, y, and z values.
  resultArr[3] = 1.0 / resultArr[3];

  objPos[0] = resultArr[0] * resultArr[3];
  objPos[1] = resultArr[1] * resultArr[3];
  objPos[2] = resultArr[2] * resultArr[3];

  return true;
};

用法

  this.sys.event_mouseClicked = function(event) {
    // Relative x and y are computed via magic by SystemModule.
    // Should range from 0 .. viewport width/height.
    var winX = event.relativeX;
    var winY = event.relativeY;
    window.console.log('Camera at [' + me.camera.position_ + ']');
    window.console.log('Clicked [' + winX + ', ' + winY + ']');

    // viewportOriginX, viewportOriginY, viewportWidth, viewportHeight
    var viewPort = [0, 0, event.viewPortWidth, event.viewPortHeight];
    var objPos = [];  // out parameter.

    // The camera's model-view matrix is the result of gluLookAt.
    var modelViewMatrix = me.camera.getCameraMatrix();
    // The perspective matrix is the result of gluPerspective.
    var perspectiveMatrix = pMatrix.get();

    // Ray start
    var result1 = octorok.math.Matrix.gluUnProject(
      winX, winY, 0.0,
      modelViewMatrix, perspectiveMatrix,
      viewPort, objPos);
    window.console.log('Seg start: ' + objPos + ' (result:' + result1 + ')');

    // Ray end
    var result2 = octorok.math.Matrix.gluUnProject(
      winX, winY, 1.0,
      modelViewMatrix, perspectiveMatrix,
      viewPort, objPos);
    window.console.log('Seg end: ' + objPos + ' (result:' + result2 + ')');
  };
};

在什么情况下if(resultArr [3] == 0.0)评估为true?
Halsafar 2013年

3

我看不到您的代码有任何问题。所以我只有一些建议:

unit_x = (2.0f*((float)(x-vp[0])/(vp[2]-vp[0])))-1.0f,
unit_y = (2.0f*((float)(y-vp[1])/(vp[3]-vp[1])))-1.0f;

并尝试将矩阵乘法更改为(p*mv).inverse();。仅仅因为glMatrices在内存中被精确地转置了。(也许这是一个很大的遗憾)

另一个
光线投射“不投射”不仅是如何从像素获取光线的方法。这是我的raycaster代码:

//works for right handed coordinates. So it is opengl friendly.
float mx = (float)((pixel.x - screenResolution.x * 0.5) * (1.0 / screenResolution.x) * camera.fovX * 0.5);
float my = (float)((pixel.y - screenResolution.y * 0.5) * (1.0 / screenResolution.x) * camera.fovX * 0.5);
float4 dx = cameraRight * mx;
float4 dy = cameraUp * my;

float3 dir = normalize(cameraDir + (dx + dy).xyz * 2.0);

你可以得到cameraRight,从视图矩阵上和方向(这是最有用的着色器)在OpenGL视矩阵

颜色挑选
颜色采摘是你渲染每一个可点击的对象与另一个(固体)的颜色,然后读取的颜色在点击的点值的技术。它在opengl中用作GL_SELECTION。但是现在不推荐使用。但是颜色拾取很容易编码为1个额外的渲染通道。

使用帧缓冲区并以与屏幕分辨率相同的分辨率渲染纹理,使用简单的着色器,该着色器仅在片段着色器中返回颜色。在“颜色传递”之后,从带有clickedPoint x和y的纹理寻址中读取值。用您读取的颜色查找对象。简单而快速。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.