0322

2026년 3월 22일

3D 네비게이션의 구현

0322.gif

이름만 보면 뭐 있어보이는데 별건 아니고

타이탄폴 스피드런 영상을 보다가 퀘스트가 뜬 순간, 쿼스트를 이행해야 하는 지점(3차원 점)까지의 방향을 2D스크린에 표시하는 걸 보았는데

생각해보니까 별거 없는 것 같아서 구현해 보았다.

// 한번 3D 네비게이션을 만들어보자.

// (0, 0, 0) 을 향하는 네비.
dp::Vector3f target_point = { 0, 0, 0 };


// 월드 공간 점 -> 스크린 공간
auto ProjectPoint = [this](const dp::Vector3f world_pos) -> dp::Vector2f {
    dp::Vector4f clip = cam3d->get_projection_matrix().get_matrix() * cam3d->get_view_matrix().get_matrix() * dp::Vector4f(world_pos, 1.0f);
    clip.x = (clip.x / clip.w) * 0.5 + 0.5;
    clip.y = (clip.y / clip.w) * 0.5 + 0.5;
    return { clip.x, clip.y };
};

// 월드 공간 점 -> 뷰 공간
auto ViewPoint = [this](const dp::Vector3f world_pos) -> dp::Vector3f {
    dp::Vector4f view = cam3d->get_view_matrix().get_matrix() * dp::Vector4f(world_pos, 1.0f);
    return dp::Vector3f(view);
};

// 만약 카메라보다 뒤에 있으면??
//  즉 z값이 양수라면??
//      -> 아마 고장날거임.
if (ViewPoint(target_point).z < 0.0f) {

    dp::Vector2f target_point_2d = dp::Vector2f(1280, 720) * ProjectPoint(target_point);
    // 스크린이면 y축 반전해야함.
    target_point_2d.y = 720 - target_point_2d.y;

    dp::Transform2D tp{};
    tp.set_position(target_point_2d);
    server->render_circle(
        get_current_projection_matrix_2d().get_matrix(),
        get_current_view_matrix_2d().get_matrix(),
        tp.get_matrix(),
        { 20.0f, 20.0f },
        dp::Cyan,
        dp::White
    );

    dp::Color4f modulate = dp::White;

    //// X-Z 평면에서, 내적으로, 
    //dp::Vector2f view_vec = { cos(cam3d->get_cam_rotation().yaw.as_radian()), sin(cam3d->get_cam_rotation().yaw.as_radian()) };
    //dp::Vector2f target_vec = dp::Vector2f(-target_point.z, target_point.x) - dp::Vector2f(-cam3d->get_world_position().z, cam3d->get_world_position().x);
    //target_vec = dp::normalize(target_vec);
    //float dott = dp::dot(view_vec, target_vec);
    //// 아까 걸렀으므로, 내적은 
    ////      일치할때 1, 
    ////      또는 90도 가까울때 0이 될 것임. (cos의 반환범위중 음수인 경우X)
    //modulate.a = dott;
    //std::cout << dott << "\n";

    auto view_vec = cam3d->get_view_matrix().get_front_vector();
    auto target_vec = dp::normalize(target_point - cam3d->get_view_matrix().get_position());

    float dott = dp::dot(view_vec, target_vec);
    modulate.a = dott;
    //std::cout << dott << "\n";

    // 내비게이션
    server->render_line(
        get_current_projection_matrix_2d().get_matrix(),
        get_current_view_matrix_2d().get_matrix(),
        dp::Transform2D().get_matrix(),
        { dp::Convert2DVectorTo(dp::CoordSystem::OpenGL, {640, 360}), 0.0f },
        dp::Vector3f(dp::Convert2DVectorTo(dp::CoordSystem::OpenGL, target_point_2d), 0.0f),
        4.0f,
        dp::White,
        modulate
    );
}

처음에는 뷰 공간 기준으로 카메라보다 뒤에 있으면 걍 안그리는 식으로 했는데 선이 갑자기 끊기는게 맘에 안들어서 modulate를 추가해 보았다.

알파값 결정으로 X, Z 평면으로만 해봤는데 또 pitch 변화에 대해서도 필요하다 생각해서

ViewMatrix3D에 get_front_vector() 인터페이스를 추가하고 걍 내적해서 구현해봤다.

여기다가 추가할 게 있다면 카메라 완전 뒤에 있는 경우, 스크린의 가장자리에 방향만 표시하기 (선 없이)

생각해보니까 저 projected point에다가 텍스트 그리면 스케일 고려 안해놓은 값싼? 빌보드 렌더링이 되는 거 아닌가?

굳이 내비게이션이 아니더라도 저 3D 점에 뭔가가 있단걸 표현하기 위해 사용 가능.

근데 벽에 가려진 경우 전에 구현해놨던 occlusion test, 특히 depth버퍼 써서 '한 점' 에 대한 가려짐 판단 쓰면 별거 없을듯.