我的Vector类中是否需要'w'组件?


21

假设您正在编写处理3d空间旋转,平移等的矩阵代码。

现在,转换矩阵必须为4x4以适合转换分量。

但是,您实际上并不需要w在矢量中存储组件吗?

即使在透视除法中,您也可以简单地w在向量之外进行计算和存储,并在从方法返回之前进行透视除法。

例如:

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

w在Vector类中存储是否有意义?


2
这不是正常矩阵矢量乘法的实现,透视除法不属于该范围。这也很容易引起误解,因为突出显示了错误的计算部分。如果要找出w分量的用途,请看一下完整的实现,然后会看到,只有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;查看我的答案以了解详细信息
Maik Semder 2011年

Answers:


27

编辑免责声明:为方便起见,在此答案中,将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个组件的空间安全。


编辑:问题 的答案基本上是

  1. 要么存储w分量:1代表位置,0代表向量
  2. 或调用不同的矩阵向量乘法函数,并通过选择其中一个函数隐式传递“ w”分量

必须选择其中之一,不可能只存储{x,y,z},而仍然只使用一个矩阵向量乘法函数。例如,XNA使用后一种方法,在其Vector3类中具有2个Transform函数,分别称为TransformTransformNormal

这是一个代码示例,其中显示了这两种方法,并说明了需要以两种可能的方式中的一种来区分两种向量。通过使用矩阵对其进行变换,我们将在世界上移动具有位置和外观方向的游戏实体。如本例所示,如果不使用'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的位置,则只有得到正确的答案。


@Maik Semder你有点错误...不可能在向量和点之间区分,因为它们是同一件事!(它们是同构的)1表示向量,0表示无限定向的向量(正如我在回答中所说) 。由于错误的假设,其余的答复毫无意义。
FxIII 2011年

1
@FxIII我看不到您的意思(没有双关语)和与此问题的相关性。您是说向量和点是相同的,所以认真地存储“ w”毫无意义吗?现在,您将要彻底改变计算机图形学,或者您不明白这个问题的重点。
Maik Semder 2011年

1
@FxIII废话,您可能想研究一些游戏开发中使用的3D数学框架,例如Sony的vectormath,您会发现很多这样的实现,尤其是查看vec_aos.h中vmathV4MakeFromV3和vmathV4MakeFromP3的实现,研究其中的区别并将它们放入第四个分量中,显然P3为1.0,V3为0.0,3D点和3D向量。
Maik Semder 2011年

3
@FxIII也是XNA Vector3类具有“ Transform”和“ TransformNormal”成员函数的原因,其原因线性代数的数学原理。通过选择这些Transform函数之一来完成的工作基本上是传递隐式的'w'参数'1'或'0',该参数基本上将矩阵的第4行包含在计算中。总结:如果不存储“ w”分量,则必须通过调用不同的转换函数来不同地对待这些向量。
Maik Semder 2011年

1
如上所述,向量和点是同构的,因此它们之间没有代数差异。但是,射影空间的同质模型试图表示的是向量空间的SET,而点不是同构的。向量空间集实际上是R ^ 3的一种闭合类型,它包括无限球体上的点。w = 0的点通常被错误地称为“向量”-它们实际上与方向球同构,更准确地称为“方向” ...不,丢失w可能经常起作用,但大多数情况下,您会发现麻烦。
Crowley11年9

4

如果您要创建Vector类,那么我想该类将存储3D矢量的描述。3D向量具有x,y和z大小。因此,除非您的向量需要任意w大小,否则不会将其存储在类中。

向量和变换矩阵之间存在很大差异。鉴于DirectX和OpenGL都可以为您处理矩阵,因此我通常不在代码中存储4x4矩阵。相反,我存储了Euler旋转(如果需要,也可以存储四元数-恰好有aw分量)和x,y,z平移。如果需要,平移是矢量,并且旋转在技术上也适合矢量,其中每个分量将存储围绕其轴的旋转量。

如果您想更深入地研究向量的数学,则欧几里得向量只是一个方向和一个量值。因此,通常用三元组数字表示,其中每个数字都是沿轴的大小;这三个量级的组合暗示了它的方向,并且该量级可以通过欧几里得距离公式找到。或者,有时它确实存储为方向(长度= 1的向量)和幅度(浮点数),如果那是方便的话(例如,幅度比方向改变得更多的话,更改该大小值,而不是取一个向量,对其进行归一化,然后将分量乘以新的大小)。


6
现代OpenGL不会为您处理矩阵。
SurvivalMachine

4

3D向量中的第四维用于计算仿射变换,仅使用矩阵就无法计算出仿射变换。该空间保持为三维,因此这意味着第四个空间以某种方式映射到3d空间中。

映射尺寸表示不同的4D向量表示相同的3D点。该图是,如果A = [x',y',z'.w']和B = [x“,y”,z“,w”],则它们表示相同点,如果x'/ x“ = y' / y“ = z'/ z” = w'/ w“ =α即分量对于相同系数α成比例。

说您可以用(1,3,7,1)或(2,6,14,2)或(131,393,917,131)或一般(α· 1,α·3,α·7,α)。

这意味着您可以将4D向量缩放到另一个代表相同3D点的向量,以使w = 1:形式(x,y,z,1)是规范形式。

当您将矩阵应用于此向量时,您可能会获得一个不等于w = 1的向量,但是您始终可以缩放结果以将其以规范形式存储。因此,答案似乎是“进行数学运算时应使用4D向量,但不要存储第四个分量”

的确如此,但是有些点不能以规范的形式表示:例如(4,2,5,0)的点。这些点是特殊的,它们表示有向无穷大,可以一致地归一化为单位矢量:您可以安全地转到无穷大并返回(甚至两次),而无需成为Chuck Norris。如果尝试强制采用规范形式的向量,那么您将得到零的悲惨除法。

现在您知道了,所以选择就是您的!


1

是的你是。对于某些矢量,您的转换是错误的。您可以在D3DX数学库中看到这一点-它们具有两个不同的矩阵向量乘法函数,一个用于w = 0,另一个用于w = 1。


0

取决于您想要和需要的东西。:)

我将其存储起来,b / c是进行转换之类的必要条件(您不能将3向量与4x4矩阵相乘),尽管如果您始终始终将w设为1,我想您可以伪造它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.