Czy potrzebuję komponentu „w” w mojej klasie Vector?

21

Załóżmy, że piszesz kod macierzy, który obsługuje rotację, tłumaczenie itp. Dla przestrzeni 3D.

Teraz macierze transformacji muszą mieć 4x4, aby pasowały do ​​komponentu tłumaczenia.

Jednak tak naprawdę nie musisz przechowywać wkomponentu w wektorze, prawda?

Nawet w podziale perspektywicznym można po prostu obliczać i przechowywać wpoza wektorem, a podział perspektywiczny przed powrotem z metody.

Na przykład:

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

Czy warto przechowywać ww klasie Vector?

Bobobobo
źródło
2
To nie jest implementacja normalnego mnożenia wektora macierzy, podział perspektywiczny tam nie należy. Jest to również dość mylące, ponieważ niewłaściwe części obliczeń są podświetlone. Jeśli chcesz dowiedzieć się, do czego służy komponent w, spójrz na pełną implementację, a następnie zobaczysz, że ostatni wiersz / kolumna (część tłumaczenia) macierzy jest stosowany tylko wtedy, gdy komponent w jest równy 1, tj. za punkty. Powinieneś wyróżnić te części: r.x = ... + a._14*v.w; r.y = ... + a._24*v.w; r.z = ... + a._34*v.w; r.w = ... + a._44*v.w;spójrz na moją odpowiedź, aby poznać szczegóły
Maik Semder

Odpowiedzi:

27

EDIT Disclaimer : Dla wygody w tej odpowiedzi wektory o w == 0 są nazywane wektorami, a w = = 1 są nazywane punktami. Chociaż, jak wskazał FxIII, nie jest to matematycznie poprawna terminologia. Ponieważ jednak odpowiedzią nie jest terminologia, ale potrzeba rozróżnienia obu typów wektorów, będę się jej trzymać. Ze względów praktycznych konwencja ta jest szeroko stosowana w tworzeniu gier.


Niemożliwe jest rozróżnienie wektorów i punktów bez komponentu „w”. Wynosi 1 dla punktów i 0 dla wektorów.

Jeśli wektory zostaną pomnożone przez macierz transformacji afinicznej 4x4, która ma translację w swoim ostatnim wierszu / kolumnie, wektor również zostałby przetłumaczony, co jest błędne, muszą zostać przetłumaczone tylko punkty. Zajmuje się tym zero w składniku „w” wektora.

Podkreślenie tej części mnożenia macierzy i wektora czyni jaśniej:

    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) 

Tzn. Źle byłoby przetłumaczyć wektor, na przykład oś obrotu, wynik jest po prostu zły. Mając czwarty składnik zerowy, nadal możesz użyć tej samej macierzy, która przekształca punkty, aby przekształcić oś obrotu, a wynik będzie ważny a jego długość jest zachowana, dopóki matryca nie ma skali. Takie zachowanie chcesz dla wektorów. Bez czwartego komponentu musielibyśmy utworzyć 2 macierze (lub 2 różne funkcje mnożenia z niejawnym czwartym parametrem.) I wykonać 2 różne wywołania funkcji dla punktów i wektorów.

Aby użyć rejestrów wektorowych współczesnych procesorów (SSE, Altivec, SPU), musisz przekazać 4x 32-bitowe zmiennoprzecinkowe (jest to rejestr 128-bitowy), a ponadto musisz zadbać o wyrównanie, zwykle 16 bajtów. Więc i tak nie masz szansy zabezpieczyć miejsca dla czwartego komponentu.


EDYCJA: Odpowiedź na pytanie jest w zasadzie

  1. Albo przechowuj składnik w: 1 dla pozycji i 0 dla wektorów
  2. Lub wywołaj różne funkcje mnożenia macierzy i wektora i pośrednio przekaż składnik „w”, wybierając jedną z obu funkcji

Trzeba wybrać jedną z nich, nie można zapisać tylko {x, y, z} i nadal używać tylko jednej funkcji mnożenia macierzy-wektora. XNA na przykład wykorzystuje to drugie podejście, mając 2 funkcje transformacji w swojej klasie Vector3 o nazwie TransformiTransformNormal

Oto przykład kodu, który pokazuje oba podejścia i pokazuje potrzebę rozróżnienia obu rodzajów wektorów na 1 z 2 możliwych sposobów. Poruszamy byt gry o pozycji i kierunku patrzenia w świecie, przekształcając go za pomocą matrycy. Jeśli nie użyjemy komponentu „w”, nie będziemy już mogli używać tego samego mnożenia macierzy i wektora, jak pokazuje ten przykład. Jeśli i tak to zrobimy, otrzymamy złą odpowiedź na transformowany look_dirwektor:

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

Stan jednostki początkowej:

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

Teraz transformacja z translacją x + 5 i obrotem o 90 stopni wokół osi y zostanie zastosowana do tego elementu. Prawidłowa odpowiedź po transformacji to:

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

Poprawną odpowiedź otrzymamy tylko wtedy, gdy rozróżnimy wektory o == 0 i pozycje o = = 1 na jeden z powyższych sposobów.

Maik Semder
źródło
@Maik Semder Trochę się mylisz ... Nie można rozróżnić wektorów punktami, ponieważ są one tym samym! (Są izomorficzne) 1 dla wektorów i 0 dla nieskończenie ukierunkowanych wektorów (jak mówię w mojej odpowiedzi) . Pozostała część odpowiedzi nie ma sensu z powodu niewłaściwych założeń.
FxIII,
1
@FxIII Nie rozumiem twojego punktu (nie zamierzam grać słów) i znaczenia dla tego pytania. Mówisz, że wektory i punkty są takie same, więc i tak nie ma sensu przechowywać „w”, poważnie? Teraz albo zrewolucjonizujesz grafikę komputerową, albo nie rozumiesz sensu tego pytania.
Maik Semder
1
@FxIII To nonsens, możesz przestudiować niektóre frameworki matematyczne 3D używane do tworzenia gier, tj . Vectormath Sony , znajdziesz wiele takich implementacji, w szczególności spojrzenie na implementację vmathV4MakeFromV3 i vmathV4MakeFromP3 w vec_aos.h, zbadaj różnicę i co włożyli do czwartego komponentu, 1.0 dla P3 i 0.0 dla V3, punkt 3D i wektor 3D oczywiście.
Maik Semder
3
@FxIII, który jest również powodem, dla którego klasa XNA Vector3 ma funkcję składową „Transform” i „TransformNormal”, przyczyną jest matematyka algebry liniowej. To, co właściwie robisz, wybierając jedną z tych funkcji transformacji, polega na przekazaniu domyślnego parametru „w” „1” lub „0”, który zasadniczo obejmuje czwarty wiersz macierzy do obliczeń lub nie. Podsumuj: jeśli nie przechowujesz komponentu „w”, musisz traktować te wektory inaczej, wywołując różne funkcje transformacji.
Maik Semder
1
Wektory i punkty są izomorficzne, jak powiedziano, stąd nie ma między nimi różnicy algebraicznej. Jednak jednorodny model przestrzeni rzutowej próbuje reprezentować to, że ZESTAW PRZESTRZENI wektora i punkty nie są izomorficzne. Zbiór przestrzeni wektorowych jest w rzeczywistości rodzajem zamknięcia dla R ^ 3, który obejmuje punkty na nieskończonej sferze. Punkty z w = 0 są często niepoprawnie nazywane „wektorami” - w rzeczywistości są one izomorficzne w stosunku do sfery kierunkowej, a dokładniej można je nazwać po prostu „kierunkami” ... I nie, utrata w może często działać, ale przeważnie będziesz mieć kłopoty.
Crowley9,
4

Jeśli tworzysz klasę Vector, zakładam, że klasa będzie przechowywać opis wektora 3D. Wektory 3D mają wielkości x, y i z. Więc jeśli twój wektor nie potrzebuje dowolnej wielkości w, nie, nie będziesz go przechowywać w klasie.

Istnieje duża różnica między wektorem a macierzą transformacji. Biorąc pod uwagę, że zarówno DirectX, jak i OpenGL zajmują się macierzami dla ciebie, zwykle nie przechowuję macierzy 4x4 w moim kodzie; raczej przechowuję rotacje Eulera (lub Quaternions, jeśli chcesz - które przypadkowo mają komponent aw) i tłumaczenie x, y, z. Translacja jest wektorem, jeśli chcesz, a obrót technicznie pasowałby również do wektora, w którym każdy komponent przechowywałby wielkość obrotu wokół swojej osi.

Jeśli chcesz nurkować nieco głębiej w matematyce wektora, wektor euklidesowy jest tylko kierunkiem i wielkością. Zwykle jest to reprezentowane przez triplet liczb, gdzie każda liczba jest wielkością wzdłuż osi; jego kierunek jest implikowany przez połączenie tych trzech wielkości, a wielkość można znaleźć za pomocą wzoru euklidesowego na odległość . Lub czasami tak naprawdę jest przechowywany jako kierunek (wektor o długości = 1) i wielkość (liczba zmiennoprzecinkowa), jeśli to jest wygodne (np. Jeśli wielkość zmienia się częściej niż kierunek, wygodniej jest po prostu zmień tę liczbę jasności niż weź wektor, normalizuj go i pomnóż składniki przez nową jasność).

Ricket
źródło
6
Nowoczesne OpenGL nie zajmuje się macierzami dla Ciebie.
SurvivalMachine
4

Czwarty wymiar w wektorze 3D służy do obliczania przekształceń afinicznych, których obliczenie przy użyciu samych macierzy będzie niemożliwe. Przestrzeń pozostaje trójwymiarowa, więc oznacza to, że czwarty jest w jakiś sposób odwzorowany w przestrzeni 3d.

Mapowanie wymiarów oznacza, że ​​różne wektory 4D wskazują ten sam punkt 3D. Mapa jest taka, że ​​jeśli A = [x ', y', z'.w '] i B = [x ", y", z ", w"] reprezentują ten sam punkt, jeśli x' / x "= y ' / y "= z '/ z" = w' / w "= α tzn. składnik jest proporcjonalny dla tego samego współczynnika α.

Powiedział, że możesz wyrazić punkt - powiedzmy (1,3,7) - w nieskończony sposób, jak (1,3,7,1) lub (2,6,14,2) lub (131 393 917 131) lub ogólnie (α · 1, α · 3, α · 7, α).

Oznacza to, że można przeskalować wektor 4D do innego reprezentującego ten sam punkt 3D, aby w = 1: forma (x, y, z, 1) była formą kanoniczną.

Kiedy zastosujesz macierz do tego wektora, możesz otrzymać wektor, który nie ma w = 1, ale zawsze możesz skalować wyniki, aby zapisać je w formie kanonicznej. Wydaje się więc, że odpowiedź brzmi „powinieneś używać wektorów 4D podczas matematyki, ale nie przechowuj czwartego elementu” .

Jest to całkiem prawda, ale są pewne punkty, których nie można ułożyć w formie kanonicznej: punkty takie jak (4,2,5,0). Te punkty są specjalne, reprezentują skierowany nieskończony punkt i mogą być konsekwentnie normalizowane do wektora jednostkowego: możesz bezpiecznie przejść do nieskończoności i powrócić (nawet dwukrotnie) bez bycia Chuckiem Norrisem. Otrzymasz żałosny podział na zero, jeśli spróbujesz wymusić te wektory w formie kanonicznej.

Teraz już wiesz, więc wybór należy do Ciebie!

FxIII
źródło
1

Tak Twoja transformacja jest nieprawidłowa dla niektórych rodzajów wektorów. Widać to w bibliotece matematycznej D3DX - mają dwie różne funkcje mnożenia macierzy i wektora, jedną dla w = 0 i jedną dla w = 1.

DeadMG
źródło
0

Zależy od tego, czego chcesz i potrzebujesz. :)

Zapisałbym to, b / c jest to konieczne do transformacji i tak dalej (nie można pomnożyć 3 wektorów z macierzą 4x4), chociaż jeśli zawsze masz aw 1, myślę, że możesz to po prostu sfałszować.

Icepick
źródło