Atualmente as engines 3D estão muito presentes em aplicações web. Com o ActionScript3 foi possível desenvolver bibliotecas 3D velozes e com isso a tecnologia se difundiu rapidamente pela web. Hoje em dia temos engines 3D muito bem elaboradas como Papervision3D, Away3D, Sandy3D, Alternativa e muitas outras.
O uso é relativamente simples, uma vez que o cálculo por traz de qualquer biblioteca é automatizado e independe do entendimento do desenvolvedor. Porém eu acredito que é extremamente importante entender ao máximo o funcionamento de qualquer aplicação, biblioteca ou classe, seja pelo conhecimento ou para aprimorar algo que ja foi criado, implementando uma nova funcionalidade ou melhorando as existentes.
Independente da maneira como é construída, qualquer engine 3D, independente da liguagem de programação utilizada, compartilham de alguns elementos em comum, um deles é o que chamamos de Matrix de Transformação. Sim matrizes, aquilo que aprendemos no colégio e que não servia para nada até o dia que resolvemos virar desenvolvedores de aplicações gráficas. Mas antes de entender o que é uma matriz de transformação, precisamos entender o que ela faz. Para isso vamos pensar numa engine 3D de uma forma mais simplificada.
Por exemplo, vamos imaginar um cubo como um conjunto de 8 pontos conectados entre si:
Cada ponto representa uma posição no espaço, essa posição é o que os matemáticos chamam de vetor. Para movimentar o cubos, é preciso movimentar esses pontos seguindo uma determinada ordem, cada ponto se transforma da mesma forma seguindo sua origem. Essa transformação é o que chamamos de matriz de transformação.
Essa transformação pode ocorrer de 3 formas em qualquer um dos eixos:
- Translação

- Escalonamento

- Rotação

Para entender o que é uma Matriz de Transformação, precisamos entender outros conceitos matemáticos como vetores, operações trigonométrigas e operações com matrizes.
Primeiro vamos imaginar um vetor como uma posição no espaço, um conjunto de 3 coordenadas (x, y e z):
Para modificar essa posição, podemos adicionar um vetor ao outro resultando num terceiro vetor. Vamos estabelecer um vetor inicial de coordenadas x, y e z respectivamente 5, 2, 0 e que a partir dessa posição vamos mover 4, 2 e 3 unidades respectivamente, resultando no vetor final de valor 9, 4 e 3
Essa transformação que chamamos de translação.
Tomando a translação como a soma de dois vetores, podemos dizer que o escalonamento se da atravéz da multiplicação de um vetor por um número.
Esse escalonamento pode acontecer em qualquer um dos eixos, de uma forma simplificada podemos considerar o seguinte:
*Lembre-se que esse exemplo não está matematicamente correto, mas por hora essa é a melhor forma de entendimento
Para entender como funciona a rotação de um ponto, devemos considerar o seguinte:
Imagine o vetor que representa a posição do ponto (x, y) como o raio (r) de um eixo de rotação que tem um ângulo inicial (a).
Logo, podemos deduzir que:
x = r*cos(a)
y = r*sen(a)
A rotação desse ponto nada mais é do que a soma desse ângulo (a) a outro (b) resultando no ângulo final (a+b) de rotação desse eixo:
x’ = r*cos(a+b)
y’ = r*sen(a+b)
Lembrando duas das identidades trigonométricas, sabemos que:
cos(a+b) = cos(a)*cos(b) – sen(a)*cos(b)
sen(a+b) = sen(a)*cos(b) + sen(b)*cos(a)
Logo:
x’ = r*cos(a+b) = r*cos(a)*cos(b) – r*sen(a)*cos(b)
y’ = r*sen(a+b) = r*sen(a)*cos(b) + r*sen(b)*cos(a)
Substituindo r*cos(a) por x e r*sen(a) por y, deduzimos que:
x’ = x*cos(b) – y*sen(b)
y’ = x*sen(b) + y*cos(b)
Em um sistema de 3 eixos essa rotação se daria em torno do eixo z, logo sua posição nesse eixo não mudaria.
x’ = x*cos(b) – y*sen(b)
y’ = x*sen(b) + y*cos(b)
z’ = z
Transpondo essa rotação para o eixo y temos a seguinte equação
x’ = x*cos(b) + z*sen(b)
y’ = y
z’ = -x*sen(b) + z*cos(b)
e no eixo x
x’ = x
y’ = y*cos(b) – z*sen(b)
z’ = y*sen(b) + z*cos(b)
O importante não é decorar essas fórmulas, mas entender o padrão que elas formam.
Se organizarmos esse valores em uma tabela da seguinte forma:
Temos uma matriz onde cada linha pertence a uma parte do vetor e cada coluna representa um elemento pelo qual será multiplicado. A primeira linha pode ser lida da seguinte forma:
x’ = x* cos(b) + y* -sin(b) + z* 0
A segunda como
y’ = x* sin(b) + y* cos(b) + z* 0
e a terceira como
z’ = x* 0 + y* 0 + z* 1
A vector can be multiplied by a matrix as this:Podemos considerar essa, como a matriz de rotação em torno do eixo z, onde b é o ângulo de rotação.
Um vetor pode ser multiplicado por uma matriz da seguinte forma:
Aplicando essa mesma lógica para o escalonamento, obtemos uma matriz de escala como essa:
Matrizes também podem ser multiplicadas (concatenadas) entre si, resultando em uma matriz de mesma ordem:
Uma vez que a translação é considerada como vetor e não como matriz, ao invez de utilizar uma matriz de transformação de terceira ordem (3×3) costuma-se utilizar uma matriz de quarta ordem (4×4), onde a ultima linha é composta por zeros e a ultima coluna contém os valores de translação.
Por fim, deve-se concatenar as matrizes de rotação, escala e translação em uma unica matriz de tranformação e aplica-la em um conjunto de pontos. Uma matriz de transformação cujos valores não alteram nada é chamada de matriz identidade, e tem como valores iniciais estes:
Bom, vamos ver em um exemplo prático como isso funciona. Para isso vamos criar uma classe básica de matriz de transformação onde vamos implementar métodos de rotação, translação e escala, e depois usa-la para movimentar um cubo 3d.
Apesar de ser um post sobre matrizes 3d, devemos ter em mente que um elemento mais básico é o vetor. Então devemos criar uma classe básica contendo apenas as coordenadas x. y e z a fim de executar a transformação de forma correta.
Lembre-se que essa abordagem é bem básica e tem como propósito desmistificar um pouco o conceito de matriz 3D, lembre-se que existem uma série de operações envolvidas numa engine 3d seja com matrizes ou com vetores além de outros elementos. Outro ponto importante a ser lembrado é que o Flash Player 10 tem suporte para matrizes e vetores 3d, o que implica num desempenho melhor uma vez que os cálculos são feitos internamente.
Vamos ao que interessa.
Primeiro vamos criar nossa classe de vetor chamada Vector3D contendo apenas 3 propriedades:
package basic3d.math { public class Vector3D { public var x:Number; public var y:Number; public var z:Number; public function Vector3D(x:Number = 0, y:Number = 0, z:Number = 0):void{ this.x = x; this.y = y; this.z = z; } } }
Agora vamos criar uma document class para nosso exemplo e nela criar 8 instâncias da Vector3D representando os pontos do cubo, um conteiner onde o cubo será desenhado e um método que será chamado constantemente para desenhar esses pontos
package { import flash.display.Sprite; import flash.events.Event; import basic3d.math.Vector3D; public class Main extends Sprite { private var p0:Vector3D; private var p1:Vector3D; private var p2:Vector3D; private var p3:Vector3D; private var p4:Vector3D; private var p5:Vector3D; private var p6:Vector3D; private var p7:Vector3D; private var container:Sprite; public function Main():void{ p0 = new Vector3D(-50, -50, -50); p1 = new Vector3D(50, -50, -50); p2 = new Vector3D(50, 50, -50); p3 = new Vector3D(50, 50, -50); p4 = new Vector3D(-50, -50, 50); p5 = new Vector3D(50, -50, 50); p6 = new Vector3D(50, 50, 50); p7 = new Vector3D(50, 50, 50); container = new Sprite(); container.x = 275; container.y = 200; addChild(container); addEventListener(Event.ENTER_FRAME, render); } private function render(e:Event):void{ trace("draw cube"); } } }
Até agora nada de mais. Vamos então começar a desenhar o cubo com base nos pontos.
Olhando para figura acima, podemos deduzir quais pontos estão conectados entre si. Então utilizando suas coordenadas x e y, podemos desenhar o nosso cubo:
private function render(e:Event):void{ container.graphics.clear(); container.graphics.lineStyle(0, 0xFF0000); container.graphics.moveTo(p0.x, p0.y); container.graphics.lineTo(p1.x, p1.y); container.graphics.lineTo(p2.x, p2.y); container.graphics.lineTo(p3.x, p3.y); container.graphics.lineTo(p0.x, p0.y); container.graphics.moveTo(p4.x, p4.y); container.graphics.lineTo(p5.x, p5.y); container.graphics.lineTo(p6.x, p6.y); container.graphics.lineTo(p7.x, p7.y); container.graphics.lineTo(p4.x, p4.y); container.graphics.moveTo(p0.x, p0.y); container.graphics.lineTo(p4.x, p4.y); container.graphics.moveTo(p1.x, p1.y); container.graphics.lineTo(p5.x, p5.y); container.graphics.moveTo(p2.x, p2.y); container.graphics.lineTo(p6.x, p6.y); container.graphics.moveTo(p3.x, p3.y); container.graphics.lineTo(p7.x, p7.y); }
Parece mais um quadrado do que um cubo, certo? Isso acontece porque os pontos p0, p1, p2 e p3 tem a mesma posição x e y dos pontos p4, p5, p6 e p7 respectivamente. Dessa forma eles se sobrepõe no espaço bidimensional (x e y). Num espaço tridimencional temos o eixo z para diferenciar as coisas, além disso precisamos considerar a perspectiva dos objectos, um ponto de fuga onde todos os pontos convergem.
Para isso devemos mudar criar um método que faça essa conversão do 3D para o 3D, vamos chama-lo de getPerspective. Esse método vai receber um vetor e retornar um ponto:
private function getPerspective(vector:Vector3D):Point { var focalLength:Number = 1000; var scaleFactor:Number = focalLength/(focalLength + vector.z); var point:Point = new Point() point.x = vector.x*scaleFactor; point.y = vector.y*scaleFactor; return point; }
Agora ja conseguimos ver algo mais próximo de um cubo. Mas antes de entender o que está acontecendo, vamos olhar mais de perto o método criado. Repare nessas duas linhas:
var focalLength:Number = 1000; var scaleFactor:Number = focalLength/(focalLength + vector.z);
o parâmetro focalLength representa o foco da nossa “camera”. Para entender isso vamos pensar o seguinte. Quanto mais afastado o objeto está, menor ele fica e vice versa. A distância focal representa a distancia entre a câmera e o objeto tal que sua escala seja 1, ou seja o objeto tem seu tamanho real. Quanto menor a distancia focal, maior é a variação de escala no eixo z. Para testar isso vamos mudar nosso foco para 100:
Comparando as duas imagens, vemos que o quadrado exterior ficou muito maior, enquanto o quadrado interior ficou muito menor, isso por que os pontos que formam esses quadrados tiveram uma variação de escala muito maior no eixo z. Mas por hora vamos manter o foco em 1000.
Agora precisamos criar nossa matriz de transformação para poder transformar esses pontos da maneira como quiser. Primeiro vamos definir as propriedades dessa matriz:
package basic3d.math { public class Matrix3D { /** * X O O O * O O O O * O O O O */ public var a :Number; /** * O X O O * O O O O * O O O O */ public var b :Number; /** * O O X O * O O O O * O O O O */ public var c :Number; /** * O O O O * X O O O * O O O O */ public var d :Number; /** * O O O O * O X O O * O O O O */ public var e :Number; /** * O O O O * O O X O * O O O O */ public var f :Number; /** * O O O O * O O O O * X O O O */ public var g :Number; /** * O O O O * O O O O * O X O O */ public var h :Number; /** * O O O O * O O O O * O O X O */ public var i :Number; /** * O O O X * O O O O * O O O O */ public var tx :Number; /** * O O O O * O O O X * O O O O */ public var ty :Number; /** * O O O O * O O O O * O O O X */ public var tz :Number; public function Matrix3D(a:Number = 1, b:Number = 0, c:Number = 0, d:Number = 0, e:Number = 1, f:Number = 0, g:Number = 0, h:Number = 0, i:Number = 1, tx:Number = 0, ty:Number = 0, tz:Number = 0):void{ this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; this.f = f; this.g = g; this.h = h; this.i = i; this.tx = tx; this.ty = ty; this.tz = tz; } } }
Repare que, por padrão, a matriz assume os valores da matriz identidade.
Agora vamos criar os métodos de transformação dessa matriz. Primeiro o método de concatenação (multiplicação) de matrizes, a partir desse método que vamos efetuar qualquer transformação em nossa matriz.
public function concat(m:Matrix3D):void{ var values:Object = {}; values.a = a*m.a+d*m.b+g*m.c; values.b = b*m.a+e*m.b+h*m.c; values.c = c*m.a+f*m.b+i*m.c; values.tx = a*m.tx+d*m.ty+g*m.tz+tx; values.d = a*m.d+d*m.e+g*m.f; values.e = b*m.d+e*m.e+h*m.f; values.f = c*m.d+f*m.e+i*m.f; values.ty = b*m.tx+e*m.ty+h*m.tz+ty; values.g = a*m.g+d*m.h+g*m.i; values.h = b*m.g+e*m.h+h*m.i; values.i = c*m.g+f*m.h+i*m.i; values.tz = c*m.tx+f*m.ty+i*m.tz+tz; for(var v:String in values){ this[v] = values[v]; } }
Um pouco confuso, não? Se você não entendeu muito bem como funciona a multiplicação de matrizes você pode conferir esse link
Agora vamos começar a rotacionar nosso cubo. Vamos testar apenas a rotação no eixo y e depois replicar o conceito para os outros eixos.
Primeiro vamos lembrar da fórmula usada para rotação em torno do eixy y:
x’ = x*cos(b) + z*sin(b)
y’ = y
z’ = -x*sin(b) + z*cos(b)
A partir dessa fórmula podemos criar uma matriz assim:
Lembrando que a transformação nada mais é do que a concatenação entre a matriz atual com a matriz de transformação, nesse caso devemos multiplica a matriz atual pela matriz de rotação no eixo y.
public function rotateY(angle:Number):void { var matrix = new Matrix3D(); matrix.a = Math.cos(angle); matrix.b = 0; matrix.c = Math.sin(angle); matrix.d = 0; matrix.e = 1; matrix.f = 0; matrix.g = -Math.sin(angle); matrix.h = 0; matrix.i = Math.cos(angle); concat(matrix); }
Para testar primeiro precisamos criar uma variável contendo o valor de rotação em radianos:
private var rotationY:Number = Math.PI*.25;
Nesse caso a rotação está em 45º
Depois precisamos criar uma matriz base e aplicar essa rotação
var matrix:Matrix3D = new Matrix3D(); matrix.rotateY(rotationY);
Depois devemos aplicar essa transformação para cada vetor. Para isso vamos criar um método chamado transform, que recebe e retorna um vetor. Esse método executa a multiplicação entre um vetor e uma matriz e adiciona os valores de translação respectivamente.
public function transform(v:Vector3D):Vector3D { var vector:Vector3D = new Vector3D(); vector.x = a*v.x+d*v.y+g*v.z+tx; vector.y = b*v.x+e*v.y+h*v.z+ty; vector.z = c*v.x+f*v.y+i*v.z+tz; return vector; }
Aplicando a transformação nos vetores, temos a seguinte modificação no metodo render:
var point0:Point = getPerspective(matrix.transform(p0)); var point1:Point = getPerspective(matrix.transform(p1)); var point2:Point = getPerspective(matrix.transform(p2)); var point3:Point = getPerspective(matrix.transform(p3)); var point4:Point = getPerspective(matrix.transform(p4)); var point5:Point = getPerspective(matrix.transform(p5)); var point6:Point = getPerspective(matrix.transform(p6)); var point7:Point = getPerspective(matrix.transform(p7));
Alé disso vamos aumentar 1º no angulo de rotação a cada frame:
rotationY += Math.PI/180;
Com isso, ja conseguimos visualizar um cubo rotacionando em torno do eixo y
Agora vamos replicar a rotação em y para os eixos x e z
public function rotateX(angle:Number):void { var matrix = new Matrix3D(); matrix.a = 1; matrix.b = 0; matrix.c = 0; matrix.d = 0; matrix.e = Math.cos(angle); matrix.f = -Math.sin(angle); matrix.g = 0; matrix.h = Math.sin(angle); matrix.i = Math.cos(angle); concat(matrix); } public function rotateZ(angle:Number):void { var matrix = new Matrix3D(); matrix.a = Math.cos(angle); matrix.b = -Math.sin(angle); matrix.c = 0; matrix.d = Math.sin(angle); matrix.e = Math.cos(angle); matrix.f = 0; matrix.g = 0; matrix.h = 0; matrix.i = 1; concat(matrix); }
Vamos criar os paramteros de rotação para esses eixos:
private var rotationX:Number = 0; private var rotationZ:Number = 0;
Acrescentar 1º a cada frame:
rotationX += Math.PI/180; rotationZ += Math.PI/180;
E chamar os métodos de rotação:
matrix.rotateX(rotationX); matrix.rotateZ(rotationZ);
O resultado é esse:
Agora sim conseguimos ver um cubo 3D rotacionando. Mas nosso trabalho não termina por aqui, ainda precisamos mudar a escala e a translação do cubo.
Vamos criar a matriz de transformação da escala:
<span lang="pt">p</span>ublic function scale(sx:Number, sy:Number, sz:Number):void { var matrix = new Matrix3D(); matrix.a = sx; matrix.b = 0; matrix.c = 0; matrix.d = 0; matrix.e = sy; matrix.f = 0; matrix.g = 0; matrix.h = 0; matrix.i = sz; concat(matrix); }
Diferente da rotação onde para cada eixo é preciso uma matriz diferente, a matriz de escala comporta os valores de transformação nos 3 eixos.
Vamos testar primeiro a escala uniforme do nosso cubo. Por hora vamos bloquear a rotação nos eixos x e z e implementar o escalonamento. Nesse exemplo vou utilizar uma senoide para aumentar e diminuir o cubo conforme o tempo:
private var sinCurve:Number = 0; sinCurve += Math.PI/180; var scale:Number = 1+Math.sin(sinCurve)*.5; matrix.scale(scale, scale, scale);
Podemos ver que o cubo aumenta e diminui em 50% do seu tamanho.
Agora vamos criar o método responsável pela translação da matriz.
public function translate(x:Number, y:Number, z:Number):void { tx = x; ty = y; tz = z; }
Como a translação é considerada como uma coluna extra na matriz, a unica coisa que devemos fazer é modificar seus valores. Para testar vamos utilizar a mesma senoide, mas dessa vez aplica-la na translação no eixo x:
sinCurve += Math.PI/180; var xPos:Number = Math.sin(sinCurve)*150; matrix.translate(xPos, 0, 0);
Vemos que a posição x do cubo varia entre 150 e -150 pixels conforme o tempo.
Espero ter ajudado a entender um pouco sobre matrizes de transformação. Lembre-se que existem muito mais operações matematicas envolvendo matrizes e vetores numa engine 3d, além de outras coisas como UV map, z-Sorting e muitas outras coisas.
Um livro que eu recomendo para quem gostaria de se aprofundar nisso é Essential Mathematics for Games and Interactive Applications do James M. Van Verth e Lars M. Bishop. Ele tem uma abordagem muito boa dos conceitos matemáticos aplicados na computação gráfica. Apesar de ser direcionado para programadores C/C++, a matemática é explicada sem seguir nenhuma linguagem.
Qualquer dúvida, sugestão ou crítica, por favor entrem em contato. Ficarei feliz em ajudar no que eu puder.
Obrigado
Referências:
http://wally.cs.iupui.edu/
http://www.prof2000.pt/
http://pt.wikibooks.org/
http://educacao.uol.com.br/matematica/
http://rrgoncalez.wordpress.com/
http://pt.wikipedia.org/wiki/Produto_de_matrizes
http://pt.wikibooks.org/wiki/Matem%C3%A1tica_elementar/Matrizes
















Muito bom post.
Obrigado
Thank you for this great tutorial.
its realy what i need.
i have a question, if i want skew or twist the cube. How a method and algorithm should i use?
Thank you again.
I never tried, but i think it’s possible. You can define a formula to each value of the matrix and create a behaviour like skew, twist, bend and any other modifier.
But this could be another class that extends the Matrix3D.
valew!!!
Existem outras coisas que podem ser feitas, se tiver alguma duvida ficarei feliz em responder.
Totally terrific post, I enjoyed looking through it. It had quite a few perfect insight. I’m bookmarking this site.
Wonderful post. Thanks!
nice work keep it up
Pingback: unbox » Blog Archive » 3D Outlines