web-dev-qa-db-ja.com

レイピッキングの実装

DirectxとopenGLを使用したレンダラーと3dシーンがあります。ビューポートとウィンドウは同じ寸法です。

特定のマウス座標xとyをプラットフォームに依存しない方法で選択する方法を教えてください。

31
Tom J Nowell

可能であれば、マウスポインターを通して目からの光線を計算してCPUでピッキングを行い、モデルと交差させます。

これがオプションでない場合は、ある種のIDレンダリングを使用します。一意の色を選択する各オブジェクトを割り当て、これらの色でオブジェクトをレンダリングし、最後にマウスポインターの下のフレームバッファーから色を読み取ります。

編集:マウス座標から光線を作成する方法が問題である場合は、次のものが必要です:投影行列[〜#〜] p [〜#〜]およびカメラ変換[〜#〜] c [〜#〜]。マウスポインタの座標が(x、y)で、ビューポートのサイズが(width、height)の場合、レイに沿ったクリップスペースの1つの位置は次のようになります。

mouse_clip = [
  float(x) * 2 / float(width) - 1,
  1 - float(y) * 2 / float(height),
  0,
  1]

(多くの場合、マウス座標の原点は左上隅にあるため、y軸を反転したことに注意してください)

以下も当てはまります。

mouse_clip = P * C * mouse_worldspace

それは与える:

mouse_worldspace = inverse(C) * inverse(P) * mouse_clip

私たちは今持っています:

p = C.position(); //Origin of camera in worldspace
n = normalize(mouse_worldspace - p); //unit vector from p through mouse pos in worldspace
29
Andreas Brinck

視錐台は次のとおりです。

viewing frustum

最初に、マウスのクリックが発生したニアプレーン上の場所を特定する必要があります。

  1. ウィンドウ座標(0..640,0..480)を[-1,1]に再スケールします。(-1、-1)は左下隅にあり、(1,1)は右上にあります。
  2. スケーリングされた座標に 'unview'マトリックスと呼ばれるものを掛けて投影を「元に戻す」:unview = (P * M).inverse() = M.inverse() * P.inverse()、ここでMはModelViewマトリックス、Pは投影マトリックス。

次に、カメラがワールドスペースのどこにあるかを特定し、カメラから開始し、ニアプレーン上で見つけた点を通過する光線を描画します。

カメラはM.inverse().col(4)にあります。つまり、逆ModelView行列の最後の列です。

最終的な疑似コード:

normalised_x = 2 * mouse_x / win_width - 1
normalised_y = 1 - 2 * mouse_y / win_height
// note the y pos is inverted, so +y is at the top of the screen

unviewMat = (projectionMat * modelViewMat).inverse()

near_point = unviewMat * Vec(normalised_x, normalised_y, 0, 1)
camera_pos = ray_Origin = modelViewMat.inverse().col(4)
ray_dir = near_point - camera_pos
24
nornagon

DirectXの経験はほとんどありませんが、OpenGLに似ていると確信しています。必要なのは、gluUnprojectの呼び出しです。

有効なZバッファーがあるとすると、次のコマンドを使用して、マウス位置のZバッファーの内容を照会できます。

// obtain the viewport, modelview matrix and projection matrix
// you may keep the viewport and projection matrices throughout the program if you don't change them
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
glGetIntegerv(GL_VIEWPORT, viewport);
glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
glGetDoublev(GL_PROJECTION_MATRIX, projection);

// obtain the Z position (not world coordinates but in range 0 - 1)
GLfloat z_cursor;
glReadPixels(x_cursor, y_cursor, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z_cursor);

// obtain the world coordinates
GLdouble x, y, z;
gluUnProject(x_cursor, y_cursor, z_cursor, modelview, projection, viewport, &x, &y, &z);

gluを使用したくない場合は、gluUnProjectを実装することもできます。自分で実装することもできます。その機能は比較的シンプルで、 opengl.org で説明されています

1
wich

まあ、かなりシンプルです。この背後にある理論は常に同じです

1)2D座標の2倍を3D空間に投影します。 (各APIには独自の機能がありますが、必要に応じて独自に実装できます)。最小Zに1つ、最大Zに1つ。

2)これらの2つの値を使用して、最小Zから最大Zを指すベクトルを計算します。

3)ベクトルと点を使用して、最小Zから最大Zに向かう光線を計算します。

4)これで光線が得られました。これにより、光線三角形/光線平面/光線何かの交差を実行して、結果を得ることができます...

1
feal87

通常のレイピッキングでも同じ状況ですが、何かがおかしいです。アンプロジェクト操作を適切に実行しましたが、機能しません。間違いはあると思いますが、どこにあるのかわかりません。私のmatix乗算、逆行列、およびvector by matix乗算はすべて正常に動作することがわかっているので、テストしました。私のコードでは、WM_LBUTTONDOWNに反応しています。したがって、lParamは[Y] [X]座標をdwordの2ワードとして返します。それらを抽出し、正規化された空間に変換します。この部分も正常に動作することを確認しました。左下隅をクリックすると、-1 -1に近い値と他の3つの隅すべてに適切な値が表示されます。その後、デバッグにlinepoins.vtx配列を使用していますが、現実に近いものではありません。

unsigned int x_coord=lParam&0x0000ffff; //X RAW COORD
unsigned int y_coord=client_area.bottom-(lParam>>16); //Y RAW COORD

double xn=((double)x_coord/client_area.right)*2-1; //X [-1 +1]
double yn=1-((double)y_coord/client_area.bottom)*2;//Y [-1 +1]

_declspec(align(16))gl_vec4 pt_eye(xn,yn,0.0,1.0); 
gl_mat4 view_matrix_inversed;
gl_mat4 projection_matrix_inversed;
cam.matrixProjection.inverse(&projection_matrix_inversed);
cam.matrixView.inverse(&view_matrix_inversed);

gl_mat4::vec4_multiply_by_matrix4(&pt_eye,&projection_matrix_inversed);
gl_mat4::vec4_multiply_by_matrix4(&pt_eye,&view_matrix_inversed);

line_points.vtx[line_points.count*4]=pt_eye.x-cam.pos.x;
line_points.vtx[line_points.count*4+1]=pt_eye.y-cam.pos.y;
line_points.vtx[line_points.count*4+2]=pt_eye.z-cam.pos.z;
line_points.vtx[line_points.count*4+3]=1.0;
0
Antiusninja

わかりました、このトピックは古いですが、それは私がトピックで見つけた最高であり、それは私を少し助けてくれたので、フォローしている人のためにここに投稿します;-)

これは、射影行列の逆行列を計算する必要なしに機能させる方法です。

void Application::leftButtonPress(u32 x, u32 y){
    GL::Viewport vp = GL::getViewport(); // just a call to glGet GL_VIEWPORT
vec3f p = vec3f::from(                        
        ((float)(vp.width - x) / (float)vp.width),
        ((float)y / (float)vp.height),
            1.);
    // alternatively vec3f p = vec3f::from(                        
    //      ((float)x / (float)vp.width),
    //      ((float)(vp.height - y) / (float)vp.height),
    //      1.);

    p *= vec3f::from(APP_FRUSTUM_WIDTH, APP_FRUSTUM_HEIGHT, 1.);
    p += vec3f::from(APP_FRUSTUM_LEFT, APP_FRUSTUM_BOTTOM, 0.);

    // now p elements are in (-1, 1)
    vec3f near = p * vec3f::from(APP_FRUSTUM_NEAR);
    vec3f far = p * vec3f::from(APP_FRUSTUM_FAR);

    // ray in world coordinates
    Ray ray = { _camera->getPos(), -(_camera->getBasis() * (far - near).normalize()) };

    _ray->set(ray.Origin, ray.dir, 10000.); // this is a debugging vertex array to see the Ray on screen

    Node* node = _scene->collide(ray, Transform());
   cout << "node is : " << node << endl;
}

これは透視投影を前提としていますが、最初に正投影の問題が発生することはありません。

0
nulleight