Como converter um clique do mouse em um raio?

18

Eu tenho uma projeção em perspectiva. Quando o usuário clica na tela, quero calcular o raio entre os planos de perto e de longe projetados a partir do ponto do mouse, para que eu possa fazer algum código de interseção de raios com o meu mundo.

Estou usando minhas próprias classes de matriz e vetor e raio e todas elas funcionam como esperado.

No entanto, quando tento converter o raio em coordenadas mundiais, minha distância sempre termina em 0,0,0 e, portanto, meu raio vai do clique do mouse até o centro do espaço do objeto, e não através dele. (As coordenadas x e y de perto e de longe são idênticas, diferem apenas nas coordenadas z em que são negativas uma da outra)

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);

O que eu entendi errado? (Como você projeta o ponto do mouse?)

Vai
fonte
Por curiosidade, existe um motivo para você não estar usando o modo GL_SELECTION embutido? (informação aqui)
Ricket
@Ricket Não está obsoleto? Não sei, mas tenho certeza.
Notabene
Os x e y estão em pixels? esse seria o problema ...
Notabene
E você está multiplicando vec3 por matrix4. Isso não é bom ... qual número entra na 4ª posição quando você vec_t (1ª, 2ª, 3ª, 4ª)?
Notabene
@ notebene Uau, eu acho que é, eu não tinha idéia. Essa discussão é boa e fornece uma alternativa que terei que procurar, e agora espero REALMENTE uma boa resposta para essa pergunta.
Ricket

Respostas:

14

Recentemente, tive que resolver isso sozinho para um aplicativo WebGL. Anexei o código fonte completo, mas, caso ele não funcione imediatamente, aqui estão algumas dicas de depuração:

  1. Não depure seu método de desprojeto no seu jogo. Se possível, tente escrever testes de estilo de teste de unidade para facilitar o isolamento do que está errado.
  2. Certifique-se de imprimir os raios de saída para os planos de recorte próximo e distante.
  3. Lembre-se de que a matemática da matriz NÃO é comutativa. A x C! = C x A. Verifique novamente sua matemática.

Além disso, para responder a alguns comentários acima, você quase nunca deseja usar as APIs de seleção do OpenGL. Isso ajuda a escolher itens existentes, como se você estivesse criando um menu, mas falha na execução da maioria dos cenários do mundo real, como a edição de modelos 3D. Onde você precisa adicionar geometria como resultado do clique.

Aqui está a minha implementação. Não há nada mágico acontecendo aqui. Apenas JavaScript e a biblioteca Closure do Google.

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;
};

Uso

  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 + ')');
  };
};
Chris Smith
fonte
Em que caso se (resultArr [3] == 0.0) é avaliado como verdadeiro?
Halsafar
3

Não vejo nenhum problema com o seu código. Então, eu só tenho algumas sugestões:

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;

E tentando mudar a multiplacação de matrizes para (p*mv).inverse();. Só porque os glMatrices são transpostos nutaralmente na memória. (este é um grande talvez, desculpe)

Outro
desdobramento de raycasting não é apenas uma maneira de obter raios do pixel. Este é o meu código para o 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);

Você pode obter cameraRight, Up e Direção de matriz de visão (que é principalmente útil no shader) Ver matriz em OpenGL

Seleção de
cores A seleção de cores é uma técnica na qual você renderiza todos os objetos clicáveis ​​com outra cor (sólida) e depois lê o valor da cor no ponto clicado. Funcionou como GL_SELECTION no opengl. Mas está obsoleto agora. Mas a seleção de cores é fácil de codificar como 1 passe de renderização adicional.

Use buffer de quadro e renderize para textura com a mesma resolução que a resolução da tela, use sombreador simples que apenas retorna cores no sombreador de fragmento. Após "passagem de cor", leia o valor de um endereçamento de textura com clicou emPoint x e y. Encontre o objeto com a cor que você lê. Fácil e bastante rápido.

Notabene
fonte