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-component가 무엇인지 찾으려면 완전한 구현을 살펴보십시오 .w-component가 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'성분의 0은 그 점을 처리합니다.

행렬-벡터 곱셈의이 부분을 강조하면 더 명확 해집니다.

    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 번째 성분을 0으로하여 회전 축을 변환하기 위해 점을 변환하는 동일한 행렬을 계속 사용할 수 있으며 결과는 유효합니다. 행렬에 스케일이없는 한 길이는 유지됩니다. 이것이 벡터에 대해 원하는 동작입니다. 4 번째 성분이 없으면 2 개의 행렬 (또는 내재 된 4 번째 매개 변수로 2 개의 다른 곱셈 함수를 작성하고 점과 벡터에 대해 2 개의 다른 함수 호출을 작성해야합니다.

최신 CPU (SSE, Altivec, SPU)의 벡터 레지스터를 사용하려면 어쨌든 4x 32 비트 부동 소수점 (128 비트 레지스터)을 전달해야하며 정렬 (일반적으로 16 바이트)을 처리해야합니다. 따라서 4 번째 구성 요소를위한 공간을 확보 할 기회가 없습니다.


편집 : 질문에 대한 대답은 기본적으로

  1. w- 컴포넌트를 저장하십시오 : 위치의 경우 1, 벡터의 경우 0
  2. 또는 다른 행렬 벡터 곱셈 함수를 호출하고 두 함수 중 하나를 선택하여 'w'구성 요소를 암시 적으로 전달합니다.

그중 하나를 선택해야합니다. {x, y, z} 만 저장하고 여전히 하나의 행렬-벡터 곱셈 함수 만 사용할 수는 없습니다. 예컨대 XNA 2가에서 변환 함수함으로써 후자의 방법을 사용 Vector3의 불리는 클래스 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 당신은 약간 잘못입니다 ... 같은 점 때문에 벡터 사이에 포인트를 구별하는 것은 불가능합니다! . 나머지 응답은 잘못된 가정 때문에 거의 의미가 없습니다.
FxIII

1
@ FxIII 나는 당신의 요점 (말장난 의도가 없음) 과이 질문과의 관련성을 보지 못했습니다. 당신은 벡터와 점이 동일하다고 말하고 있으므로 어쨌든 'w'를 저장하는 것이 의미가 없습니까? 이제 컴퓨터 그래픽에 혁명을 일으키거나이 질문에 대한 답을 얻지 못할 것입니다.
Maik Semder 2018 년

1
@FxIII 그것은 말도 안됩니다. 게임 개발에 사용되는 3D 수학 프레임 워크, 예를 들어 Sony의 vectormath를 공부하고 싶을 것입니다. 예를 들어, 많은 구현을 찾을 것입니다. 그리고 그들이 4 번째 성분, P3의 경우 1.0, V3의 경우 0.0, 3D 점 및 3D 벡터에 넣은 것.
Maik Semder

3
@FxIII 또한 XNA Vector3 클래스에 "Transform"과 "TransformNormal"멤버 함수가있는 이유 선형 대수의 수학입니다. 이러한 변환 함수 중 하나를 선택하여 기본적으로하는 것은 '1'또는 '0'의 암시 적 'w'매개 변수를 전달하는 것입니다.이 매개 변수에는 기본적으로 행렬의 4 번째 행이 계산에 포함됩니다. 요약 : 'w'구성 요소를 저장하지 않으면 다른 변환 함수를 호출하여 해당 벡터를 다르게 처리해야합니다.
Maik Semder

1
벡터와 점은 말한 바와 같이 동형이므로, 그들 사이에는 대수적 차이가 없습니다. 그러나, 투영 공간의 동종 모델이 나타내는 것은 벡터 공간의 세트와 점이 동형이 아니라는 것입니다. 벡터 공간 세트는 사실상 무한 구상의 점을 포함하는 R ^ 3의 폐쇄 유형입니다. w = 0 인 점은 종종 "벡터"로 잘못 지칭됩니다. 이러한 점은 실제로 방향 구에 대해 동형이며, 더 정확하게는 "방향"이라고 칭합니다. 문제를 찾고 있습니다.
Crowley9

4

Vector 클래스를 만드는 경우 클래스가 3D 벡터에 대한 설명을 저장한다고 가정합니다. 3D 벡터는 x, y 및 z 크기입니다. 따라서 벡터에 임의의 크기가 필요한 경우가 아니라면 클래스에 저장하지 않습니다.

벡터와 변환 행렬에는 큰 차이가 있습니다. DirectX와 OpenGL이 모두 행렬을 처리한다고 가정하면 일반적으로 코드에 4x4 행렬을 저장하지 않습니다. 오히려 오일러 회전 (또는 원하는 경우 쿼터니언-우연히 aw 구성 요소가 있음)과 x, y, z 변환을 저장합니다. 변환은 원하는 경우 벡터이며 회전은 기술적으로 벡터에 적합하며 각 구성 요소는 축 주위에 회 전량을 저장합니다.

벡터의 수학에 대해 조금 더 깊이 파고 들고 싶다면 유클리드 벡터 는 방향과 크기 일뿐입니다. 따라서 일반적으로 이것은 삼중 수로 표현되며, 각 숫자는 축을 따라 크기입니다. 그 방향은이 세 가지 크기의 조합에 의해 암시되며 그 크기는 유클리드 거리 공식 으로 찾을 수 있습니다 . 또는 때로는 방향 (길이 = 1 인 벡터)과 크기 (플로트)로 저장됩니다. 편리한 경우 (예 : 크기가 방향보다 자주 변경되는 경우 더 편리 할 수 ​​있습니다) 벡터를 취하고 정규화 하고 성분에 새로운 크기를 곱하는 것보다 그 크기 수를 변경하십시오 .


6
현대 OpenGL은 행렬을 다루지 않습니다.
SurvivalMachine

4

3D 벡터의 4 차원은 행렬만으로는 계산할 수없는 아핀 변환을 계산하는 데 사용됩니다. 공간은 3 차원으로 유지되므로 네 번째는 어떤 방식 으로든 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, α).

즉, w = 1 :가되도록 형식 (x, y, z, 1)이 표준 형식이되도록 4D 벡터를 동일한 3D 점을 나타내는 다른 점으로 스케일 할 수 있습니다.

이 벡터에 행렬을 적용하면 w = 1이 아닌 벡터를 얻을 수 있지만 항상 정식 형태로 저장하도록 결과의 크기를 조정할 수 있습니다. 따라서 답은 "수학을 할 때 4D 벡터를 사용해야하지만 네 번째 구성 요소는 저장하지 마십시오" 입니다.

이것은 사실이지만 (4,2,5,0)과 같은 표준 형식으로 넣을 수없는 몇 가지 사항이 있습니다 . 이러한 점은 특수한 점으로, 무한 점을 향하고 단위 벡터로 일관되게 정규화 할 수 있습니다. 척 노리스 (Chuck Norris)가 아니라도 무한대로 이동하여 두 번 돌아올 수 있습니다. 이러한 벡터를 정식 형태로 만들려고하면 비참한 나눗셈이 0으로 나타납니다.

이제 당신은 알고 있습니다, 그래서 선택은 당신입니다!


1

예, 그렇습니다 일부 종류의 벡터에서는 변환이 올바르지 않습니다. D3DX 수학적 라이브러리에서 이것을 볼 수 있습니다. 두 개의 서로 다른 행렬 벡터 곱셈 함수가 있습니다. 하나는 w = 0이고 다른 하나는 w = 1입니다.


0

당신이 원하는 것과 필요한 것에 달려 있습니다. :)

나는 그것을 저장하고 b / c 변환에 필요합니다. (4 x 4 행렬로 3 벡터를 곱할 수는 없지만) 항상 aw가 1 인 경우 가짜 일 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.