¿Cómo proyecto correctamente un punto detrás de la cámara?

10

Estoy haciendo un juego en 3D en el que pongo un marcador de exclamación sobre los puntos de interés.

Para averiguar en qué lugar de la pantalla 2D debo colocar mi marcador, estoy proyectando manualmente el punto 3D donde debería estar el marcador.

Se parece a esto:

Los marcadores se ven increíbles

Se ve bastante bien. Cuando el marcador está fuera de la pantalla, simplemente recorto las coordenadas para que quepan en la pantalla. Se parece a esto:

Los marcadores se ven aún más increíbles

Hasta ahora la idea está yendo bastante bien. Sin embargo, cuando los puntos de interés están detrás de la cámara, las coordenadas X, Y resultantes se invierten (como en positivo / negativo), y obtengo el marcador para que aparezca en la esquina opuesta de la pantalla, así:

Los marcadores no son tan geniales

(El punto proyectado y luego sujeto es la punta del marcador. No importa la rotación del marcador)

Tiene sentido, ya que las coordenadas detrás del tronco están invertidas en X e Y. Entonces, lo que estoy haciendo es invertir las coordenadas cuando están detrás de la cámara. Sin embargo, todavía no sé cuál es exactamente la condición cuando las coordenadas deben invertirse.

Así es actualmente mi código de proyección (en C # con 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);
    }

Como puede ver, mi idea actual es invertir las coordenadas cuando las coordenadas Z o W son negativas. Funciona la mayor parte del tiempo, pero todavía hay algunas ubicaciones de cámara muy específicas donde no funciona. En particular, este punto muestra una coordenada funcionando y la otra no (la ubicación correcta debe ser la esquina inferior derecha):

Marcadores medio asombrosos

He intentado invertir cuando:

  • Z es negativo (esto es lo que tiene más sentido para mí)
  • W es negativo (no entiendo el significado de un valor W negativo)
  • Ya sea Zo Wes negativo (lo que está funcionando actualmente la mayor parte del tiempo)
  • Zy Wson de signo diferente, alias: Z / W < 0(tiene sentido para mí, aunque no funciona)

Pero todavía no he encontrado una manera consistente con la cual todos mis puntos se proyecten correctamente.

¿Algunas ideas?

Pijama Panda
fuente

Respuestas:

2

¿Qué tal hacerlo un poco diferente?

Comience como de costumbre y renderice todos los marcadores dentro de la ventana gráfica. Si un marcador está fuera de la ventana gráfica, proceda:

  1. Obtener posiciones del marcador A
  2. Obtener la posición de la cámara B(tal vez un poco delante de ella)
  3. Calcular el vector 3D ABentre estos dos
  4. Convierta y normalice ese vector en límites de pantalla 2D ( Aestará en el centro de la vista y Balcanzará un borde de vistas)

ingrese la descripción de la imagen aquí

Las líneas de color son ABvectores. Las líneas negras finas son alturas de caracteres. A la derecha está la pantalla 2D

  1. Tal vez agregue una regla, que todo detrás de la cámara siempre va al borde inferior
  2. Dibuje el marcador en el espacio de la pantalla con respecto a los tamaños y las superposiciones de los marcadores

Es posible que deba probarlo para ver si el marcador que sale de la ventana saltará entre las posiciones. Si eso sucede, puede intentar saltar la posición cuando el marcador se acerque al borde de la ventana gráfica.

Kromster
fuente
¿Cómo funcionaría exactamente ese paso 4?
Panda Pyjama
@PandaPajama: He agregado un poco a la respuesta. ¿Está más claro ahora?
Kromster
Realmente no. ¿Cómo estás "convirtiendo y normalizando" el vector? Si eso es aplicando una matriz de proyección, entonces estás haciendo lo mismo que yo
Panda Pyjama
@PandaPajama: Según tengo entendido, lo estás haciendo en el espacio de la cámara (de ahí el problema negativo de ZW). Sugiero hacerlo en el espacio mundial y solo luego convertirlo en espacio de pantalla.
Kromster
Pero, al calcular AB, ¿no estoy convirtiendo la posición de destino de las coordenadas mundiales a las coordenadas de la cámara?
Panda Pyjama
1

Dados los tres vectores [camera position], [camera direction]y [object position]. Calcule el producto escalar de [camera direction]y [object position]-[camera position]. Si el resultado es negativo, el objeto está detrás de la cámara.

Dado que entendí bien su código, sería:

if((PositionX - Camera.PositionX) * Camera.LookAtX
    + (PositionY - Camera.PositionY) * Camera.LookAtY
    + (PositionZ - Camera.PositionZ) * Camera.LookAtZ
    < 0)
aaaaaaaaaaaa
fuente
Yes PositionY + (y * Scaling). Lo intentaré esta noche
Panda Pyjama
Con esto puedo saber cuando el punto está detrás de la cámara. Sin embargo, esto no impide que la proyección alcance una singularidad en Z = 0.
Panda Pyjama
@PandaPajama ¿Es un problema práctico? La probabilidad de Zser exactamente 0debe ser muy pequeña, la mayoría de los jugadores nunca lo experimentarán, y el impacto del juego en los pocos casos esperados es un fallo visual insignificante de un solo cuadro. Yo diría que tu tiempo se gasta mejor en otro lado.
aaaaaaaaaaaa
0

Solo prueba (proyectado. W <0), no (Z <0 || W <0).

En algunas formulaciones de espacio de clip ( tos OpenGL tos ), el rango visible incluye valores Z positivos y negativos, mientras que W siempre es positivo delante de la cámara y negativo detrás.

Jaybird
fuente
Lo intenté solo con W <0, pero todavía hay lugares donde se proyecta incorrectamente.
Panda Pyjama
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;

Y quita esta parte

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

Pruebe con una valla publicitaria, que hará que la imagen quede automáticamente frente a la cámara, y la dibujará automáticamente en la pantalla en la ubicación adecuada

Michael Langford
fuente
3
¿Has leído la pregunta?
Kromster
Lo siento, permítame explicarlo: es posible tomar la salida de una valla publicitaria, deshacerse de la escala (por lo que es 2D pero sigue la posición 3d) y luego mantenerla dentro de los límites de la pantalla usando algunas matemáticas de matriz.
Michael Langford
La pregunta es específicamente sobre "y luego manténgalo dentro de los límites de la pantalla usando algunas matemáticas de matriz "
Kromster