Нужен ли мне компонент 'w' в моем классе Vector?

21

Предположим, вы пишете матричный код, который обрабатывает вращение, перемещение и т. Д. Для трехмерного пространства.

Теперь матрицы преобразования должны быть 4х4, чтобы соответствовать компоненту перевода.

Однако вам не нужно хранить 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в классе Vector?

bobobobo
источник
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 матрицы (или 2 разные функции умножения с неявным 4-м параметром) и сделать 2 разных вызова функций для точек и векторов.

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


РЕДАКТИРОВАТЬ: ответ на вопрос в основном

  1. Сохраните w-компонент: 1 для позиций и 0 для векторов
  2. Или вызовите разные функции умножения матрицы на вектор и неявно передайте компонент 'w', выбрав одну из функций

Нужно выбрать один из них, невозможно сохранить только {x, y, z} и при этом использовать только одну функцию умножения матрицы на вектор. XNA, например, использует последний подход, имея 2 функции Transform в своем классе Vector3 , которые называются TransformиTransformNormal

Вот пример кода, который демонстрирует оба подхода и демонстрирует необходимость различать оба вида векторов одним из двух возможных способов. Мы переместим игровую сущность с позицией и направлением взгляда в мире, преобразовав ее с помощью матрицы. Если мы не используем компонент '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
источник
@Maik Semder Вы немного не правы ... Невозможно расставить точки между векторами и точками, потому что это одно и то же! (Они изоморфны) 1 для векторов и 0 для бесконечно направленных векторов (как я говорю в своем ответе) , Остальная часть ответа не имеет большого смысла из-за неправильных предположений.
FxIII
1
@FxIII Я не понимаю твою точку зрения (не каламбур) и актуальность этого вопроса. Вы говорите, что векторы и точки одинаковы, так что нет никакого смысла хранить 'w' в любом случае, серьезно? Теперь вы либо совершите революцию в компьютерной графике, либо не поймете смысл этого вопроса.
Майк Земдер
1
@FxIII Это чепуха, вы можете изучить некоторые математические 3D-фреймворки, используемые при разработке игр, например, Sony Vectormath , вы найдете множество таких реализаций, в частности, посмотрите на реализацию vmathV4MakeFromV3 и vmathV4MakeFromP3 в vec_aos.h, изучите разницу и что они вкладывают в 4-й компонент: 1,0 для P3 и 0,0 для V3, 3D-точки и 3D-вектор, очевидно.
Майк Земдер
3
@FxIII также является причиной того, что класс XNA Vector3 имеет функцию-член «Transform» и «TransformNormal», причина - математика линейной алгебры. То, что вы в основном делаете, выбирая одну из этих функций Transform, передает неявный параметр 'w', равный '1' или '0', который в основном включает 4-ю строку матрицы в расчет или нет. Подводя итог: если вы не сохраняете компонент 'w', то вам нужно обрабатывать эти векторы по-разному, вызывая различные функции преобразования.
Майк Земдер
1
Векторы и точки, как сказано, изоморфны, следовательно, между ними нет алгебраической разницы. Однако то, что пытается представить однородная модель проективного пространства, состоит в том, что множество векторных пространств и точек не являются изоморфными. Множество векторных пространств фактически является типом замыкания для R ^ 3, который включает точки на бесконечной сфере. Точки с w = 0 часто неправильно называют «векторами» - они на самом деле изоморфны сфере направлений и точнее будут называться просто «направлениями» ... И нет, потеря w может часто работать, но в основном вы будете находить проблемы.
Crowley9
4

Если вы создаете класс Vector, я предполагаю, что класс будет хранить описание трехмерного вектора. Трехмерные векторы имеют значения x, y и z. Поэтому, если вашему вектору не нужна произвольная величина w, нет, вы не будете хранить его в классе.

Существует большая разница между вектором и матрицей преобразования. Учитывая, что DirectX и OpenGL имеют дело с матрицами для вас, я обычно не храню матрицу 4x4 в своем коде; скорее, я храню ротации Эйлера (или кватернионы, если хотите - у которых по совпадению действительно есть компонент w) и перевод x, y, z. Если хотите, перевод представляет собой вектор, и вращение технически будет соответствовать и вектору, где каждый компонент будет хранить величину вращения вокруг своей оси.

Если вы хотите глубже погрузиться в математику вектора, евклидов вектор - это только направление и величина. Поэтому, как правило, это представляется тройкой чисел, где каждое число является величиной вдоль оси; его направление подразумевается комбинацией этих трех величин, и величина может быть найдена с помощью формулы евклидова расстояния . Или, иногда, оно действительно сохраняется как направление (вектор с длиной = 1) и величиной (с плавающей точкой), если это удобно (например, если величина меняется чаще, чем направление, может быть удобнее просто изменить это значение, чем взять вектор, нормализовать его и умножить компоненты на новое значение).

Ricket
источник
6
Современный OpenGL не работает с матрицами для вас.
SurvivalMachine
4

Четвертое измерение в трехмерном векторе используется для вычисления аффинных преобразований, которые невозможно будет вычислить, используя только матрицы. Пространство остается трехмерным, так что это означает, что четвертое отображается каким-то образом в трехмерном пространстве.

Карта размеров означает, что разные 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, α).

Это означает, что вы можете масштабировать четырехмерный вектор на другой, представляющий ту же трехмерную точку, так что w = 1: форма (x, y, z, 1) является канонической формой.

Применяя матрицу к этому вектору, вы можете получить вектор, который не имеет w = 1, но вы всегда можете масштабировать результаты, чтобы сохранить их в канонической форме. Таким образом, ответ выглядит так: «Вы должны использовать 4D-векторы при выполнении математики, но не хранить четвертый компонент» .

Это вполне верно, но есть некоторые моменты, которые вы не можете выразить в канонической форме: такие как (4,2,5,0). Эти точки являются особыми, они представляют направленную бесконечную точку и могут быть последовательно нормализованы к единичному вектору: вы можете безопасно перейти к бесконечности и вернуться (даже дважды), не будучи Чаком Норрисом. Вы получите жалкое деление на ноль, если попытаетесь привести эти векторы в каноническую форму.

Теперь вы знаете, так что выбор за вами!

FXIII
источник
1

Да, вы делаете. Ваше преобразование неверно для некоторых видов векторов. Вы можете увидеть это в математической библиотеке D3DX - у них есть две разные функции умножения матрицы на вектор, одна для w = 0 и одна для w = 1.

DeadMG
источник
0

Зависит от того, что вы хотите и нужно. :)

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

ледоруб
источник