Чи потрібен компонент 'w' у моєму класі Vector?


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в класі Вектор?


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;подивіться детально мою відповідь
Майк Семерд

Відповіді:


27

EDIT Застереження : Для зручності у цій відповіді вектори з w == 0 називаються векторами, а з w == 1 називаються точками. Хоча, як зазначав FxIII, це не є математично правильною термінологією. Однак, оскільки суть відповіді - не термінологія, а необхідність розрізняти обидва типи векторів, я дотримуюся цього. З практичної причини ця конвенція широко використовується в ігровій розробці.


Неможливо розрізнити вектори та точки без компонента 'w'. Це 1 для балів і 0 для векторів.

Якщо вектори множать на матрицю перетворення афінної трансформації 4х4, яка має переклад в останньому рядку / стовпчику, вектор також буде переведений, що невірно, повинні бути переведені лише точки. Нуль в '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 матриці (або 2 різні функції множення з неявним 4-м параметром.) Та зробити 2 різні виклики функцій для точок і векторів.

Для того, щоб використовувати векторні регістри сучасних процесорів (SSE, Altivec, SPU), вам в будь-якому випадку потрібно пройти 4х 32-бітові поплавці (це 128-бітний регістр), плюс вам потрібно подбати про вирівнювання, як правило, 16 байт. Таким чином, у вас немає шансів зберегти місце для 4-го компонента.


EDIT: Відповідь на питання в основному

  1. Або зберігайте w-компонент: 1 для позицій і 0 для векторів
  2. Або викликати різні функції множення матричного вектора та неявно передавати компонент 'w', вибравши одну з будь-яких функцій

Треба вибрати одну з них, зберігати не можна лише {x, y, z} і все ж використовувати лише одну функцію множення матрично-векторного. Наприклад, XNA використовує останній підхід, маючи 2 функції трансформації у своєму класі Vector3 , звані TransformтаTransformNormal

Ось приклад коду, який показує обидва підходи та демонструє необхідність розрізняти обидва типи векторів за 1 із 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 та обертанням на 90 градусів навколо осі y. Правильна відповідь після перетворення:

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 , ви знайдете безліч таких реалізацій, детально подивіться на реалізацію vmathV4MakeFromV3 та vmathV4MakeFromP3 у vec_aos.h, вивчіть різницю і що вони вкладають у 4-й компонент, 1,0 для P3 і 0,0 для V3, 3D-точки та 3D-очевидно.
Майк Семдер

3
@FxIII, що також є причиною того, що клас XNA Vector3 має функцію "Transform" і "TransformNormal", член - це математика лінійної алгебри. Те, що ви в основному робите, обираючи одну з цих функцій Трансформація, - це передавання неявного параметра "w" з "1" або "0", який в основному включає в обчислення четвертий рядок матриці чи ні. Підсумуйте: Якщо ви не зберігаєте компонент 'w', вам доведеться по-різному ставитися до цих векторів, викликаючи різні функції перетворення.
Майк Семдер

1
Як сказано, вектори і точки є ізоморфними, отже, між ними немає алгебраїчної різниці. Однак гомогенна модель проективного простору намагається зобразити - це те, що SET векторних просторів і точок не є ізоморфним. Набір векторних просторів фактично є типом замикання для R ^ 3, що включає точки на нескінченній сфері. Точки з w = 0 часто неправильно називають "векторами" - це насправді ізоморфно напрямковій сфері і було б точніше назвати просто "напрямками" ... І ні, втрата w може часто спрацювати, але в основному ви будете знаходити неприємності.
Crowley9

4

Якщо ви робите векторний клас, я припускаю, що клас зберігатиме опис 3D-вектора. 3D-вектори мають величини x, y та z. Тому, якщо ваш вектор не потребує довільної w-величини, ні, ви не будете зберігати його у класі.

Існує велика різниця між вектором і матрицею перетворення. З огляду на те, що і DirectX, і OpenGL мають справу з матрицями для вас, я зазвичай не зберігаю матрицю 4x4 у своєму коді; скоріше, я зберігаю обертання Ейлера (або кватерніони, якщо ви хочете - у яких випадково є компонент aw) та переклад x, y, z. Переклад є вектором, якщо ви хочете, і обертання технічно підходило б і до вектора, де кожен компонент зберігав би обертання навколо своєї осі.

Якщо ви хочете зануритися трохи глибше в математику вектора, евклідовий вектор - це лише напрямок і величина. Тому зазвичай це представлено трійкою чисел, де кожне число є величиною вздовж осі; його напрямок має на увазі поєднання цих трьох величин, і величину можна знайти за формулою відстані Евкліда . Або іноді він дійсно зберігається як напрямок (вектор з довжиною = 1) і величиною (поплавок), якщо це зручно (наприклад, якщо величина змінюється частіше, ніж напрямок, це може бути зручніше просто змінити це число величини, ніж взяти вектор, нормалізувати його і помножити компоненти на нову величину).


6
Сучасний OpenGL не має для вас матриць.
SurvivalMachine

4

Четвертий вимір у 3D-векторі використовується для обчислення афінних перетворень, які неможливо буде обчислити, використовуючи лише матриці. Простір залишається тривимірним, тому це означає, що четвертий якимось чином відображається в 3d просторі.

Зображення розмірів означає, що різні 4D вектори вказують на одну і ту ж триточну точку. Карта полягає в тому, що якщо 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,2,5,0). Ці точки є особливими, вони являють собою спрямовану нескінченну точку і можуть бути нормалізовані до одиничного вектора послідовно: можна сміливо йти до нескінченності та повертатися (навіть удвічі), не будучи Чак Норрісом. Ви отримаєте жалюгідний поділ на нуль, якщо спробуєте насилити ці вектори в канонічній формі.

Тепер ви знаєте, тому вибір за вами!


1

Так ти зробиш. Трансформація невірна для деяких видів вектора. Це можна побачити в математичній бібліотеці D3DX - вони мають дві різні функції множення матричних векторів, одну для w = 0 і одну для w = 1.


0

Залежить від того, що ти хочеш і потребуєш. :)

Я б зберігав його, так як він необхідний для перетворень і таких (ви не можете помножити 3 вектора з матрицею 4х4), хоча якщо у вас завжди просто 1, я думаю, ви могли це просто підробити.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.