编辑免责声明:为方便起见,在此答案中,将w == 0的向量称为向量,将w == 1的向量称为点。尽管正如FxIII所指出的那样,这不是数学上正确的术语。但是,由于答案的重点不是术语,而是区分两种向量的需要,因此我会坚持下去。出于实际原因,此约定已广泛用于游戏开发中。
没有“ w”成分的矢量和点是不可能区分的。对于点为1,对于向量为0。
如果将矢量乘以在最后一行/列中有翻译的4x4仿射变换矩阵,则矢量也将被翻译,这是错误的,仅必须翻译点。向量的“ w”分量中的零会照顾到这一点。
突出显示矩阵向量乘法的这一部分将使其更加清晰:
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)
也就是说,平移向量是错误的,例如旋转轴,结果就是错误的。通过使它的第四个分量为零,您仍然可以使用相同的矩阵来变换点以变换旋转轴,并且结果将是有效的只要矩阵中没有刻度,它的长度就可以保留。那就是您想要向量的行为。如果没有第4个组件,则必须创建2个矩阵(或2个具有隐式第4个参数的不同乘法函数。)并对点和向量进行2个不同的函数调用。
为了使用现代CPU(SSE,Altivec,SPU)的向量寄存器,您无论如何都要传递4x 32位浮点数(它是一个128位寄存器),此外还要注意对齐,通常是16个字节。因此,您仍然没有机会确保第4个组件的空间安全。
编辑:问题
的答案基本上是
- 要么存储w分量:1代表位置,0代表向量
- 或调用不同的矩阵向量乘法函数,并通过选择其中一个函数隐式传递“ w”分量
必须选择其中之一,不可能只存储{x,y,z},而仍然只使用一个矩阵向量乘法函数。例如,XNA使用后一种方法,在其Vector3类中具有2个Transform函数,分别称为Transform
和TransformNormal
这是一个代码示例,其中显示了这两种方法,并说明了需要以两种可能的方式中的一种来区分两种向量。通过使用矩阵对其进行变换,我们将在世界上移动具有位置和外观方向的游戏实体。如本例所示,如果不使用'w'分量,就不能再使用相同的矩阵向量乘法。如果无论如何我们都会对转换后的look_dir
向量得到错误的答案:
#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;
}
初始实体状态:
position : 0.000000 0.000000 0.000000 1.000000
look_dir : 1.000000 0.000000 0.000000 0.000000
现在,将平移x + 5并围绕y轴旋转90度的变换应用于此实体。转换后的正确答案是:
position : 5.000000 0.000000 0.000000 1.000000
look_dir : 0.000000 0.000000 1.000000 0.000000
如果我们以上述方式之一区分w == 0的向量和w == 1的位置,则只有得到正确的答案。
r.x = ... + a._14*v.w;
r.y = ... + a._24*v.w;
r.z = ... + a._34*v.w;
r.w = ... + a._44*v.w;
查看我的答案以了解详细信息