Benötige ich die 'w'-Komponente in meiner Vector-Klasse?


21

Angenommen, Sie schreiben Matrix-Code, der Rotation, Übersetzung usw. für den 3D-Raum übernimmt.

Nun müssen die Transformationsmatrizen 4x4 sein, damit sie in die Übersetzungskomponente passen.

Sie müssen jedoch tatsächlich keinew Komponente im Vektor speichern , oder?

Auch bei der perspektivischen Unterteilung können Sie einfach waußerhalb des Vektors berechnen und speichern und die perspektivische Unterteilung durchführen, bevor Sie von der Methode zurückkehren.

Beispielsweise:

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

Gibt es einen Punkt beim Speichern win der Vector-Klasse?


2
Das ist nicht die Implementierung für eine normale Matrixvektormultiplikation, die Perspektiventeilung gehört nicht dorthin. Auch ist es ziemlich irreführend, weil die falschen Teile der Berechnung hervorgehoben werden. Wenn Sie herausfinden möchten, wofür die w-Komponente gedacht ist, sehen Sie sich die vollständige Implementierung an und sehen, dass die letzte Zeile / Spalte (der Übersetzungsteil) der Matrix nur angewendet wird, wenn die w-Komponente 1 ist, d. H nach Punkten. Sie sollten diese Teile hervorheben: r.x = ... + a._14*v.w; r.y = ... + a._24*v.w; r.z = ... + a._34*v.w; r.w = ... + a._44*v.w;Sehen Sie sich meine Antwort für Details an
Maik Semder

Antworten:


27

EDIT Disclaimer : In dieser Antwort werden Vektoren mit w == 0 als Vektoren und mit w == 1 als Punkte bezeichnet. Obwohl FxIII darauf hinwies, ist dies keine mathematisch korrekte Terminologie. Da der Punkt der Antwort jedoch nicht die Terminologie ist, sondern die Notwendigkeit, beide Vektortypen zu unterscheiden, werde ich mich daran halten. Aus praktischen Gründen ist diese Konvention in der Spieleentwicklung weit verbreitet.


Es ist nicht möglich, zwischen Vektoren und Punkten ohne 'w'-Komponente zu unterscheiden. Es ist 1 für Punkte und 0 für Vektoren.

Wenn Vektoren mit einer affinen 4x4-Transformationsmatrix multipliziert werden, deren letzte Zeile / Spalte eine Translation enthält, wird der Vektor ebenfalls translatiert, was falsch ist. Es müssen nur Punkte translatiert werden. Dafür sorgt die Null in der 'w'-Komponente eines Vektors.

Das Hervorheben dieses Teils der Matrix-Vektor-Multiplikation macht es klarer:

    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) 

Das heißt, es wäre falsch, einen Vektor, beispielsweise eine Rotationsachse, zu verschieben. Das Ergebnis ist einfach falsch. Wenn Sie die vierte Komponente Null haben, können Sie immer noch dieselbe Matrix verwenden, die die Punkte transformiert, um die Rotationsachse zu transformieren. Das Ergebnis ist dann gültig und seine Länge bleibt erhalten, solange es keine Skala in der Matrix gibt. Das ist das Verhalten, das Sie für Vektoren wünschen. Ohne die 4. Komponente müssten Sie 2 Matrizen (oder 2 verschiedene Multiplikationsfunktionen mit einem impliziten 4. Parameter) erstellen und 2 verschiedene Funktionsaufrufe für Punkte und Vektoren durchführen.

Um die Vektorregister moderner CPUs (SSE, Altivec, SPUs) zu verwenden, müssen Sie ohnehin 4x 32-Bit-Floats (ein 128-Bit-Register) übergeben und sich um die Ausrichtung kümmern, normalerweise 16 Byte. Sie haben also ohnehin keine Chance, den Platz für die 4. Komponente zu sichern.


EDIT: Die Antwort auf die Frage ist im Grunde

  1. Speichern Sie entweder die w-Komponente: 1 für Positionen und 0 für Vektoren
  2. Oder rufen Sie verschiedene Matrix-Vektor-Multiplikationsfunktionen auf und übergeben Sie implizit die 'w'-Komponente, indem Sie eine der beiden Funktionen auswählen

Man muss eine davon auswählen, es ist nicht möglich, nur {x, y, z} zu speichern und trotzdem nur eine Matrix-Vektor-Multiplikationsfunktion zu verwenden. XNA verwendet zum Beispiel den letzteren Ansatz, indem es 2 Transformationsfunktionen in seiner Vector3- Klasse namens Transformund hatTransformNormal

Hier ist ein Codebeispiel, das beide Ansätze zeigt und die Notwendigkeit demonstriert, beide Arten von Vektoren auf eine der beiden möglichen Arten zu unterscheiden. Wir werden eine Spieleinheit mit einer Position und einer Blickrichtung in der Welt bewegen, indem wir sie mit einer Matrix transformieren. Wenn wir die 'w'-Komponente nicht verwenden, können wir nicht mehr dieselbe Matrix-Vektor-Multiplikation verwenden, wie dieses Beispiel zeigt. Wenn wir es trotzdem tun, erhalten wir eine falsche Antwort für den transformierten look_dirVektor:

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

Ursprünglicher Entitätsstatus:

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

Nun wird eine Transformation mit einer Verschiebung von x + 5 und einer Drehung von 90 Grad um die y-Achse auf dieses Objekt angewendet. Die richtige Antwort nach der Transformation lautet:

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

Die richtige Antwort erhalten wir nur, wenn wir Vektoren mit w == 0 und Positionen mit w == 1 auf eine der oben dargestellten Arten unterscheiden.


@Maik Semder Du liegst ein bisschen falsch ... Es ist nicht möglich, zwischen Vektoren und Punkten zu unterscheiden, da dies dasselbe ist! (Sie sind isomorph) 1 für Vektoren und 0 für unendlich gerichtete Verktoren (wie ich in meiner Antwort sage) . Der Rest der Antwort ist aufgrund falscher Annahmen wenig sinnvoll.
FxIII

1
@FxIII Ich verstehe Ihren Standpunkt (kein Wortspiel beabsichtigt) und die Relevanz für diese Frage nicht. Sie sagen, Vektoren und Punkte sind gleich, also macht es keinen Sinn, ernsthaft 'w' zu speichern? Entweder revolutionieren Sie die Computergrafik, oder Sie verstehen diese Frage nicht.
Maik Semder

1
@FxIII Das ist Quatsch, vielleicht möchten Sie einige 3D-Mathematik-Frameworks untersuchen, die in der Spieleentwicklung verwendet werden, dh Sonys Vektormath . Sie werden eine Menge solcher Implementierungen finden, insbesondere einen Blick auf die Implementierung von vmathV4MakeFromV3 und vmathV4MakeFromP3 in vec_aos.h, studieren Sie den Unterschied und was sie in die 4. Komponente, 1,0 für P3 und 0,0 für V3, 3D-Punkt und 3D-Vektor offensichtlich setzen.
Maik Semder

3
@FxIII das ist auch der Grund, warum die XNA Vector3- Klasse eine "Transform" - und eine "TransformNormal" -Mitgliedsfunktion hat, der Grund ist die Mathematik der linearen Algebra. Wenn Sie eine dieser Transformationsfunktionen auswählen, übergeben Sie im Grunde genommen einen impliziten 'w'-Parameter von' 1 'oder' 0 ', der die 4. Zeile der Matrix in die Berechnung einschließt oder nicht. Zusammenfassen: Wenn Sie die 'w'-Komponente nicht speichern, müssen Sie diese Vektoren unterschiedlich behandeln, indem Sie verschiedene Transformationsfunktionen aufrufen.
Maik Semder

1
Vektoren und Punkte sind wie gesagt isomorph, daher gibt es keinen algebraischen Unterschied zwischen ihnen. Das homogene Modell des projektiven Raums versucht jedoch darzustellen, dass die Menge der Vektorräume und Punkte nicht isomorph ist. Die Menge der Vektorräume ist praktisch eine Art Abschluß für R ^ 3, der die Punkte auf der unendlichen Kugel enthält. Punkte mit w = 0 werden oft fälschlicherweise als "Vektoren" bezeichnet - diese sind tatsächlich isomorph zur Richtungskugel und würden genauer einfach als "Richtungen" bezeichnet ... Und nein, w zu verlieren mag oft funktionieren, aber meistens werden Sie es tun Ärger finden.
Crowley9

4

Wenn Sie eine Vektorklasse erstellen, wird in der Klasse vermutlich die Beschreibung eines 3D-Vektors gespeichert. 3D-Vektoren haben die Größen x, y und z. Wenn Ihr Vektor also keine willkürliche Größe benötigt, nein, Sie werden ihn nicht in der Klasse speichern.

Es gibt einen großen Unterschied zwischen einem Vektor und einer Transformationsmatrix. Da sowohl DirectX als auch OpenGL für Sie Matrizen verarbeiten, speichere ich normalerweise keine 4x4-Matrix in meinem Code. Vielmehr speichere ich Euler-Rotationen (oder Quaternionen, wenn Sie möchten - die zufällig eine aw-Komponente haben) und die x, y, z-Übersetzung. Die Translation ist ein Vektor, wenn Sie möchten, und die Rotation würde technisch auch in einen Vektor passen, in dem jede Komponente den Rotationsbetrag um ihre Achse speichert.

Wenn Sie etwas tiefer in die Mathematik eines Vektors eintauchen möchten, ist ein euklidischer Vektor nur eine Richtung und eine Größe. Typischerweise wird dies durch ein Triplett von Zahlen dargestellt, wobei jede Zahl die Größe entlang einer Achse ist; Ihre Richtung ergibt sich aus der Kombination dieser drei Größen, und die Größe kann mit der euklidischen Abstandsformel ermittelt werden. Oder manchmal wird es wirklich als eine Richtung (ein Vektor mit Länge = 1) und eine Größe (ein Gleitkomma) gespeichert. Wenn dies zweckmäßig ist (z. B. wenn sich die Größe häufiger ändert als die Richtung, kann es zweckmäßiger sein, einfach zu sein Ändern Sie diese Betragszahl, um einen Vektor zu nehmen, ihn zu normalisieren und die Komponenten mit der neuen Betragszahl zu multiplizieren.


6
Modernes OpenGL befasst sich nicht mit Matrizen für Sie.
SurvivalMachine

4

Die vierte Dimension im 3D-Vektor wird verwendet, um die affinen Transformationen zu berechnen, die mit Matrizen allein nicht berechnet werden können. Der Raum bleibt dreidimensional, was bedeutet, dass der vierte auf irgendeine Weise im 3D-Raum abgebildet wird.

Bemaßungen zuordnen bedeutet, dass unterschiedliche 4D Vektoren denselben 3D Punkt anzeigen. Die Abbildung ist, dass wenn A = [x ', y', z '. W'] und B = [x ", y", z ", w"], sie den gleichen Punkt darstellen, wenn x '/ x "= y' / y "= z '/ z" = w' / w "= α, dh die Komponenten sind proportional für den gleichen Koeffizienten α.

Sagte, dass man einen Punkt - sagen wir (1,3,7) - auf unendliche Weise wie (1,3,7,1) oder (2,6,14,2) oder (131,393,917,131) oder allgemein (α · 1, α · 3, α · 7, α).

Dies bedeutet, dass Sie einen 4D-Vektor auf einen anderen skalieren können, der denselben 3D-Punkt darstellt, sodass w = 1: Die Form (x, y, z, 1) ist die kanonische Form.

Wenn Sie eine Matrix auf diesen Vektor anwenden, erhalten Sie möglicherweise einen Vektor mit nicht w = 1, aber Sie können die Ergebnisse immer skalieren, um sie in kanonischer Form zu speichern. Die Antwort scheint also zu sein: "Sie sollten 4D-Vektoren verwenden, wenn Sie rechnen, aber die vierte Komponente nicht speichern" .

Dies ist ganz richtig, aber es gibt einige Punkte, die Sie nicht in kanonischer Form formulieren können: Punkte wie (4,2,5,0). Diese Punkte sind spezielle, sie repräsentieren gerichtete unendliche Punkte und können konsistent auf Einheitsvektoren normiert werden: Sie können sicher ins Unendliche gehen und zurückkehren (sogar zweimal), ohne Chuck Norris zu sein. Sie erhalten eine miserable Division durch Null, wenn Sie versuchen, diese Vektoren in kanonischer Form zu erzwingen.

Jetzt wissen Sie, die Wahl liegt also bei Ihnen!


1

Ja, das tust du. Ihre Transformation ist für einige Arten von Vektoren falsch. Sie können dies in der mathematischen D3DX-Bibliothek sehen - sie haben zwei verschiedene Matrix-Vektor-Multiplikationsfunktionen, eine für w = 0 und eine für w = 1.


0

Kommt darauf an, was Sie wollen und brauchen. :)

Ich würde es speichern, b / c es ist notwendig für Transformationen und solche (Sie können einen 3-Vektor nicht mit einer 4x4-Matrix multiplizieren), obwohl, wenn Sie immer nur aw von 1 haben, ich denke, Sie könnten es einfach vortäuschen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.