Preciso do componente 'w' na minha classe Vector?

21

Suponha que você esteja escrevendo um código de matriz que lida com rotação, tradução etc. para o espaço 3D.

Agora as matrizes de transformação precisam ser 4x4 para ajustar o componente de conversão.

No entanto, você realmente não precisa armazenar um wcomponente no vetor, precisa ?

Mesmo na divisão de perspectiva, você pode simplesmente calcular e armazenar wfora do vetor e dividir a perspectiva antes de retornar do método.

Por exemplo:

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

Existe um ponto em armazenar wna classe Vector?

bobobobo
fonte
2
Essa não é a implementação para uma multiplicação normal de vetores matriciais, a divisão da perspectiva não pertence a ela. Também é bastante enganador, porque as partes erradas do cálculo são destacadas. Se você quiser descobrir para que serve o componente w, observe a implementação completa e verá que a última linha / coluna (a parte de conversão) da matriz é aplicada apenas se o componente w for 1, ou seja, por pontos. Você deve destacar as partes: r.x = ... + a._14*v.w; r.y = ... + a._24*v.w; r.z = ... + a._34*v.w; r.w = ... + a._44*v.w;Olhe para a minha resposta para mais detalhes
Maik Semder

Respostas:

27

Isenção de responsabilidade da edição : por conveniência nesta resposta, vetores com w == 0 são chamados vetores e com w == 1 são chamados pontos. Embora, como FxIII apontou, essa não seja uma terminologia matematicamente correta. No entanto, como o ponto da resposta não é a terminologia, mas a necessidade de distinguir os dois tipos de vetores, eu continuarei com isso. Por razões práticas, esta convenção é amplamente usada no desenvolvimento de jogos.


Não é possível distinguir entre vetores e pontos sem um componente 'w'. É 1 para pontos e 0 para vetores.

Se os vetores forem multiplicados por uma matriz de transformação afina 4x4 que tenha uma tradução em sua última linha / coluna, o vetor também será traduzido, o que está errado, apenas os pontos devem ser traduzidos. O zero no componente 'w' de um vetor cuida disso.

Destacar esta parte da multiplicação de vetores de matriz torna mais claro:

    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) 

Ou seja, seria errado traduzir um vetor, por exemplo, um eixo de rotação, o resultado é simplesmente errado. Ao ter seu 4º componente zero, você ainda pode usar a mesma matriz que transforma os pontos para transformar o eixo de rotação e o resultado será válido. e seu comprimento é preservado desde que não exista escala na matriz. Esse é o comportamento que você deseja para vetores. Sem o 4º componente, você teria que criar 2 matrizes (ou 2 funções de multiplicação diferentes com um 4º parâmetro implícito) e fazer 2 chamadas de funções diferentes para pontos e vetores.

Para usar os registradores vetoriais de CPUs modernas (SSE, Altivec, SPUs), é necessário passar flutuações 4x de 32 bits de qualquer maneira (é um registrador de 128 bits), além de cuidar do alinhamento, geralmente 16 bytes. Portanto, você não tem a chance de proteger o espaço para o quarto componente.


EDIT: A resposta para a pergunta é basicamente

  1. Armazene o componente w: 1 para posições e 0 para vetores
  2. Ou chame diferentes funções de multiplicação de vetores matriciais e passe implicitamente o componente 'w' escolhendo uma das funções

É preciso escolher um deles, não é possível armazenar apenas {x, y, z} e ainda usar apenas uma função de multiplicação de vetores matriciais. O XNA, por exemplo, usa a última abordagem, tendo 2 funções Transform em sua classe Vector3 , chamadas TransformeTransformNormal

Aqui está um exemplo de código que mostra as duas abordagens e demonstra a necessidade de distinguir os dois tipos de vetores em uma das duas maneiras possíveis. Vamos mover uma entidade do jogo com uma posição e uma direção de olhar no mundo, transformando-a em uma matriz. Se não usarmos o componente 'w', não poderemos mais usar a mesma multiplicação de vetores de matriz, como este exemplo demonstra. Se fizermos de qualquer maneira, obteremos uma resposta errada para o look_dirvetor transformado :

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

Estado inicial da entidade:

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

Agora, uma transformação com uma translação de x + 5 e uma rotação de 90 graus em torno do eixo y será aplicada a essa entidade. A resposta correta após a transformação é:

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

Apenas obteremos a resposta correta se distinguirmos vetores com w == 0 e posições com w == 1 de uma das maneiras apresentadas acima.

Maik Semder
fonte
@Maik Semder Você está um pouco errado ... Não é possível diferenciar entre vetores e pontos porque esses são a mesma coisa (eles são isomórficos) 1 para vetores e 0 para verificadores direcionados infinitos (como eu digo na minha resposta) . O restante da resposta tem pouco sentido por causa de suposições erradas.
FxIII
11
@FxIII Não vejo o seu ponto (sem trocadilhos) e a relevância desta questão. Você está dizendo que vetores e pontos são os mesmos, então não faz sentido armazenar 'w' de qualquer maneira, sério? Agora, você revolucionará a computação gráfica ou não entenderá o assunto.
Maik Semder
11
@FxIII Isso é um absurdo, talvez você queira estudar algumas estruturas matemáticas em 3D usadas no desenvolvimento de jogos, por exemplo, o vetor da Sony , você encontrará muitas dessas implementações. e o que eles colocam no quarto componente, 1,0 para P3 e 0,0 para V3, ponto 3D e vetor 3D obviamente.
Maik Semder
3
@FxIII também é o motivo pelo qual a classe XNA Vector3 tem uma função de membro "Transform" e "TransformNormal", o motivo é a matemática da álgebra linear. Basicamente, o que você faz escolhendo uma dessas funções Transform é passar um parâmetro 'w' implícito de '1' ou '0', que basicamente inclui a quarta linha da matriz no cálculo ou não. Resumir: se você não armazenar o componente 'w', precisará tratar esses vetores de maneira diferente, chamando diferentes funções de transformação.
Maik Semder
11
Vetores e pontos são isomórficos como foi dito, portanto, não há diferença algébrica entre eles. No entanto, o que o modelo homogêneo de espaço projetivo tenta representar é que o conjunto de vetores SPACES e pontos não são isomórficos. O conjunto de espaços vetoriais é, na verdade, um tipo de fechamento para R ^ 3 que inclui os pontos na esfera infinita. Pontos com w = 0 são freqüentemente chamados incorretamente de "vetores" - na verdade, são isomórficos para a esfera de direção e seriam mais precisamente denominados simplesmente "direções" ... E não, perder w pode frequentemente funcionar, mas geralmente você estar encontrando problemas.
Crowley9
4

Se você estiver criando uma classe Vector, presumo que a classe armazene a descrição de um vetor 3D. Os vetores 3D têm magnitudes x, ye z. Portanto, a menos que seu vetor precise de uma magnitude arbitrária, não, você não o armazenará na classe.

Há uma grande diferença entre um vetor e uma matriz de transformação. Como o DirectX e o OpenGL lidam com matrizes para você, normalmente não armazeno uma matriz 4x4 no meu código; em vez disso, armazeno rotações de Euler (ou Quaternions, se você quiser - que coincidentemente têm um componente aw) e tradução x, y, z. A tradução é um vetor, se você desejar, e a rotação também caberia tecnicamente em um vetor, onde cada componente armazenaria a quantidade de rotação em torno de seu eixo.

Se você quiser mergulhar um pouco mais na matemática de um vetor, um vetor euclidiano é apenas uma direção e uma magnitude. Então, tipicamente, isso é representado por um triplo de números, onde cada número é a magnitude ao longo de um eixo; sua direção está implícita pela combinação dessas três magnitudes, e a magnitude pode ser encontrada com a fórmula da distância euclidiana . Ou, às vezes, ele realmente é armazenado como uma direção (um vetor com comprimento = 1) e uma magnitude (uma flutuação), se é o que é conveniente (por exemplo, se a magnitude mudar com mais frequência do que a direção, pode ser mais conveniente apenas alterar esse número de magnitude do que pegar um vetor, normalizá-lo e multiplicar os componentes pela nova magnitude).

Ricket
fonte
6
O OpenGL moderno não lida com matrizes para você.
SurvivalMachine
4

A quarta dimensão no vetor 3D é usada para calcular as transformações afins que serão impossíveis de calcular usando apenas matrizes. O espaço permanece tridimensional, então isso significa que o quarto é mapeado no espaço 3D de alguma forma.

Mapear uma dimensão significa que diferentes vetores 4D indicam o mesmo ponto 3D. O mapa é que, se A = [x ', y', z'.w '] e B = [x ", y", z ", w"], eles representam o mesmo ponto se x' / x "= y ' / y "= z '/ z" = w' / w "= α, isto é, o componente é proporcional para o mesmo coeficiente α.

Disse que você pode expressar um ponto - digamos (1,3,7) - de maneiras infinitas, como (1,3,7,1) ou (2,6,14,2) ou (131,393,917,131) ou em geral (α · 1, a · 3, a · 7, a).

Isso significa que você pode dimensionar um vetor 4D para outro representando o mesmo ponto 3D, para que w = 1: a forma (x, y, z, 1) seja a forma canônica.

Quando você aplica uma matriz a esse vetor, pode obter um vetor que não tenha w = 1, mas sempre é possível dimensionar os resultados para armazená-lo de forma canônica. Portanto, a resposta parece ser "você deve usar vetores 4D ao fazer contas, mas não armazene o quarto componente" .

Isso é verdade, mas há alguns pontos que você não pode colocar em forma canônica: pontos como (4,2,5,0). Esses pontos são especiais, representam pontos infinitos direcionados e podem ser normalizados para o vetor unitário de forma consistente: você pode ir com segurança ao infinito e retornar (mesmo duas vezes) sem ser Chuck Norris. Você obterá uma divisão miserável por zero se tentar forçar esses vetores de forma canônica.

Agora você sabe, então a escolha é sua!

FxIII
fonte
1

Sim você faz. Sua transformação está incorreta para alguns tipos de vetor. Você pode ver isso na biblioteca matemática do D3DX - eles têm duas funções diferentes de multiplicação de vetores matriciais, uma para w = 0 e outra para w = 1.

DeadMG
fonte
0

Depende do que você deseja e precisa. :)

Eu armazená-lo-ia, porque é necessário para transformações e coisas do tipo (você não pode multiplicar um vetor 3 com uma matriz 4x4), embora se você sempre tiver apenas 1 de um, acho que poderia fingir.

picador de gelo
fonte