Ai-je besoin du composant «w» dans ma classe Vector?


21

Supposons que vous écrivez du code matriciel qui gère la rotation, la traduction, etc. pour l'espace 3D.

Maintenant, les matrices de transformation doivent être 4x4 pour s'adapter au composant de traduction.

Cependant, vous n'avez pas vraiment besoin de stocker un wcomposant dans le vecteur , n'est-ce pas ?

Même dans la division en perspective, vous pouvez simplement calculer et stocker en wdehors du vecteur et diviser en perspective avant de revenir de la méthode.

Par exemple:

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

Est-il utile de stocker wdans la classe Vector?


2
Ce n'est pas l'implémentation pour une multiplication vectorielle matricielle normale, la division en perspective n'y appartient pas. Il est également assez trompeur, car les mauvaises parties du calcul sont mises en évidence. Si vous voulez savoir à quoi sert le composant w, regardez l'implémentation complète, puis vous voyez que la dernière ligne / colonne (la partie traduction) de la matrice n'est appliquée que si le composant w est 1, c'est-à-dire pour les points. Vous devriez mettre en évidence ces parties: r.x = ... + a._14*v.w; r.y = ... + a._24*v.w; r.z = ... + a._34*v.w; r.w = ... + a._44*v.w;regardez ma réponse pour plus de détails
Maik Semder

Réponses:


27

EDIT Avertissement : Pour plus de commodité dans cette réponse, les vecteurs avec w == 0 sont appelés vecteurs et avec w == 1 sont appelés points. Bien que, comme l'a souligné FxIII, ce n'est pas une terminologie mathématiquement correcte. Cependant, puisque le point de la réponse n'est pas la terminologie, mais la nécessité de distinguer les deux types de vecteurs, je m'en tiendrai. Pour des raisons pratiques, cette convention est largement utilisée dans le développement de jeux.


Il n'est pas possible de faire la distinction entre les vecteurs et les points sans composante «w». C'est 1 pour les points et 0 pour les vecteurs.

Si les vecteurs sont multipliés avec une matrice de transformation affine 4x4 qui a une traduction dans sa dernière ligne / colonne, le vecteur serait également traduit, ce qui est faux, seuls les points doivent être traduits. Le zéro dans la composante «w» d'un vecteur s'en occupe.

La mise en évidence de cette partie de la multiplication matrice-vecteur le rend plus clair:

    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) 

C'est-à-dire qu'il serait faux de traduire un vecteur, par exemple un axe de rotation, le résultat est tout simplement faux, En ayant son 4ème composant zéro, vous pouvez toujours utiliser la même matrice qui transforme les points pour transformer l'axe de rotation et le résultat sera valide et sa longueur est conservée tant qu'il n'y a pas d'échelle dans la matrice. C'est le comportement que vous souhaitez pour les vecteurs. Sans le 4ème composant, vous devriez créer 2 matrices (ou 2 fonctions de multiplication différentes avec un 4ème paramètre implicite. Et faire 2 appels de fonction différents pour les points et les vecteurs.

Pour utiliser les registres vectoriels des processeurs modernes (SSE, Altivec, SPU), vous devez quand même passer 4x flottants 32 bits (c'est un registre 128 bits), plus vous devez vous occuper de l'alignement, généralement 16 octets. Vous n'avez donc pas la possibilité de sécuriser l'espace pour le 4ème composant de toute façon.


EDIT: La réponse à la question est essentiellement

  1. Soit stocker la composante w: 1 pour les positions et 0 pour les vecteurs
  2. Ou appelez différentes fonctions de multiplication matrice-vecteur et passez implicitement le composant «w» en choisissant l'une des deux fonctions

Il faut en choisir une, il n'est pas possible de ne stocker que {x, y, z} et de n'utiliser qu'une seule fonction de multiplication matrice-vecteur. XNA par exemple utilise cette dernière approche en ayant 2 fonctions Transform dans sa classe Vector3 , appelées TransformetTransformNormal

Voici un exemple de code qui montre les deux approches et démontre la nécessité de distinguer les deux types de vecteurs de 1 des 2 façons possibles. Nous déplacerons une entité de jeu avec une position et une direction de regard dans le monde en la transformant avec une matrice. Si nous n'utilisons pas le composant «w», nous ne pouvons plus utiliser la même multiplication matrice-vecteur, comme le montre cet exemple. Si nous le faisons de toute façon, nous obtiendrons une mauvaise réponse pour le look_dirvecteur transformé :

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

État initial de l'entité:

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

Maintenant, une transformation avec une translation de x + 5 et une rotation de 90 degrés autour de l'axe y sera appliquée à cette entité. La bonne réponse après la transformation est:

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

Nous n'obtiendrons la bonne réponse que si nous distinguons les vecteurs avec w == 0 et les positions avec w == 1 de l'une des manières présentées ci-dessus.


@Maik Semder Vous vous trompez un peu ... Il n'est pas possible de faire la distinction entre les vecteurs et les points car ce sont les mêmes! (Ils sont isomorphes) 1 pour les vecteurs et 0 pour les vercteurs orientés infinis (comme je le dis dans ma réponse) . Le reste de la réponse a peu de sens en raison de fausses hypothèses.
FxIII

1
@FxIII Je ne vois pas votre point (sans jeu de mots) et la pertinence de cette question. Vous dites que les vecteurs et les points sont les mêmes, donc cela n'a aucun sens de stocker "w" de toute façon, sérieusement? Maintenant, soit vous allez révolutionner l'infographie, soit vous ne comprenez pas le point de cette question.
Maik Semder

1
@FxIII C'est absurde, vous voudrez peut-être étudier certains cadres mathématiques 3D utilisés dans le développement de jeux, c.-à-d . Vectormath de Sony , vous trouverez beaucoup de ces implémentations, regardez en particulier l'implémentation de vmathV4MakeFromV3 et vmathV4MakeFromP3 dans vec_aos.h, étudiez la différence et ce qu'ils ont mis dans le 4ème composant, 1.0 pour P3 et 0.0 pour V3, point 3D et vecteur 3D évidemment.
Maik Semder

3
@FxIII c'est aussi la raison pour laquelle la classe XNA Vector3 a une fonction membre "Transform" et "TransformNormal", la raison en est le calcul de l'algèbre linéaire. Ce que vous faites essentiellement en choisissant l'une de ces fonctions de transformation, c'est de passer un paramètre «w» implicite de «1» ou «0», qui inclut ou non la 4ème ligne de la matrice dans le calcul. Résumé: Si vous ne stockez pas le composant «w», vous devez alors traiter ces vecteurs différemment en appelant différentes fonctions de transformation.
Maik Semder

1
Les vecteurs et les points sont isomorphes comme dit, il n'y a donc pas de différence algébrique entre eux. Cependant, ce que le modèle homogène de l'espace projectif essaie de représenter, c'est que l'ensemble des espaces vectoriels et les points ne sont pas isomorphes. L'ensemble des espaces vectoriels est en effet un type de fermeture pour R ^ 3 qui inclut les points sur la sphère infinie. Les points avec w = 0 sont souvent appelés à tort «vecteurs» - ceux-ci sont en fait isomorphes à la sphère de direction, et seraient plus précisément appelés simplement «directions» ... Et non, la perte de w peut souvent fonctionner, mais surtout vous le ferez avoir du mal.
Crowley9

4

Si vous créez une classe Vector, je suppose que la classe stockera la description d'un vecteur 3D. Les vecteurs 3D ont des magnitudes x, y et z. Donc, à moins que votre vecteur ait besoin d'une amplitude w arbitraire, non, vous ne le stockerez pas dans la classe.

Il y a une grande différence entre un vecteur et une matrice de transformation. Étant donné que DirectX et OpenGL traitent des matrices pour vous, je ne stocke généralement pas de matrice 4x4 dans mon code; je stocke plutôt des rotations d'Euler (ou des Quaternions si vous le souhaitez - qui ont par hasard un composant aw) et une traduction x, y, z. La translation est un vecteur si vous le souhaitez, et la rotation s'inscrirait techniquement dans un vecteur également, où chaque composant stockerait la quantité de rotation autour de son axe.

Si vous voulez plonger un peu plus profondément dans les mathématiques d'un vecteur, un vecteur euclidien n'est qu'une direction et une magnitude. Donc, typiquement, cela est représenté par un triplet de nombres, où chaque nombre est la grandeur le long d'un axe; sa direction est impliquée par la combinaison de ces trois grandeurs, et la grandeur peut être trouvée avec la formule de distance euclidienne . Ou, parfois, il est vraiment stocké sous la forme d'une direction (un vecteur de longueur = 1) et d'une amplitude (un flottant), si c'est ce qui convient (par exemple, si l'amplitude change plus souvent que la direction, il peut être plus pratique de simplement changer ce nombre de grandeur que pour prendre un vecteur, le normaliser et multiplier les composantes par la nouvelle grandeur).


6
OpenGL moderne ne s'occupe pas des matrices pour vous.
SurvivalMachine

4

La quatrième dimension du vecteur 3D est utilisée pour calculer les transformations affines qui seront impossibles à calculer en utilisant uniquement des matrices. L'espace reste tridimensionnel, ce qui signifie que le quatrième est cartographié dans l'espace 3D d'une manière ou d'une autre.

Mapper une dimension signifie que différents vecteurs 4D indiquent le même point 3D. La carte est que si A = [x ', y', z'.w '] et B = [x ", y", z ", w"] ils représentent le même point si x' / x "= y ' / y "= z '/ z" = w' / w "= α c'est-à-dire que les composantes sont proportionnelles pour le même coefficient α.

Dit que vous pouvez exprimer un point - disons (1,3,7) - de manières infinies comme (1,3,7,1) ou (2,6,14,2) ou (131,393,917,131) ou en général (α · 1, α · 3, α · 7, α).

Cela signifie que vous pouvez mettre à l'échelle un vecteur 4D à un autre représentant le même point 3D de sorte que w = 1: la forme (x, y, z, 1) est la forme canonique.

Lorsque vous appliquez une matrice à ce vecteur, vous pouvez obtenir un vecteur qui n'a pas le w = 1, mais vous pouvez toujours mettre à l'échelle les résultats pour le stocker sous forme canonique. La réponse semble donc être "vous devez utiliser des vecteurs 4D lorsque vous faites des calculs mais ne stockez pas le quatrième composant" .

C'est tout à fait vrai, mais il y a certains points que vous ne pouvez pas mettre sous forme canonique: des points comme (4,2,5,0). Ces points sont spéciaux, ils représentent un point infini dirigé et peuvent être normalisés en vecteur unité de manière cohérente: vous pouvez aller en toute sécurité à l'infini et revenir (même deux fois) sans être Chuck Norris. Vous obtiendrez une misérable division par zéro si vous essayez de forcer ces vecteurs sous forme canonique.

Maintenant que vous savez, le choix vous appartient!


1

Oui. Votre transformation est incorrecte pour certains types de vecteur. Vous pouvez le voir dans la bibliothèque mathématique D3DX - ils ont deux fonctions de multiplication matrice-vecteur différentes, une pour w = 0 et une pour w = 1.


0

Cela dépend de ce que vous voulez et de ce dont vous avez besoin. :)

Je le stockerais, b / c c'est nécessaire pour les transformations et autres (vous ne pouvez pas multiplier un vecteur 3 avec une matrice 4x4), mais si vous avez toujours juste un aw de 1, je suppose que vous pouvez simplement le simuler.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.