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à
- Lưu trữ thành phần w: 1 cho các vị trí và 0 cho các vectơ
- 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 Transform
vàTransformNormal
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_dir
vectơ đã 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.
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