Tôi có cần thành phần 'w' trong lớp Vector của mình không?


21

Giả sử bạn đang viết mã ma trận xử lý xoay, dịch vv cho không gian 3d.

Bây giờ các ma trận biến đổi phải là 4 x 4 để phù hợp với thành phần dịch thuật.

Tuy nhiên, bạn thực sự không cần lưu trữ một wthành phần trong vector phải không?

Ngay cả trong phân chia phối cảnh, bạn có thể chỉ cần tính toán và lưu trữ wbên ngoài vectơ và phân chia phối cảnh trước khi quay lại từ phương thức.

Ví dụ:

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

Có một điểm trong việc lưu trữ wtrong lớp Vector?


2
Đó không phải là việc thực hiện cho phép nhân vectơ ma trận thông thường, sự phân chia phối cảnh không thuộc về đó. Ngoài ra nó khá sai lệch, bởi vì các phần sai của phép tính được tô sáng. Nếu bạn muốn tìm hiểu thành phần w dùng để làm gì, hãy nhìn vào phần triển khai hoàn chỉnh, sau đó bạn thấy rằng hàng / cột cuối cùng (phần dịch) của ma trận chỉ được áp dụng, nếu thành phần w là 1, tức là cho điểm. Bạn nên làm nổi bật những phần đó: r.x = ... + a._14*v.w; r.y = ... + a._24*v.w; r.z = ... + a._34*v.w; r.w = ... + a._44*v.w;nhìn vào câu trả lời của tôi để biết chi tiết
Maik

Câu trả lời:


27

EDIT Disclaimer : Để thuận tiện trong vectơ câu trả lời này với w == 0 được gọi là vectơ và với w == 1 được gọi là điểm. Mặc dù như FxIII đã chỉ ra, đó không phải là một thuật ngữ chính xác về mặt toán học. Tuy nhiên, vì điểm của câu trả lời không phải là thuật ngữ, mà là cần phân biệt cả hai loại vectơ, tôi sẽ tuân theo nó. Vì lý do thực tế, quy ước này được sử dụng rộng rãi trong phát triển trò chơi.


Không thể phân biệt giữa vectơ và điểm nếu không có thành phần 'w'. Nó là 1 cho điểm và 0 cho vectơ.

Nếu các vectơ được nhân với ma trận biến đổi affine 4 x 4 có bản dịch ở hàng / cột cuối cùng của nó, thì vectơ cũng sẽ được dịch, đó là sai, chỉ phải dịch điểm. Số không trong thành phần 'w' của một vectơ đảm nhận việc đó.

Làm nổi bật phần này của phép nhân vectơ ma trận làm cho nó rõ ràng hơn:

    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) 

Tức là sẽ sai khi dịch vectơ, ví dụ trục xoay, kết quả đơn giản là sai, bằng cách có thành phần thứ 4 bằng 0, bạn vẫn có thể sử dụng cùng một ma trận biến đổi các điểm để biến đổi trục xoay và kết quả sẽ hợp lệ và chiều dài của nó được bảo tồn miễn là không có thang đo trong ma trận. Đó là hành vi bạn muốn cho vectơ. Nếu không có thành phần thứ 4, bạn sẽ phải tạo 2 ma trận (hoặc 2 hàm nhân khác nhau với tham số thứ 4 ẩn và thực hiện 2 lệnh gọi hàm khác nhau cho các điểm và vectơ.

Để sử dụng các thanh ghi vectơ của CPU hiện đại (SSE, Altivec, SPU), bạn phải vượt qua các phao 4x 32 bit (dù là thanh ghi 128 bit), ngoài ra bạn phải chăm sóc căn chỉnh, thường là 16 byte. Vì vậy, dù sao bạn cũng không có cơ hội để đảm bảo không gian cho thành phần thứ 4.


EDIT: Câu trả lời cho câu hỏi về cơ bản là

  1. Lưu trữ thành phần w: 1 cho các vị trí và 0 cho các vectơ
  2. Hoặc gọi các hàm nhân vectơ ma trận khác nhau và hoàn toàn vượt qua thành phần 'w' bằng cách chọn một trong hai hàm

Người ta phải chọn một trong số chúng, không thể chỉ lưu trữ {x, y, z} và vẫn chỉ sử dụng một hàm nhân vectơ ma trận. Ví dụ, XNA sử dụng cách tiếp cận sau bằng cách có 2 hàm Transform trong lớp Vector3 , được gọi TransformTransformNormal

Dưới đây là một ví dụ mã cho thấy cả hai cách tiếp cận và thể hiện sự cần thiết phải phân biệt cả hai loại vectơ theo 1 trong 2 cách có thể. Chúng tôi sẽ di chuyển một thực thể trò chơi có vị trí và hướng nhìn trong thế giới bằng cách chuyển đổi nó bằng ma trận. Nếu chúng ta không sử dụng thành phần 'w', chúng ta không thể sử dụng phép nhân vectơ ma trận giống nhau nữa, như ví dụ này chứng minh. Nếu chúng ta làm điều đó bằng mọi cách, chúng ta sẽ nhận được một câu trả lời sai cho look_dirvectơ đã chuyển đổi :

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

Trạng thái thực thể ban đầu:

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

Bây giờ, một phép biến đổi với bản dịch x + 5 và xoay 90 độ quanh trục y sẽ được áp dụng cho thực thể này. Câu trả lời đúng sau thông báo là:

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

Chúng tôi sẽ chỉ nhận được câu trả lời đúng nếu chúng tôi phân biệt vectơ với w == 0 và vị trí với w == 1 theo một trong các cách được trình bày ở trên.


@Maik Semder Bạn hơi sai một chút ... Không thể phân biệt giữa các vectơ một điểm bởi vì đó là những điều giống nhau! (Chúng là đẳng cấu) 1 cho vectơ và 0 cho verctor định hướng vô hạn (như tôi nói trong câu trả lời của tôi) . Phần còn lại của phản ứng có rất ít ý nghĩa vì các giả định sai.
FxIII

1
@FxIII Tôi không thấy quan điểm của bạn (không có ý định chơi chữ) và sự liên quan đến câu hỏi này. Bạn đang nói vectơ và điểm giống nhau, vì vậy, thật vô nghĩa khi lưu trữ 'w', nghiêm túc chứ? Bây giờ hoặc bạn sẽ cách mạng hóa đồ họa máy tính hoặc bạn không hiểu được câu hỏi này.
Maik

1
@FxIII Điều đó thật vô lý, bạn có thể muốn nghiên cứu một số khung toán học 3D được sử dụng trong phát triển trò chơi, ví dụ như vectormath của Sony , bạn sẽ tìm thấy rất nhiều triển khai như vậy, xem xét việc triển khai vmathV4MakeFromV3 và vmathV4MakeFromP3. và những gì họ đưa vào thành phần thứ 4, 1.0 cho P3 và 0.0 cho V3, điểm 3D và vectơ 3D rõ ràng.
Maik

3
@FxIII đó cũng là lý do tại sao lớp XNA Vector3 có hàm thành viên "Transform" và "TransformN normal ", lý do toán học của đại số tuyến tính. Về cơ bản, những gì bạn làm bằng cách chọn một trong các hàm Transform đó là truyền tham số 'w' ẩn '1' hoặc '0', về cơ bản có bao gồm hàng thứ 4 của ma trận vào phép tính hay không. Tóm tắt: Nếu bạn không lưu trữ thành phần 'w', thì bạn phải xử lý các vectơ đó khác nhau bằng cách gọi các hàm biến đổi khác nhau.
Maik

1
Các vectơ và điểm là đẳng cấu như đã nói, do đó không có sự khác biệt đại số giữa chúng. Tuy nhiên, điều mà mô hình đồng nhất của không gian chiếu cố gắng thể hiện là TẬP THỂ SPACES và các điểm không phải là đẳng cấu. Tập hợp các không gian vectơ có hiệu lực là một kiểu đóng cho R ^ 3 bao gồm các điểm trên quả cầu vô hạn. Các điểm có w = 0 thường được gọi không chính xác là "vectơ" - chúng thực sự là đẳng cấu với hình cầu hướng và sẽ được gọi chính xác hơn là "chỉ đường" ... Và không, mất w thường có thể hoạt động, nhưng chủ yếu là bạn sẽ đang tìm kiếm rắc rối.
Crowley9

4

Nếu bạn đang tạo một lớp Vector, thì tôi đoán rằng lớp sẽ lưu trữ mô tả của một vectơ 3D. Các vectơ 3D có độ lớn x, y và z. Vì vậy, trừ khi vectơ của bạn cần một cường độ w tùy ý, không, bạn sẽ không lưu trữ nó trong lớp.

Có một sự khác biệt lớn giữa một vectơ và ma trận biến đổi. Vì cả DirectX và OpenGL đều xử lý ma trận cho bạn, tôi thường không lưu trữ ma trận 4 x 4 trong mã của mình; thay vào đó, tôi lưu trữ các phép quay Euler (hoặc Quancyions nếu bạn muốn - điều trùng hợp ngẫu nhiên có thành phần aw) và bản dịch x, y, z. Bản dịch là một vectơ nếu bạn muốn, và kỹ thuật xoay cũng sẽ phù hợp với một vectơ, trong đó mỗi thành phần sẽ lưu trữ số lượng xoay quanh trục của nó.

Nếu bạn muốn tìm hiểu sâu hơn một chút về toán học của một vectơ, vectơ Euclide chỉ là một hướng và độ lớn. Vì vậy, thông thường, điều này được biểu thị bằng một bộ ba số, trong đó mỗi số là độ lớn dọc theo một trục; hướng của nó được ngụ ý bởi sự kết hợp của ba độ lớn này và độ lớn có thể được tìm thấy với công thức khoảng cách Euclide . Hoặc, đôi khi nó thực sự được lưu trữ dưới dạng hướng (vectơ có chiều dài = 1) và độ lớn (phao), nếu đó là điều thuận tiện (ví dụ: nếu cường độ thay đổi thường xuyên hơn hướng, có thể thuận tiện hơn khi chỉ thay đổi số độ lớn đó hơn là lấy một vectơ, chuẩn hóa nó và nhân các thành phần với độ lớn mới).


6
OpenGL hiện đại không giải quyết các ma trận cho bạn.
SurvivalMachine

4

Kích thước thứ tư trong vectơ 3D được sử dụng để tính toán các phép biến đổi affine sẽ không thể tính toán bằng các ma trận một mình. Không gian vẫn là ba chiều, do đó, điều này có nghĩa là thứ tư được ánh xạ trong không gian 3d theo một cách nào đó.

Ánh xạ một kích thước có nghĩa là các vectơ 4D khác nhau biểu thị cùng một điểm 3D. Bản đồ là nếu A = [x ', y', z'.w '] và B = [x ", y", z ", w"] chúng đại diện cho cùng một điểm nếu x' / x "= y ' / y "= z '/ z" = w' / w "= α tức là thành phần tỷ lệ với cùng một hệ số α.

Nói rằng bạn có thể diễn đạt một điểm - nói (1,3,7) - theo cách cư xử vô hạn như (1,3,7,1) hoặc (2,6,14,2) hoặc (131,393,917,131) hoặc nói chung (α · 1, α · 3, α · 7, α).

Điều này có nghĩa là bạn có thể chia tỷ lệ một vectơ 4D sang một vectơ khác biểu thị cùng một điểm 3D sao cho w = 1: dạng (x, y, z, 1) là dạng chính tắc.

Khi bạn áp dụng một ma trận cho vectơ này, bạn có thể có được một vectơ không có w = 1, nhưng bạn luôn có thể chia tỷ lệ kết quả để lưu trữ nó ở dạng chính tắc. Vì vậy, câu trả lời là "bạn nên sử dụng vectơ 4D khi làm toán nhưng không lưu trữ thành phần thứ tư" .

Điều này khá đúng nhưng có một số điểm bạn không thể đặt ở dạng chính tắc: các điểm như (4.2,5,0). Những điểm đó là những điểm đặc biệt, chúng đại diện cho điểm vô hạn có hướng và có thể được chuẩn hóa thành vectơ đơn vị một cách nhất quán: bạn có thể đi đến vô hạn và quay trở lại (thậm chí hai lần) mà không phải là Chuck Norris. Bạn sẽ nhận được một phép chia khốn khổ bằng 0 nếu bạn cố ép các vectơ đó ở dạng chính tắc.

Bây giờ bạn biết, vì vậy sự lựa chọn là của bạn!


1

Vâng, bạn làm. Chuyển đổi của bạn là không chính xác cho một số loại vector. Bạn có thể thấy điều này trong thư viện toán học D3DX - chúng có hai hàm nhân vectơ ma trận khác nhau, một cho w = 0 và một cho w = 1.


0

Phụ thuộc vào những gì bạn muốn và cần. :)

Tôi sẽ lưu trữ nó, vì nó cần thiết cho các phép biến đổi và như vậy (bạn không thể nhân 3 vectơ với ma trận 4 x 4), mặc dù nếu bạn luôn chỉ có 1, tôi đoán bạn có thể giả mạo nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.