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

wVectorクラスに格納する点はありますか?


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

回答:


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番目のコンポーネントをゼロにすると、ポイントを変換する同じマトリックスを使用して回転軸を変換でき、結果が有効になりますマトリックスにスケールがない限り、その長さは保持されます。これがベクトルに必要な動作です。4番目のコンポーネントがなければ、2つの行列(または暗黙的な4番目のパラメーターを持つ2つの異なる乗算関数)を作成し、ポイントとベクトルに対して2つの異なる関数呼び出しを行う必要があります。

最新のCPU(SSE、Altivec、SPU)のベクターレジスタを使用するには、とにかく4x 32ビットの浮動小数点数(128ビットのレジスタ)を渡す必要があり、さらに、通常16バイトのアライメントに注意する必要があります。とにかく、4番目のコンポーネントのスペースを安全に確保する機会はありません。


編集: 質問に対する答えは基本的に

  1. wコンポーネントを格納します:位置に1、ベクトルに0
  2. または、異なる行列ベクトル乗算関数を呼び出し、いずれかの関数のいずれかを選択して暗黙的に「w」コンポーネントを渡します

そのうちの1つを選択する必要があります。{x、y、z}のみを保存し、1つの行列ベクトル乗算関数のみを使用することはできません。たとえば、XNAはVector3クラスに2つのTransform関数を持ちTransformTransformNormal

両方のアプローチを示し、2つの可能な方法のいずれかで両方の種類のベクトルを区別する必要があることを示すコード例を次に示します。マトリックスで変換することで、世界の位置と視線方向でゲームエンティティを移動します。「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

1
@FxIII私はあなたの主張(しゃれは意図していません)とこの質問との関連性がわかりません。ベクトルとポイントは同じだと言っているので、とにかく「w」を保存する意味はありませんか?今、あなたはコンピューターグラフィックスに革命を起こすか、この質問の要点を得られないかのどちらかです。
マイクセンダー

1
@FxIIIそれはナンセンスです、ゲーム開発で使用されるいくつかの3D数学フレームワーク、つまりSonyのvectormathを勉強したいと思うかもしれません。そのような実装がたくさんあります。そして、4番目のコンポーネントに入れるもの、P3の場合は1.0、V3の場合は0.0、3Dポイントおよび3Dベクトルは明らかに。
マイクセンダー

3
@FxIIIは、XNA Vector3クラスに「Transform」および「TransformNormal」メンバー関数がある理由であります。その理由、線形代数の数学です。これらの変換関数のいずれかを選択して基本的に行うことは、「1」または「0」の暗黙の「w」パラメーターを渡すことです。これには、基本的に行列の4行目が含まれます。要約:「w」コンポーネントを保存しない場合、異なる変換関数を呼び出すことにより、これらのベクトルを異なる方法で処理する必要があります。
マイクセンダー

1
ベクトルと点は、前述のように同型であるため、それらの間に代数的な違いはありません。ただし、射影空間の同種モデルが表現しようとするのは、ベクトルSPACESのSETと点が同型ではないということです。ベクトル空間のセットは、事実上、無限球上の点を含むR ^ 3の閉包の一種です。w = 0のポイントはしばしば「ベクトル」と誤って参照されます-これらは実際には方向球と同型であり、より正確には単に「方向」と呼ばれます。トラブルを見つけています。
クローリー9

4

Vectorクラスを作成している場合、そのクラスには3Dベクトルの説明が格納されると思います。3Dベクトルには、x、y、およびzの大きさがあります。したがって、ベクトルに任意のwマグニチュードが必要でない限り、いいえ、クラスに保存しません。

ベクトルと変換行列には大きな違いがあります。DirectXとOpenGLの両方がマトリックスを処理することを考えると、通常、コードに4x4マトリックスを格納しません。むしろ、オイラー回転(または必要に応じてクォータニオン-偶然にawコンポーネントを持つ)とx、y、z変換を保存します。移動は必要に応じてベクトルであり、回転は技術的にはベクトルにも適合します。各コンポーネントはその軸の周りの回転量を保存します。

ベクトルの数学をさらに深く掘り下げたい場合、ユークリッドベクトルは単なる方向と大きさです。したがって、通常、これは数字のトリプレットで表されます。各数字は軸に沿った大きさです。その方向はこれらの3つの等級の組み合わせによって暗示され、等級はユークリッド距離の式で見つけることができます。または、方向(長さ= 1のベクトル)と大きさ(フロート)として実際に格納される場合があります。それが便利な場合(たとえば、大きさが方向よりも頻繁に変化する場合は、ベクトルを取得し、それを正規化し、コンポーネントに新しい大きさを乗算するよりも、その大きさの数値を変更します)


6
最新のOpenGLは、マトリックスを処理しません。
SurvivalMachine

4

3Dベクトルの4番目の次元は、マトリックスのみでは計算できないアフィン変換を計算するために使用されます。空間は3次元のままであるため、4番目の空間は何らかの方法で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,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番目のコンポーネントは保存しないでください」と思われます。

これは非常に真実ですが、正規形に入れることができないポイントがいくつかあります:(4,2,5,0)のようなポイント。これらのポイントは特別なポイントであり、方向付けられた無限ポイントを表し、単位ベクトルに一貫して正規化できます。チャックノリスにならずに、安全に無限に移動して(2回でも)戻ることができます。これらのベクトルを正規の形式で強制しようとすると、悲惨なゼロ除算が行われます。

わかったので、選択はあなた次第です!


1

そうです。一部の種類のベクトルでは、変換が正しくありません。これはD3DX数学ライブラリで見ることができます。2つの異なる行列ベクトル乗算関数があり、1つはw = 0用、もう1つはw = 1用です。


0

必要なものに依存します。:)

私はそれを保存しますが、b / cは変換などに必要です(3xベクトルに4x4行列を掛けることはできません)が、常に1のawを持つ場合は、それを偽造することができると思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.