Як правильно проектувати точку за камерою?


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. Перетворіть та нормалізуйте цей вектор у двовимі межі екрана ( Aбуде у центрі перегляду та Bбуде торкатися межі перегляду)

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

Кольорові лінії - це ABвектори. Тонкі чорні лінії - це висота символів. Праворуч - 2D екран

  1. Можливо, додамо правило, що все за камерою завжди йде до нижнього краю
  2. Намалюйте маркер у просторі екрану з урахуванням розмірів та перекриттів маркерів

Можливо, вам доведеться спробувати це, щоб побачити, чи не буде маркер, який виходить з вікна перегляду, переходити між позиціями. Якщо це трапиться, ви можете спробувати позиціонувати позицію, коли маркер наближається до межі вікна перегляду.


Як саме працював би цей крок 4?
Піжама Panda

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

Yє PositionY + (y * Scaling). Я спробую сьогодні спробувати
піжама Panda

З цим я справді можу знати, коли точка знаходиться за камерою. Однак це не заважає проекції досягти сингулярності при Z = 0.
Піжама Панда

@PandaPajama Це практичне питання? Вірогідність Zбути точно 0такою має бути дуже малою, більшість гравців цього ніколи не відчують, а ігровий вплив у очікуваних кількох випадках - це незначний візуальний глюк з одним кадром. Я б сказав, що ваш час краще проводити в іншому місці.
ааааааааааа

0

Тільки тест (прогнозується. W <0), а не (Z <0 || W <0).

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


Я намагався лише з W <0, але все ж є місця, де це неправильно спроектовано.
Піжама Panda

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

-2

Спробуйте рекламний щит, який автоматично зробить фотографію обличчям до камери та автоматично виведе малюнок на екран у потрібному місці


3
Ви читали питання?
Кромстер

Вибачте, дозвольте мені пояснити - Можна взяти висновок білборда, позбутися від масштабування (значить, це 2D, але дотримується 3d-позиції), а потім тримати його в межах екрана, використовуючи деяку матричну математику.
Майкл Ленгфорд

Питання стосується конкретно "а потім тримати його в межах екрана, використовуючи деяку матричну математику "
Кромстер,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.