Jak poprawnie wyświetlić punkt za kamerą?

10

Tworzę grę 3D, w której umieszczam wykrzyknik nad interesującymi miejscami.

Aby dowiedzieć się, gdzie na ekranie 2D powinienem umieścić znacznik, ręcznie rzutuję punkt 3D, w którym powinien znajdować się znacznik.

To wygląda tak:

Markery wyglądają niesamowicie

Wygląda całkiem nieźle. Kiedy znacznik znajduje się poza ekranem, po prostu przycinam współrzędne, aby pasowały do ​​ekranu. To wygląda tak:

Markery wyglądają jeszcze bardziej niesamowicie

Jak dotąd pomysł idzie całkiem nieźle. Jednak gdy punkty zainteresowania znajdują się za kamerą, powstałe współrzędne X, Y są odwrócone (jak w dodatnich / ujemnych), a znacznik pojawia się w przeciwległym rogu ekranu, tak jak poniżej:

Markery nie są takie niesamowite

(Rzutowany następnie zaciśnięty punkt jest końcem markera. Nie przeszkadza to w obrocie markera)

Ma to sens, ponieważ współrzędne za ścięciem są odwrócone w X i Y. Więc robię to, aby odwrócić współrzędne, gdy znajdują się za kamerą. Jednak wciąż nie wiem, jaki jest dokładnie warunek, w którym należy odwrócić współrzędne.

Tak właśnie wygląda mój kod projekcji (w C # z 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);
    }

Jak widać, moim obecnym pomysłem jest odwrócenie współrzędnych, gdy współrzędne Z lub W są ujemne. Działa prawie przez większość czasu, ale wciąż istnieją pewne bardzo specyficzne lokalizacje kamer, w których nie działa. W szczególności ten punkt pokazuje jedną współrzędną działającą, a drugą nie (poprawna lokalizacja powinna być w prawym dolnym rogu):

Markery na wpół niesamowite

Próbowałem odwrócić, gdy:

  • Z jest negatywny (to jest dla mnie najbardziej sensowne)
  • W jest ujemny (nie rozumiem znaczenia ujemnej wartości W)
  • Albo Zalbo Wjest negatywne (co obecnie działa przez większość czasu)
  • Zi Wmają inny znak, alias: Z / W < 0(ma dla mnie sens. choć nie działa)

Ale wciąż nie znalazłem spójnego sposobu, w jaki wszystkie moje punkty są poprawnie rzutowane.

Jakieś pomysły?

Panda Piżama
źródło

Odpowiedzi:

2

Co powiesz na zrobienie tego trochę inaczej?

Zacznij jak zwykle i wyrenderuj wszystkie znaczniki w rzutni. Jeśli znacznik znajduje się poza oknem ekranu - kontynuuj:

  1. Uzyskaj pozycje znacznika A
  2. Ustaw pozycję kamery B(może nieco przed nią)
  3. Oblicz wektor 3D ABmiędzy tymi dwoma
  4. Konwertuj i znormalizuj ten wektor do granic ekranu 2D ( Abędzie w środku widoku i Buderzy w granicę widoku)

wprowadź opis zdjęcia tutaj

Kolorowe linie to ABwektory. Cienkie czarne linie mają wysokości znaków. Po prawej stronie jest ekran 2D

  1. Może dodać zasadę, że wszystko za kamerą zawsze przechodzi do dolnej krawędzi
  2. Narysuj znacznik w przestrzeni ekranu w odniesieniu do rozmiarów znaczników i zakładek

Być może trzeba spróbować, aby sprawdzić, czy znacznik opuszczający rzutnię przeskakuje między pozycjami. Jeśli tak się stanie, możesz spróbować przesunąć pozycję, gdy znacznik zbliża się do granicy rzutni.

Kromster
źródło
Jak dokładnie działałby ten krok 4?
Panda Pajama
@PandaPajama: Dodałem trochę do odpowiedzi. Czy teraz jest bardziej jasne?
Kromster
Nie całkiem. Jak „konwertujesz i normalizujesz” wektor? Jeśli zastosujesz macierz projekcji, robisz to samo, co ja
Panda Pajama
@PandaPajama: O ile rozumiem, robisz to w przestrzeni kamery (stąd negatywny problem ZW). Proponuję zrobić to w przestrzeni świata, a dopiero potem przekonwertować na przestrzeń ekranu.
Kromster
Ale obliczając AB, czy nie przekształcam pozycji docelowej ze współrzędnych światowych na współrzędne kamery?
Panda Pajama
1

Biorąc pod uwagę trzy wektory [camera position], [camera direction]i [object position]. Oblicz iloczyn skalarny dla[camera direction] i [object position]-[camera position]. Jeśli wynik jest ujemny, obiekt znajduje się za kamerą.

Biorąc pod uwagę, że dobrze zrozumiałem twój kod, będzie to:

if((PositionX - Camera.PositionX) * Camera.LookAtX
    + (PositionY - Camera.PositionY) * Camera.LookAtY
    + (PositionZ - Camera.PositionZ) * Camera.LookAtZ
    < 0)
aaaaaaaaaaaa
źródło
Yjest PositionY + (y * Scaling). Spróbuję dziś wieczorem
Panda Pajama
Dzięki temu mogę naprawdę wiedzieć, kiedy punkt znajduje się za kamerą. Jednak nie uniemożliwia to projekcji osiągnięcie osobliwości przy Z = 0.
Panda Pajama
@PandaPajama Czy to praktyczny problem? Prawdopodobieństwo Zbycia dokładnie 0powinno być bardzo małe, większość graczy nigdy go nie doświadczy, a wpływ rozgrywki w spodziewanych kilku przypadkach jest znikomą usterką wizualną pojedynczej klatki. Powiedziałbym, że twój czas lepiej spędzić gdzie indziej.
aaaaaaaaaaaa
0

Tylko test (przewidywany W <0), a nie (Z <0 || W <0).

W niektórych formułach przestrzeni klipu ( kaszel OpenGL kaszel ) widoczny zakres obejmuje dodatnie i ujemne wartości Z, podczas gdy W jest zawsze dodatnie przed kamerą, a ujemne z tyłu.

jaybird
źródło
Próbowałem tylko z W <0, ale wciąż są miejsca, w których jest niepoprawnie wyświetlany.
Panda Pajama,
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;

I usuń tę część

// Invert X and Y when behind the camera
if (projectedVector.Z < 0 || projectedVector.W < 0)
{
    screenX = -screenX;
    screenY = -screenY;
}
BiiG
źródło
-2

Wypróbuj billboard, który automatycznie sprawi, że zdjęcie będzie skierowane w stronę aparatu i automatycznie narysuje zdjęcie na ekranie w odpowiednim miejscu

Michael Langford
źródło
3
Czy przeczytałeś pytanie?
Kromster
Przepraszam, pozwól mi wyjaśnić - Możliwe jest wzięcie wyniku billboardu, pozbycie się skalowania (więc jest 2D, ale podąża za pozycją 3d), a następnie trzymaj go w granicach ekranu za pomocą matematyki.
Michael Langford
Pytanie dotyczy konkretnie „a następnie trzymaj je w granicach ekranu za pomocą matematyki matematycznej
Kromster