SSE3 e a aritimética horizontal. Calculando “planos”

Num ambiente 3D temos que decidir quais objetos serão desenhados e quais não o serão. Isso é feito comparando a distância de um objeto até um dos planos que compõem o campo de visão. O campo de visão é composto de 6 planos, formando um trapezóide chamado frustum:

Só o que está "dentro" do "frustum" é visível pela câmera!

O conceito de “dentro” ou “fora” depende de como os 6 planos do frustum são definidos. Um plano segue sempre a equação:

Ax + By + Cz + D = 0

E os coeficientes A, B, C e D são calculados da seguinte forma: Obtemos 3 pontos que estão no plano, com esses 3 pontos obtemos 2 vetores. O produto vetorial deles nos dá o vetor perpendicular ao plano. Este vetor perpendicular, depois de ter suas coordenadas divididas pelo tamanho do mesmo é conhecido como “vetor normal”.

No caso do OpenGL, o vetor normal, no frustum, aponta para dentro do frustum.

Os coeficientes A, B e C são exatamente as coordenadas do vetor normal. O coeficiente D é a projeção de um ponto qualquer no plano em relação ao vetor normal calculado anteriormente.

Se tudo isso é grego pra você, recomendo que estude um pouco de Geometria Analítica, isto é, se houver interesse em aprender algo sobre Computação Gráfica!

Aqui vem o pedaço que quero mostrar: Para calcularmos a distância entre um ponto qualquer no espaço em relação ao plano, basta substituir as variáveis x, y e z da equação acima pelas coordenadas do ponto. E aqui entra uma instrução interessante do SSE3. A adição horizontal. Suponhamos que os coeficientes que definem um plano estejam armazenados numa variável do tipo vec4_t (que já definimos em outros exemplos). E o ponto que desejamos calcular a distância em relação ao plano também tem suas coordenadas armazenadas numa variável do tipo vec4_t. Calcular a distância fica fácil:

float GetDistanceFromPlane(vec4_t plane, vec4_t pt)
{
  vec4_t t;
  t.x = _mm_mul_ps(plane.x, pt.x);
  return (t.v[0] + t.v[1] + t.v[2] + t.v[3]);
}

O problema dessa rotina é que temos 4 adições. Que tal reduzirmos essas 4 para apenas 2?

float GetDistanceFromPlane(vec4_t plane, vec4_t pt)
{
  vec4_t t;
  t.x = _mm_mul_ps(plane.x, pt.x);
  t.x = _mm_hadd_ps(t.x, t.x);
  t.x = _mm_hadd_ps(t.x, t.x);
  return t.v[0];
}

A função _mm_hadd_ps() funciona assim:

hadd faz adições horizontais. Isso não era permitido no SSE e no SSE2!

E quanto aos 2 blocos r2 e r3, na figura acima? Bem… por isso _mm_hadd_ps toma dois operandos. A figura acima mostra o que acontece com o primeiro operando. a mesma coisa acontece com o segundo, mas os resultados são colocados em r2 e r3.

Essa, para mim, é a principal vantagem de usar SSE e SSE3. Temos múltiplas operações verticais (entre registradores) e horizontais (no mesmo registrador). SSE4.1 ainda fornece uma coisa muito interessante: produto escalar! A rotina que mostrei neste post (veja a rotina DotProduct) já existe em uma única instrução, no SSE 4.1!

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s