¿Necesito el componente 'w' en mi clase de Vector?


21

Suponga que está escribiendo un código de matriz que maneja la rotación, la traducción, etc. para el espacio 3d.

Ahora las matrices de transformación tienen que ser 4x4 para encajar el componente de traducción.

Sin embargo, en realidad no necesita almacenar un wcomponente en el vector, ¿verdad?

Incluso en la división de perspectiva, puede simplemente calcular y almacenar wfuera del vector, y dividir la perspectiva antes de regresar del método.

Por ejemplo:

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

¿Hay algún punto en el almacenamiento wen la clase Vector?


2
Esa no es la implementación para una multiplicación de vector de matriz normal, la división en perspectiva no pertenece allí. También es bastante engañoso, porque se resaltan las partes incorrectas del cálculo. Si desea averiguar para qué es el componente w, observe la implementación completa, luego verá que la última fila / columna (la parte de traducción) de la matriz solo se aplica, si el componente w es 1, es decir por puntos. Deberías resaltar esas partes: r.x = ... + a._14*v.w; r.y = ... + a._24*v.w; r.z = ... + a._34*v.w; r.w = ... + a._44*v.w;mira mi respuesta para más detalles
Maik Semder

Respuestas:


27

Exención de responsabilidad de EDIT : por conveniencia en esta respuesta, los vectores con w == 0 se denominan vectores y con w == 1 se denominan puntos. Aunque, como señaló FxIII, esa no es una terminología matemáticamente correcta. Sin embargo, dado que el punto de la respuesta no es la terminología, sino la necesidad de distinguir ambos tipos de vectores, me atendré a ella. Por razones prácticas, esta convención se usa ampliamente en el desarrollo de juegos.


No es posible distinguir entre vectores y puntos sin un componente 'w'. Es 1 para puntos y 0 para vectores.

Si los vectores se multiplican con una matriz de transformación afín 4x4 que tiene una traducción en su última fila / columna, el vector también se traduciría, lo cual es incorrecto, solo se deben traducir los puntos. El cero en el componente 'w' de un vector se encarga de eso.

Al resaltar esta parte de la multiplicación matriz-vector, queda más claro:

    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) 

Es decir, sería incorrecto traducir un vector, por ejemplo, un eje de rotación, el resultado es simplemente incorrecto. Al tener su cuarto componente cero, aún puede usar la misma matriz que transforma los puntos para transformar el eje de rotación y el resultado será válido y su longitud se conserva siempre que no haya escala en la matriz. Ese es el comportamiento que desea para los vectores. Sin el cuarto componente, tendría que crear 2 matrices (o 2 funciones de multiplicación diferentes con un cuarto parámetro implícito) y realizar 2 llamadas a funciones diferentes para puntos y vectores.

Para usar los registros vectoriales de las CPU modernas (SSE, Altivec, SPU), debe pasar 4x 32 bits flotantes de todos modos (es un registro de 128 bits), además de tener que ocuparse de la alineación, generalmente 16 bytes. Por lo tanto, no tiene la oportunidad de proteger el espacio para el cuarto componente de todos modos.


EDITAR: la respuesta a la pregunta es básicamente

  1. Almacene el componente w: 1 para posiciones y 0 para vectores
  2. O llame a diferentes funciones de multiplicación de matriz-vector y pase implícitamente el componente 'w' eligiendo una de las funciones

Uno debe elegir uno de ellos, no es posible almacenar solo {x, y, z} y aún usar solo una función de multiplicación de matriz-vector. XNA, por ejemplo, utiliza el último enfoque al tener 2 funciones de transformación en su clase Vector3 , llamadas TransformyTransformNormal

Aquí hay un ejemplo de código que muestra ambos enfoques y demuestra la necesidad de distinguir ambos tipos de vectores en 1 de las 2 formas posibles. Vamos a mover una entidad de juego con una posición y una dirección de mirada en el mundo transformándola con una matriz. Si no usamos el componente 'w', ya no podemos usar la misma multiplicación de matriz-vector, como lo demuestra este ejemplo. Si lo hacemos de todos modos, obtendremos una respuesta incorrecta para el look_dirvector transformado :

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

Estado de entidad inicial:

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

Ahora se aplicará a esta entidad una transformación con una traducción de x + 5 y una rotación de 90 grados alrededor del eje y. La respuesta correcta después de la transformación es:

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

Solo obtendremos la respuesta correcta si distinguimos los vectores con w == 0 y las posiciones con w == 1 en una de las formas presentadas anteriormente.


@Maik Semder Estás un poco equivocado ... ¡No es posible distinguir entre vectores y puntos porque son lo mismo! (Son isomórficos) 1 para vectores y 0 para verctores con dirección infinita (como digo en mi respuesta) . El resto de la respuesta tiene poco sentido debido a suposiciones erróneas.
FxIII

1
@FxIII No veo su punto (sin juego de palabras) y la relevancia de esta pregunta. Está diciendo que los vectores y los puntos son iguales, por lo que no tiene sentido almacenar 'w' de todos modos, ¿en serio? Ahora, o vas a revolucionar los gráficos de computadora o no entiendes el punto de esta pregunta.
Maik Semder

1
@FxIII Eso no tiene sentido, es posible que desee estudiar algunos marcos matemáticos 3D utilizados en el desarrollo de juegos, es decir, el vectormath de Sony , encontrará muchas de esas implementaciones, una mirada particular a la implementación de vmathV4MakeFromV3 y vmathV4MakeFromP3 en vec_aos.h, estudie la diferencia y lo que pusieron en el cuarto componente, 1.0 para P3 y 0.0 para V3, punto 3D y vector 3D obviamente.
Maik Semder

3
@FxIII esa es también la razón por la cual la clase XNA Vector3 tiene una función miembro "Transform" y "TransformNormal", la razón es la matemática del álgebra lineal. Lo que básicamente hace al elegir una de esas funciones Transformar es pasar un parámetro 'w' implícito de '1' o '0', que básicamente incluye la cuarta fila de la matriz en el cálculo o no. Resumir: si no almacena el componente 'w', entonces debe tratar esos vectores de manera diferente llamando a diferentes funciones de transformación.
Maik Semder

1
Los vectores y los puntos son isomorfos como se dijo, por lo tanto, no hay diferencia algebraica entre ellos. Sin embargo, lo que el modelo homogéneo del espacio proyectivo intenta representar es que el CONJUNTO de ESPACIOS vectoriales y los puntos no son isomorfos. El conjunto de espacios vectoriales es en efecto un tipo de cierre para R ^ 3 que incluye los puntos en la esfera infinita. Los puntos con w = 0 a menudo se denominan incorrectamente como "vectores"; estos en realidad son isomórficos a la esfera de dirección, y se denominarían con mayor precisión simplemente "direcciones" ... Y no, perder w a menudo puede funcionar, pero sobre todo lo hará estar encontrando problemas
Crowley9

4

Si está haciendo una clase de Vector, entonces supongo que la clase almacenará la descripción de un vector 3D. Los vectores 3D tienen magnitudes x, y y z. Entonces, a menos que su vector necesite una magnitud w arbitraria, no, no lo almacenará en la clase.

Hay una gran diferencia entre un vector y una matriz de transformación. Dado que DirectX y OpenGL manejan matrices para usted, normalmente no almaceno una matriz 4x4 en mi código; más bien, almaceno las rotaciones de Euler (o Quaternions si lo desea, que casualmente tienen un componente aw) y la traducción x, y, z. La traducción es un vector si lo desea, y la rotación técnicamente también encajaría en un vector, donde cada componente almacenaría la cantidad de rotación alrededor de su eje.

Si quieres sumergirte un poco más en las matemáticas de un vector, un vector euclidiano es solo una dirección y una magnitud. Por lo general, esto se representa mediante un triplete de números, donde cada número es la magnitud a lo largo de un eje; su dirección está implícita en la combinación de estas tres magnitudes, y la magnitud se puede encontrar con la fórmula de distancia euclidiana . O, a veces, realmente se almacena como una dirección (un vector con longitud = 1) y una magnitud (un flotador), si eso es conveniente (por ejemplo, si la magnitud cambia con más frecuencia que la dirección, puede ser más conveniente simplemente cambiar ese número de magnitud que tomar un vector, normalizarlo y multiplicar los componentes por la nueva magnitud).


66
OpenGL moderno no maneja matrices para usted.
SurvivalMachine

4

La cuarta dimensión en el vector 3D se usa para calcular las transformaciones afines que serán imposibles de calcular usando solo matrices. El espacio sigue siendo tridimensional, por lo que esto significa que el cuarto está mapeado en el espacio 3d de alguna manera.

Mapear una dimensión significa que diferentes vectores 4D indican el mismo punto 3D. El mapa es que si A = [x ', y', z'.w '] y B = [x ", y", z ", w"] representan el mismo punto si x' / x "= y ' / y "= z '/ z" = w' / w "= α, es decir, el componente es proporcional para el mismo coeficiente α.

Dijo que puedes expresar un punto, digamos (1,3,7), de maneras infinitas como (1,3,7,1) o (2,6,14,2) o (131,393,917,131) o en general (α · 1, α · 3, α · 7, α).

Esto significa que puede escalar un vector 4D a otro que represente el mismo punto 3D para que w = 1: la forma (x, y, z, 1) sea la forma canónica.

Cuando aplica una matriz a este vector, puede obtener un vector que no tiene w = 1, pero siempre puede escalar los resultados para almacenarlo en forma canónica. Entonces, la respuesta parece ser "deberías usar vectores 4D cuando hagas matemáticas pero no guardes el cuarto componente" .

Esto es bastante cierto, pero hay algunos puntos que no puedes poner en forma canónica: puntos como (4,2,5,0). Esos puntos son especiales, representan el punto infinito dirigido y se pueden normalizar al vector unitario de manera consistente: puedes ir al infinito de manera segura y regresar (incluso dos veces) sin ser Chuck Norris. Obtendrá una división miserable por cero si intenta forzar esos vectores en forma canónica.

Ahora ya lo sabes, ¡así que la elección es tuya!


1

Si tu puedes. Su transformación es incorrecta para algunos tipos de vectores. Puede ver esto en la biblioteca matemática D3DX: tienen dos funciones de multiplicación de vectores de matriz diferentes, una para w = 0 y otra para w = 1.


0

Depende de lo que quieras y necesites. :)

Lo almacenaría, b / c ES necesario para transformaciones y demás (no se puede multiplicar un vector 3 con una matriz 4x4), aunque si siempre tiene un aw de 1, supongo que podría simularlo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.