Ho bisogno del componente 'w' nella mia classe Vector?


21

Supponiamo di scrivere un codice a matrice che gestisca la rotazione, la traduzione ecc. Per lo spazio 3d.

Ora le matrici di trasformazione devono essere 4x4 per adattarsi al componente di traduzione.

Tuttavia, in realtà non è necessario memorizzare un wcomponente nel vettore, vero?

Anche nella divisione prospettica, puoi semplicemente calcolare e archiviare wal di fuori del vettore e dividere la prospettiva prima di tornare dal metodo.

Per esempio:

// post multiply vec2=matrix*vector
Vector operator*( const Matrix & a, const Vector& v )
{
  Vector r ;
  // do matrix mult
  r.x = a._11*v.x + a._12*v.y ...

  real w = a._41*v.x + a._42*v.y ...

  // perspective divide
  r /= w ;

  return r ;
}

C'è un punto nella memorizzazione wnella classe Vector?


2
Questa non è l'implementazione per una normale moltiplicazione di matrici, la divisione della prospettiva non vi appartiene. Inoltre è abbastanza fuorviante, perché vengono evidenziate le parti sbagliate del calcolo. Se vuoi scoprire a cosa serve il componente w, guarda l'implementazione completa, quindi vedi che l'ultima riga / colonna (la parte di traduzione) della matrice viene applicata solo se il componente w è 1, cioè per punti. Dovresti evidenziare quelle parti: r.x = ... + a._14*v.w; r.y = ... + a._24*v.w; r.z = ... + a._34*v.w; r.w = ... + a._44*v.w;guarda la mia risposta per i dettagli
Maik Semder,

Risposte:


27

EDIT Dichiarazione di non responsabilità : per comodità in questa risposta i vettori con w == 0 sono chiamati vettori e con w == 1 sono chiamati punti. Sebbene, come ha sottolineato FxIII, questa non è una terminologia matematicamente corretta. Tuttavia, poiché il punto della risposta non è la terminologia, ma la necessità di distinguere entrambi i tipi di vettori, mi atterrò ad esso. Per motivi pratici questa convenzione è ampiamente utilizzata nello sviluppo di giochi.


Non è possibile distinguere tra vettori e punti senza un componente 'w'. È 1 per punti e 0 per vettori.

Se i vettori vengono moltiplicati con una matrice di trasformazione affine 4x4 che ha una traduzione nell'ultima riga / colonna, anche il vettore verrebbe tradotto, il che è sbagliato, solo i punti devono essere tradotti. Lo zero nel componente 'w' di un vettore si occupa di questo.

Evidenziando questa parte della moltiplicazione matrice-vettore si rende più chiaro:

    r.x = ... + a._14 * v.w; 
    r.y = ... + a._24 * v.w; 
    r.z = ... + a._34 * v.w; 
    r.w = ... + a._44 * v.w;

a._14, a._24 and a._34 is the translational part of the affine matrix.
Without a 'w' component one has to set it implicitly to 0 (vector) or to 1 (point) 

Vale a dire che sarebbe sbagliato tradurre un vettore, ad esempio un asse di rotazione, il risultato è semplicemente sbagliato. Avendo il suo quarto componente zero è ancora possibile utilizzare la stessa matrice che trasforma i punti per trasformare l'asse di rotazione e il risultato sarà valido e la sua lunghezza è preservata fintanto che non c'è scala nella matrice. Questo è il comportamento che desideri per i vettori. Senza il 4 ° componente dovresti creare 2 matrici (o 2 diverse funzioni di moltiplicazione con un 4 ° parametro implicito. Ed effettuare 2 diverse chiamate di funzione per punti e vettori.

Per utilizzare i registri vettoriali delle moderne CPU (SSE, Altivec, SPU) devi comunque passare 4x float a 32 bit (è un registro a 128 bit), inoltre devi occuparti dell'allineamento, di solito 16 byte. Quindi non hai comunque la possibilità di proteggere lo spazio per il 4 ° componente.


EDIT: la risposta alla domanda è sostanzialmente

  1. Memorizzare il componente w: 1 per le posizioni e 0 per i vettori
  2. Oppure chiama diverse funzioni di moltiplicazione matrice-vettore e passa implicitamente il componente 'w' scegliendo una delle due funzioni

Bisogna sceglierne uno, non è possibile memorizzare solo {x, y, z} e usare ancora una sola funzione di moltiplicazione matrice-vettore. XNA ad esempio usa quest'ultimo approccio avendo 2 funzioni Transform nella sua classe Vector3 , chiamate TransformeTransformNormal

Ecco un esempio di codice che mostra entrambi gli approcci e dimostra la necessità di distinguere entrambi i tipi di vettori in 1 dei 2 modi possibili. Sposteremo un'entità di gioco con una posizione e una direzione di sguardo nel mondo trasformandola con una matrice. Se non utilizziamo il componente 'w', non possiamo più usare la stessa moltiplicazione matrice-vettore, come dimostra questo esempio. Se lo facciamo comunque, avremo una risposta sbagliata per il look_dirvettore trasformato :

#include <cstdio>
#include <cmath>

struct vector3
{
    vector3() {}
    vector3(float _x, float _y, float _z) { x = _x; y = _y; z = _z; }
    float x, y, z;    
};

struct vector4
{
    vector4() {}
    vector4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; }
    float x, y, z, w;
};

struct matrix
{
    // convenience column accessors
    vector4&        operator[](int col)         { return cols[col]; }
    const vector4&  operator[](int col) const   { return cols[col]; }
    vector4 cols[4];
};

// since we transform a vector that stores the 'w' component, 
// we just need this one matrix-vector multiplication
vector4 operator*( const matrix &m, const vector4 &v )
{
    vector4 ret;
    ret.x = v.x * m[0].x + v.y * m[1].x + v.z * m[2].x + v.w * m[3].x;
    ret.y = v.x * m[0].y + v.y * m[1].y + v.z * m[2].y + v.w * m[3].y;
    ret.z = v.x * m[0].z + v.y * m[1].z + v.z * m[2].z + v.w * m[3].z;
    ret.w = v.x * m[0].w + v.y * m[1].w + v.z * m[2].w + v.w * m[3].w;
    return ret;
}

// if we don't store 'w' in the vector we need 2 different transform functions
// this to transform points (w==1), i.e. positions
vector3 TransformV3( const matrix &m, const vector3 &v )
{
    vector3 ret;
    ret.x = v.x * m[0].x + v.y * m[1].x + v.z * m[2].x + 1.0f * m[3].x;
    ret.y = v.x * m[0].y + v.y * m[1].y + v.z * m[2].y + 1.0f * m[3].y;
    ret.z = v.x * m[0].z + v.y * m[1].z + v.z * m[2].z + 1.0f * m[3].z;
    return ret;
}

// and this one is to transform vectors (w==0), like a direction-vector
vector3 TransformNormalV3( const matrix &m, const vector3 &v )
{
    vector3 ret;
    ret.x = v.x * m[0].x + v.y * m[1].x + v.z * m[2].x + 0.0f * m[3].x;
    ret.y = v.x * m[0].y + v.y * m[1].y + v.z * m[2].y + 0.0f * m[3].y;
    ret.z = v.x * m[0].z + v.y * m[1].z + v.z * m[2].z + 0.0f * m[3].z;
    return ret;
}

// some helpers to output the results
void PrintV4(const char *msg, const vector4 &p )  { printf("%-15s: %10.6f %10.6f %10.6f %10.6f\n",  msg, p.x, p.y, p.z, p.w ); }
void PrintV3(const char *msg, const vector3 &p )  { printf("%-15s: %10.6f %10.6f %10.6f\n",         msg, p.x, p.y, p.z); }

#define STORE_W     1

int main()
{
    // suppose we have a "position" of an entity and its 
    // look direction "look_dir" which is a unit vector

    // we will move this entity in the world

    // the entity will be moved in the world by a translation 
    // in x+5 and a rotation of 90 degrees around the y-axis 
    // let's create that matrix first

    // the rotation angle, 90 degrees in radians
    float a = 1.570796326794896619f;
    matrix moveEntity;
    moveEntity[0] = vector4( cos(a), 0.0f, sin(a), 0.0f);
    moveEntity[1] = vector4(   0.0f, 1.0f,   0.0f, 0.0f);
    moveEntity[2] = vector4(-sin(a), 0.0f, cos(a), 0.0f);
    moveEntity[3] = vector4(   5.0f, 0.0f,   0.0f, 1.0f);

#if STORE_W

    vector4 position(0.0f, 0.0f, 0.0f, 1.0f);
    // entity is looking towards the positive x-axis
    vector4 look_dir(1.0f, 0.0f, 0.0f, 0.0f);

    // move the entity using the matrix
    // we can use the same function for the matrix-vector multiplication to transform 
    // the position and the unit vector since we store 'w' in the vector
    position = moveEntity * position;
    look_dir = moveEntity * look_dir;

    PrintV4("position", position);
    PrintV4("look_dir", look_dir);

#else

    vector3 position(0.0f, 0.0f, 0.0f);
    // entity is looking towards the positive x-axis
    vector3 look_dir(1.0f, 0.0f, 0.0f);

    // move the entity using the matrix
    // we have to call 2 different transform functions one to transform the position 
    // and the other one to transform the unit-vector since we don't 
    // store 'w' in the vector
    position = TransformV3(moveEntity, position);
    look_dir = TransformNormalV3(moveEntity, look_dir);

    PrintV3("position", position);
    PrintV3("look_dir", look_dir);

#endif

    return 0;
}

Stato entità iniziale:

position       :   0.000000   0.000000   0.000000   1.000000
look_dir       :   1.000000   0.000000   0.000000   0.000000

Ora una trasformazione con una traslazione di x + 5 e una rotazione di 90 gradi attorno all'asse y verrà applicata a questa entità. La risposta corretta dopo la trasformazione è:

position       :   5.000000   0.000000   0.000000   1.000000
look_dir       :   0.000000   0.000000   1.000000   0.000000

Otterremo la risposta corretta solo se distinguiamo i vettori con w == 0 e le posizioni con w == 1 in uno dei modi sopra presentati.


@Maik Semder Ti sbagli un po '... Non è possibile distinguere tra vettori e punti perché quelli sono la stessa cosa! (Sono isomorfi) 1 per i vettori e 0 per i verctor a direzione infinita (come dico nella mia risposta) . Il resto della risposta ha poco senso a causa di ipotesi errate.
FxIII,

1
@FxIII Non riesco a vedere il tuo punto (nessun gioco di parole voluto) e la pertinenza a questa domanda. Stai dicendo che vettori e punti sono gli stessi, quindi non ha senso archiviare 'w' comunque, sul serio? Ora o rivoluzionerai la computer grafica o non capirai il punto di questa domanda.
Maik Semder,

1
@FxIII Questa è una sciocchezza, potresti voler studiare alcuni framework matematici 3D utilizzati nello sviluppo del gioco, ad esempio il vectormath di Sony , troverai molte di queste implementazioni, in particolare guarda l'implementazione di vmathV4MakeFromV3 e vmathV4MakeFromP3 in vec_aos.h, studia la differenza e ciò che hanno messo nel 4 ° componente, 1.0 per P3 e 0.0 per V3, punto 3D e vettore 3D ovviamente.
Maik Semder,

3
@FxIII questo è anche il motivo per cui la classe XNA Vector3 ha una funzione membro "Transform" e "TransformNormal", il motivo è la matematica dell'algebra lineare. Quello che fai fondamentalmente scegliendo una di quelle funzioni Transform è passare un parametro 'w' implicito di '1' o '0', che fondamentalmente include la 4a riga della matrice nel calcolo o meno. Riassumi: se non memorizzi il componente 'w', devi trattare quei vettori in modo diverso chiamando diverse funzioni di trasformazione.
Maik Semder,

1
Vettori e punti sono isomorfi come detto, quindi non vi è alcuna differenza algebrica tra loro. Tuttavia, ciò che il modello omogeneo di spazio proiettivo cerca di rappresentare è che il SET di SPAZI vettoriali e i punti non sono isomorfi. L'insieme di spazi vettoriali è in effetti un tipo di chiusura per R ^ 3 che include i punti sulla sfera infinita. I punti con w = 0 sono spesso erroneamente definiti "vettori" - in realtà sono isomorfi rispetto alla sfera di direzione e verrebbero definiti più semplicemente semplicemente "direzioni" ... E no, perdere w può spesso funzionare, ma soprattutto lo farai trovare problemi.
Crowley9,

4

Se stai creando una classe Vector, suppongo che la classe memorizzerà la descrizione di un vettore 3D. I vettori 3D hanno magnitudo x, ye z. Quindi, a meno che il tuo vettore non abbia bisogno di una grandezza arbitraria, no, non lo memorizzerai nella classe.

C'è una grande differenza tra un vettore e una matrice di trasformazione. Dato che sia DirectX che OpenGL hanno a che fare con le matrici, in genere non memorizzo una matrice 4x4 nel mio codice; piuttosto, immagazzino rotazioni di Eulero (o Quaternioni se vuoi - che per coincidenza hanno un componente aw) e traduzione x, y, z. La traduzione è un vettore, se lo desideri, e la rotazione si adatterebbe tecnicamente anche a un vettore, in cui ogni componente memorizzerebbe la quantità di rotazione attorno al proprio asse.

Se vuoi immergerti un po 'più a fondo nella matematica di un vettore, un vettore euclideo è solo una direzione e una grandezza. Quindi in genere questo è rappresentato da una tripletta di numeri, in cui ogni numero è la grandezza lungo un asse; la sua direzione è implicita dalla combinazione di queste tre magnitudini e la magnitudine può essere trovata con la formula della distanza euclidea . Oppure, a volte è davvero memorizzato come una direzione (un vettore con lunghezza = 1) e una grandezza (un galleggiante), se questo è ciò che è conveniente (ad esempio se la magnitudine cambia più spesso della direzione, potrebbe essere più conveniente semplicemente cambia quel numero di magnitudo piuttosto che prendere un vettore, normalizzarlo e moltiplicare i componenti per la nuova magnitudine).


6
Il moderno OpenGL non si occupa delle matrici per te.
SurvivalMachine,

4

La quarta dimensione nel vettore 3D viene utilizzata per calcolare le trasformazioni affine che sarà impossibile calcolare utilizzando solo le matrici. Lo spazio rimane tridimensionale, quindi questo significa che il quarto è mappato nello spazio 3d in qualche modo.

Mappa a dimensioni significa che diversi vettori 4D indicano lo stesso punto 3D. La mappa è che se A = [x ', y', z'.w '] e B = [x ", y", z ", w"] rappresentano lo stesso punto se x' / x "= y ' / y "= z '/ z" = w' / w "= α, ovvero i componenti sono proporzionali per lo stesso coefficiente α.

Ha detto che puoi esprimere un punto - diciamo (1,3,7) - in modi infiniti come (1,3,7,1) o (2,6,14,2) o (131,393,917,131) o in generale (α · 1, α · 3, α · 7, α).

Ciò significa che puoi ridimensionare un vettore 4D su un altro che rappresenta lo stesso punto 3D in modo che w = 1: la forma (x, y, z, 1) sia la forma canonica.

Quando si applica una matrice a questo vettore, è possibile ottenere un vettore che non ha w = 1, ma è sempre possibile ridimensionare i risultati per memorizzarlo in forma canonica. Quindi la risposta sembra essere "dovresti usare i vettori 4D quando fai matematica ma non memorizzare il quarto componente" .

Questo è abbastanza vero ma ci sono alcuni punti che non puoi mettere in forma canonica: punti come (4,2,5,0). Questi punti sono speciali, rappresentano il punto infinito diretto e possono essere normalizzati in modo coerente al vettore unitario: puoi tranquillamente andare all'infinito e tornare (anche due volte) senza essere Chuck Norris. Otterrai una miserabile divisione per zero se provi a forzare quei vettori in forma canonica.

Ora lo sai, quindi la scelta è tua!


1

Si. La tua trasformazione non è corretta per alcuni tipi di vettore. Puoi vederlo nella libreria matematica D3DX: hanno due diverse funzioni di moltiplicazione matrice-vettore, una per w = 0 e una per w = 1.


0

Dipende da ciò che vuoi e di cui hai bisogno. :)

Lo memorizzerei, b / c È necessario per le trasformazioni e simili (non è possibile moltiplicare un vettore 3 con una matrice 4x4), anche se se ne hai sempre solo uno di 1, immagino che potresti semplicemente falsificarlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.