[Java] Come convertire un punto 3D in proiezione prospettica 2D?


Answers

Penso che questo probabilmente risponda alla tua domanda. Ecco cosa ho scritto lì:

Ecco una risposta molto generale. Dì che la videocamera è a (Xc, Yc, Zc) e il punto che vuoi proiettare è P = (X, Y, Z). La distanza tra la telecamera e il piano 2D su cui si sta proiettando è F (quindi l'equazione del piano è Z-Zc = F). Le coordinate 2D di P proiettate sul piano sono (X ', Y').

Quindi, molto semplicemente:

X '= ((X - Xc) * (F / Z)) + Xc

Y '= ((Y - Yc) * (F / Z)) + Yc

Se la tua fotocamera è l'origine, allora questo semplifica:

X '= X * (F / Z)

Y '= Y * (F / Z)

Question

Attualmente sto lavorando con le curve e le superfici di Bezier per disegnare la famosa teiera dello Utah. Usando patch Bezier di 16 punti di controllo, sono stato in grado di disegnare la teiera e visualizzarla usando una funzione "da mondo a macchina fotografica" che dà la possibilità di ruotare la teiera risultante e attualmente sto usando una proiezione ortografica.

Il risultato è che ho una teiera "piatta", che è prevista in quanto lo scopo di una proiezione ortografica è di preservare le linee parallele.

Tuttavia, mi piacerebbe utilizzare una proiezione prospettica per dare profondità alla teiera. La mia domanda è: come si può prendere il vertice xyz 3D restituito dalla funzione 'world to camera' e convertirlo in una coordinata 2D. Sto volendo usare il piano di proiezione su z = 0 e consentire all'utente di determinare la lunghezza focale e la dimensione dell'immagine usando i tasti freccia sulla tastiera.

Sto programmando questo in java e ho impostato tutto il gestore di eventi di input, e ho anche scritto una classe matrix che gestisce la moltiplicazione della matrice di base. Ho letto wikipedia e altre risorse per un po ', ma non riesco a capire come si esegue questa trasformazione.




Per ottenere le coordinate corrette per la prospettiva, basta dividere per la coordinata z :

xc = x / z
yc = y / z

Quanto sopra funziona supponendo che la fotocamera sia a (0, 0, 0) e tu stia proiettando sul piano in z = 1 - hai bisogno di tradurre le co-ord relative alla camera altrimenti.

Ci sono alcune complicazioni per le curve, nella misura in cui la proiezione dei punti di una curva 3D di Bezier non ti darà in generale gli stessi punti del disegno di una curva Bezier 2D attraverso i punti proiettati.




La mia risposta precedente era sbagliata e spazzatura.

Ecco una ripetizione:

Guardando lo schermo dall'alto, ottieni l'asse x e z.
Guardando lo schermo dal lato, ottieni l'asse yez.

Calcola le lunghezze focali delle viste superiore e laterale, usando la trigonometria, che è la distanza tra l'occhio e il centro dello schermo, che è determinata dal campo visivo dello schermo. Questo rende la forma di due triangoli rettangoli indietro.

hw = screen_width / 2

hh = screen_height / 2

fl_top = hw / tan (θ / 2)

fl_side = hh / tan (θ / 2)


Quindi prendi la lunghezza focale media.

fl_average = (fl_top + fl_side) / 2


Ora calcola la nuova x e la nuova y con l'aritmetica di base, poiché il triangolo più grande formato dal punto 3d e il punto dell'occhio è congruente con il triangolo più piccolo formato dal punto 2d e dal punto dell'occhio.

x '= (x * fl_top) / (z + fl_top)

y '= (y * fl_top) / (z + fl_top)


O puoi semplicemente impostare

x '= x / (z + 1)

e

y '= y / (z + 1)




Grazie a @Mads Elvenheim per un codice di esempio corretto. Ho corretto gli errori di sintassi minori nel codice (solo alcuni problemi const e ovvi operatori mancanti). Inoltre, vicino e lontano hanno significati molto diversi in vs.

Per il tuo piacere, ecco la versione compilabile (MSVC2013). Divertiti. Considera che ho reso costante NEAR_Z e FAR_Z. Probabilmente non lo vorresti.

#include <vector>
#include <cmath>
#include <stdexcept>
#include <algorithm>

#define M_PI 3.14159

#define NEAR_Z 0.5
#define FAR_Z 2.5

struct Vector
{
    float x;
    float y;
    float z;
    float w;

    Vector() : x( 0 ), y( 0 ), z( 0 ), w( 1 ) {}
    Vector( float a, float b, float c ) : x( a ), y( b ), z( c ), w( 1 ) {}

    /* Assume proper operator overloads here, with vectors and scalars */
    float Length() const
    {
        return std::sqrt( x*x + y*y + z*z );
    }
    Vector& operator*=(float fac) noexcept
    {
        x *= fac;
        y *= fac;
        z *= fac;
        return *this;
    }
    Vector  operator*(float fac) const noexcept
    {
        return Vector(*this)*=fac;
    }
    Vector& operator/=(float div) noexcept
    {
        return operator*=(1/div);   // avoid divisions: they are much
                                    // more costly than multiplications
    }

    Vector Unit() const
    {
        const float epsilon = 1e-6;
        float mag = Length();
        if (mag < epsilon) {
            std::out_of_range e( "" );
            throw e;
        }
        return Vector(*this)/=mag;
    }
};

inline float Dot( const Vector& v1, const Vector& v2 )
{
    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}

class Matrix
{
public:
    Matrix() : data( 16 )
    {
        Identity();
    }
    void Identity()
    {
        std::fill( data.begin(), data.end(), float( 0 ) );
        data[0] = data[5] = data[10] = data[15] = 1.0f;
    }
    float& operator[]( size_t index )
    {
        if (index >= 16) {
            std::out_of_range e( "" );
            throw e;
        }
        return data[index];
    }
    const float& operator[]( size_t index ) const
    {
        if (index >= 16) {
            std::out_of_range e( "" );
            throw e;
        }
        return data[index];
    }
    Matrix operator*( const Matrix& m ) const
    {
        Matrix dst;
        int col;
        for (int y = 0; y<4; ++y) {
            col = y * 4;
            for (int x = 0; x<4; ++x) {
                for (int i = 0; i<4; ++i) {
                    dst[x + col] += m[i + col] * data[x + i * 4];
                }
            }
        }
        return dst;
    }
    Matrix& operator*=( const Matrix& m )
    {
        *this = (*this) * m;
        return *this;
    }

    /* The interesting stuff */
    void SetupClipMatrix( float fov, float aspectRatio )
    {
        Identity();
        float f = 1.0f / std::tan( fov * 0.5f );
        data[0] = f*aspectRatio;
        data[5] = f;
        data[10] = (FAR_Z + NEAR_Z) / (FAR_Z- NEAR_Z);
        data[11] = 1.0f; /* this 'plugs' the old z into w */
        data[14] = (2.0f*NEAR_Z*FAR_Z) / (NEAR_Z - FAR_Z);
        data[15] = 0.0f;
    }

    std::vector<float> data;
};


inline Vector operator*( const Vector& v, Matrix& m )
{
    Vector dst;
    dst.x = v.x*m[0] + v.y*m[4] + v.z*m[8] + v.w*m[12];
    dst.y = v.x*m[1] + v.y*m[5] + v.z*m[9] + v.w*m[13];
    dst.z = v.x*m[2] + v.y*m[6] + v.z*m[10] + v.w*m[14];
    dst.w = v.x*m[3] + v.y*m[7] + v.z*m[11] + v.w*m[15];
    return dst;
}

typedef std::vector<Vector> VecArr;
VecArr ProjectAndClip( int width, int height, const VecArr& vertex )
{
    float halfWidth = (float)width * 0.5f;
    float halfHeight = (float)height * 0.5f;
    float aspect = (float)width / (float)height;
    Vector v;
    Matrix clipMatrix;
    VecArr dst;
    clipMatrix.SetupClipMatrix( 60.0f * (M_PI / 180.0f), aspect);
    /*  Here, after the perspective divide, you perform Sutherland-Hodgeman clipping
    by checking if the x, y and z components are inside the range of [-w, w].
    One checks each vector component seperately against each plane. Per-vertex
    data like colours, normals and texture coordinates need to be linearly
    interpolated for clipped edges to reflect the change. If the edge (v0,v1)
    is tested against the positive x plane, and v1 is outside, the interpolant
    becomes: (v1.x - w) / (v1.x - v0.x)
    I skip this stage all together to be brief.
    */
    for (VecArr::const_iterator i = vertex.begin(); i != vertex.end(); ++i) {
        v = (*i) * clipMatrix;
        v /= v.w; /* Don't get confused here. I assume the divide leaves v.w alone.*/
        dst.push_back( v );
    }

    /* TODO: Clipping here */

    for (VecArr::iterator i = dst.begin(); i != dst.end(); ++i) {
        i->x = (i->x * (float)width) / (2.0f * i->w) + halfWidth;
        i->y = (i->y * (float)height) / (2.0f * i->w) + halfHeight;
    }
    return dst;
}
#pragma once



So che è un vecchio argomento ma la tua illustrazione non è corretta, il codice sorgente imposta correttamente la matrice del clip.

[fov * aspectRatio][        0        ][        0              ][        0       ]
[        0        ][       fov       ][        0              ][        0       ]
[        0        ][        0        ][(far+near)/(far-near)  ][(2*near*far)/(near-far)]
[        0        ][        0        ][        1              ][        0       ]

qualche aggiunta alle tue cose:

Questa matrice di clip funziona solo se si proietta su un piano 2D statico se si desidera aggiungere movimento e rotazione della telecamera:

viewMatrix = clipMatrix * cameraTranslationMatrix4x4 * cameraRotationMatrix4x4;

questo ti permette di ruotare il piano 2D e spostarlo in giro ..-