Как правильно спроецировать точку за камерой?

10

Я делаю 3D-игру, в которой я ставлю восклицательный знак над достопримечательностями.

Чтобы узнать, где на 2D-экране я должен поместить свой маркер, я вручную проецирую 3D-точку, где должен быть маркер.

Это выглядит так:

Маркеры выглядят потрясающе

Выглядит довольно хорошо Когда маркер находится за пределами экрана, я просто обрезаю координаты, чтобы они помещались на экране. Это выглядит так:

Маркеры выглядят еще более круто

Пока идея идёт довольно хорошо. Однако, когда точки интереса находятся за камерой, результирующие координаты X, Y инвертируются (как в положительном / отрицательном), и я получаю маркер для отображения в противоположном углу экрана, например так:

Маркеры не такие крутые

(Спроецированная, затем зажатая точка - это кончик маркера. Не обращайте внимания на вращение маркера)

Это имеет смысл, так как координаты за усечкой инвертированы в X и Y. Так что я делаю, чтобы инвертировать координаты, когда они находятся за камерой. Тем не менее, я до сих пор не знаю, что именно является условием, когда координаты должны быть инвертированы.

Вот как выглядит мой код проекции (в C # с SharpDX):

    public override PointF ProjectPosition(float viewportWidth, float viewportHeight, float y)
    {
        var projectionMatrix = Matrix.PerspectiveFovRH(GetCalibratedFOV(Camera.FOV, viewportWidth, viewportHeight), viewportWidth / viewportHeight, Camera.Near, Camera.Far);
        var viewMatrix = Matrix.LookAtRH(new Vector3(Camera.PositionX, Camera.PositionY, Camera.PositionZ), new Vector3(Camera.LookAtX, Camera.LookAtY, Camera.LookAtZ), Vector3.UnitY);
        var worldMatrix = Matrix.RotationY(Rotation) * Matrix.Scaling(Scaling) * Matrix.Translation(PositionX, PositionY, PositionZ);
        var worldViewProjectionMatrix = worldMatrix * viewMatrix * projectionMatrix;

        Vector4 targetVector = new Vector4(0, y, 0, 1);
        Vector4 projectedVector = Vector4.Transform(targetVector, worldViewProjectionMatrix);

        float screenX = (((projectedVector.X / projectedVector.W) + 1.0f) / 2.0f) * viewportWidth;
        float screenY = ((1.0f - (projectedVector.Y / projectedVector.W)) / 2.0f) * viewportHeight;
        float screenZ = projectedVector.Z / projectedVector.W;

        // Invert X and Y when behind the camera
        if (projectedVector.Z < 0 ||
            projectedVector.W < 0)
        {
            screenX = -screenX;
            screenY = -screenY;
        }

        return new PointF(screenX, screenY);
    }

Как вы можете видеть, моя текущая идея - инвертировать координаты, когда координаты Z или W отрицательны. Это работает - большую часть времени, но все еще есть некоторые очень специфические местоположения камеры, где это не работает. В частности, эта точка показывает, что одна координата работает, а другая нет (правильное местоположение должно быть внизу справа):

Маркеры наполовину потрясающие

Я пытался инвертировать, когда:

  • Z отрицателен (это то, что имеет наибольшее значение для меня)
  • W отрицательно (я не понимаю значения отрицательного значения W)
  • Либо Zили Wотрицательно (что в настоящее время работает большую часть времени)
  • Zи Wимеют разные признаки, иначе: Z / W < 0(имеет смысл для меня. хотя не работает)

Но до сих пор не нашли последовательного способа, с помощью которого все мои очки правильно проецируются.

Любые идеи?

Панда Пижама
источник

Ответы:

2

Как насчет того, чтобы сделать это немного по-другому?

Начните как обычно и визуализируйте все маркеры в области просмотра. Если маркер находится вне области просмотра - продолжайте:

  1. Получить позиции маркера A
  2. Получить положение камеры B(может быть, немного впереди нее)
  3. Рассчитать 3D вектор ABмежду этими двумя
  4. Преобразовать и нормализовать этот вектор в 2D-границы экрана ( Aбудет в центре вида и Bпопадет на границу видов)

введите описание изображения здесь

Цветные линии - это ABвекторы. Тонкие черные линии - это высота персонажей. Справа 2D экран

  1. Может быть, добавить правило, что все, что за камерой всегда идет к нижнему краю
  2. Нарисуйте маркер в пространстве экрана относительно размеров маркеров и перекрытий

Возможно, вам придется попробовать это, чтобы увидеть, будет ли маркер, выходящий из области просмотра, перемещаться между позициями. Если это произойдет, вы можете попробовать изменить положение, когда маркер приближается к границе области просмотра.

Kromster
источник
Как именно этот шаг 4 будет работать?
Панда Пижама
@PandaPajama: я немного добавил к ответу. Это более понятно сейчас?
Кромстер
На самом деле, нет. Как вы «конвертируете и нормализуете» вектор? Если это происходит с применением матрицы проекции, то вы делаете то же самое, что и я
Panda Pajama
@PandaPajama: Насколько я понимаю, вы делаете это в пространстве камеры (отсюда и отрицательная проблема ZW). Я предлагаю сделать это в мировом пространстве и только потом конвертировать в экранное пространство.
Кромстер
Но, вычисляя AB, я не преобразовываю целевую позицию от мировых координат до координат камеры?
Панда Пижама
1

Учитывая три вектора [camera position], [camera direction]а [object position]. Вычислить скалярное произведение [camera direction]и [object position]-[camera position]. Если результат отрицательный, объект находится за камерой.

Учитывая, что я правильно понял ваш код, это будет:

if((PositionX - Camera.PositionX) * Camera.LookAtX
    + (PositionY - Camera.PositionY) * Camera.LookAtY
    + (PositionZ - Camera.PositionZ) * Camera.LookAtZ
    < 0)
AAAAAAAAAAAA
источник
Yесть PositionY + (y * Scaling). Я попробую сегодня вечером
Panda Pajama
С этим я действительно могу знать, когда точка находится за камерой. Однако это не мешает проекции достичь сингулярности при Z = 0.
Панда Пижама
@PandaPajama Это практическая проблема? Вероятность Zбыть точно 0должна быть очень мала, большинство игроков никогда не будут испытывать ее, а влияние геймплея в ожидаемых нескольких случаях - незначительный визуальный сбой одного кадра. Я бы сказал, что ваше время лучше провести в другом месте.
аааааааааааа
0

Только тест (прогноз. W <0), но не (Z <0 || W <0).

В некоторых космическом клипе рецептур ( кашель OpenGL от кашля ), видимый диапазон включает положительные и отрицательные значения Z, тогда как W всегда положителен перед камерой и отрицательных позади.

Jaybird
источник
Я пробовал только с W <0, но все еще есть места, где это неправильно проецируется.
Панда Пижама
0
float screenX = (((projectedVector.X / abs(projectedVector.W)) + 1.0f) / 2.0f) * viewportWidth;
float screenY = ((1.0f - (projectedVector.Y / abs(projectedVector.W))) / 2.0f) * viewportHeight;

И удалить эту часть

// Invert X and Y when behind the camera
if (projectedVector.Z < 0 || projectedVector.W < 0)
{
    screenX = -screenX;
    screenY = -screenY;
}
BiiG
источник
-2

Попробуйте рекламный щит, который автоматически сделает изображение лицом к камере и автоматически нарисует изображение на экране в нужном месте.

Майкл Лэнгфорд
источник
3
Вы читали вопрос?
Кромстер
Извините, позвольте мне объяснить - можно взять выходные данные рекламного щита, избавиться от масштабирования (так что это 2D, но следует 3D-позиции), а затем держать его в пределах экрана, используя некоторую матричную математику.
Майкл Лэнгфорд
Вопрос конкретно о том, «а затем держать его в границах экрана, используя некоторую матричную математику »
Кромстер