0322
2026년 3월 22일
3D 네비게이션의 구현

이름만 보면 뭐 있어보이는데 별건 아니고
타이탄폴 스피드런 영상을 보다가 퀘스트가 뜬 순간, 쿼스트를 이행해야 하는 지점(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버퍼 써서 '한 점' 에 대한 가려짐 판단 쓰면 별거 없을듯.