sábado, 22 de abril de 2017

Atividade 2 - Aplicação do Pipeline Gráfico a um objeto 3D

Nesta atividade, nos foi orientado a desenvolver, usando as ferramentas disponibilizadas para o primeiro trabalho, o Pipeline Gráfico que foi apresentado em sala, que converte a câmera ortogonal em perspectiva, carregar um uma malha de triângulos disponibilizada pelo professor, no formato ".obj" e em consequência disso poder aplicar transformações no mesmo, tais como translação ou rotação.

1. "Loading" do objeto para o programa

Para carregar o objeto, foram usadas duas bibliotecas recomendadas pelo professor: O loader Assimp, para extrair as informações do arquivo, e o GLM(OpenGL Mathematics) para realizar os cálculos com matrizes e vetores.
O carregamento do objeto com o Assimp é feito de maneira bem simples, até por que eu já tinha experiência com o uso dele da outra disciplina da sexta (Physically Based Rendering), usando a classe aiLoader todas as informações do obj são disponibilizadas de maneira simples. Então só foi necessário criar uma lista encadeada que guardaria todos os triângulos do objeto para serem acessados mais tarde. Também foi criada a struct Triângulo, que contém três glm::vec4(Vetor de quatro elementos) para guardar as informações dos pontos e três glm::vec3(Vetor de três elementos) para guardar os valores de cor de cada ponto.


2. Criação de Matrizes Iniciais


2.1 Criação da Câmera e do seu espaço

 

Eu não criei uma classe câmera pois não achei necessário para este trabalho, ao invés disso eu criei os três vetores no próprio main, position, lookat e up.
E com estes três vetores foi possível criar o espaço da câmera como foi ensinado pelo professor e usando o GLM para auxiliar a fazer os cálculos:


2.2 Construção da Matriz Model

 

A matriz Model é a que dita as transformações que serão feitas no objeto para que ele vá para o espaço do universo, ela é individual para cada objeto, e pode ser constituída de várias outras matrizes multiplicadas, neste caso só foi usada uma matriz identidade, já que não seriam feitas transformações no objeto inicialmente.



2.3 Construção da Matriz View

 

A matriz view é criada com os parâmetros da câmera e os vetores do seu espaço, ela transporta os vértices para o espaço da câmera, que foi criado anteriormente.


Depois as matrizes Model e View são multiplicadas para criar a ModelView.

2.4 Construção da Matriz Projection

 

A matriz Projection é a que vai transformar a cena para que ela tenha perspectiva, fazendo com que todos os vértices tenham sua posição modificada de acordo com a distância dos mesmos da câmera.



Multiplicando a Projection com a ModelView nós criamos a ModelViewProj (mvp).


3. A função PrintObj()

Como a matriz ModelViewProj é diferente para cada objeto, a função PrintObj deve receber ela e, logicamente, a lista de triângulos que corresponde ao objeto:



3.1 Levando os vértices ao espaço da câmera

Para isso nós só precisamos multiplicar todos os vértices do objeto pela matriz ModelViewProj, ou mvp, e todas as operações citadas antes serão feitas ao mesmo tempo:


3.2 Trazendo os vértices de volta para o espaço homogêneo

Depois de realizadas as transformações com a mvp, os vértices deixaram de ter seus últimos elementos iguais, para consertar isso nós precisamos dovidir todos os elementos do vértice pelo valor do último, assim fazendo com que todos tenham o último elemento igual a 1:

3.3 Criação da Matriz Viewport

A Matriz Viewport é quem é responsável por converter os vértices do espaço da câmera para as coordenadas da tela, para isso nós precisamos criar três matrizes, uma de Translação e duas de escala. A translação vai colocar a origem do espaço no centro da tela e as escalas são para inverter o y, para a imagem nçaoi ficar de cabeça para baixo e a outra é para a imagem se ajustar à resolução da tela:

Para converter os vértices do objeto para as coordenadas da tela só é preciso multiplicá-los pela matriz Viewport:

Para imprimir o objeto na tela foi necessário chamar a função DrawTriangle() do trabalho passado para cada triângulo da lista, e o resultado está na imagem abaixo:
Comparação com o Loader do professor
Para que o resultado ficasse igual ao exemplo enviado pelo professor foi necessário mudar a posição da câmera para o ponto (0,0,4) e o d da câmera perspectiva para 2.

4. Aplicando um rotação ao objeto

Para que o objeto roationasse uniformemente no programa foi criada uma matriz rotação que foi multiplicada à matriz model, onde o ângulo de rotação era incrementado em 0.01 a cada iteração do loop principal:

e o resultado é mostrado neste vídeo:



5. Conclusões

Como pode-se ver os objetivos traçados para esta atividade foram alcançados com êxito, apesar de ter passado muito tempo tentando resolver um problema que eu pensava que era na importação do objeto, mas ocorria por que as operações de marizes no GLM são feitas da esquerda para a direita, como foi visto acima o código já está da maneira correta, se eu tentasse usar a multiplicação na ordem que o professor ensinou sempre dava segmentation fault na hora de executar, mas depois de resolvido este problema tudo ocorreu normalmente.


terça-feira, 21 de fevereiro de 2017

Atividade 1 - Rasterização de Ponto e Linha, Triângulo e Interpolação

Rasterização de Ponto

Primeiramente, para que pudessem ser feitas as demais exigências do Trabalho, uma função primordial tinha que ser implementada, que é a de desenhar um ponto na tela, a PutPixel ela é bastante simples e não há muita necessidade de se aprofundar nela, basicamente o programa irá modificar o Buffer de Imagem da tela, que é um array, nos quatro elementos correspondente às cores do pixel desejado, mudando assim a sua cor na tela.

Rasterização de Linha

Para rasterizar uma linha, primeiramente, só para testar a função PutPixel eu fiz um laço for que desenha uma linha de um canto ao outro da tela, aqui está o resultado:

Depois eu comecei a implementar o Algoritmo de Bresenham para Rasterização de Linha, inicialmente só no Primeiro Octante, pois esta foi a parte ensinada pelo professor:

Depois de confirmar que o algoritmo estava funcionando da maneira correta eu continuei a implementar a função DrawLine para os outros Octantes.

A maneira que eu pensei para converter o algoritmo inicial para funcionar para os outros octantes foi a seguinte:

No primeiro Octante, o valor de X varia sempre de 1 em 1, e em todos as iterações do laço ele é incrementado, ao contrário de Y que vai variar de acordo com o algoritmo.
O que eu fiz foi só intercalar as posições de X e Y no algoritmo e trocar seus sinais, caso eles cresçam negativamente.

Desta maneira ficou o meu planejamento:

1º Octante - X é incrementado em valores fixos | Y é incrementado em valores calculados pelo algoritmo.

2º Octante - X é incrementado em valores calculados | Y é incrementado em valores fixos.

3º Octante - X é decrementado em valores calculados | Y é incrementado em valores fixos.

4º Octante - X é decrementado em valores fixos | Y cresce positivamente em valores calculados.
...
E assim continua até o Oitavo Octante.

Também foi necessária a criação de algoritmos mais simples para retas horizontais e verticais e também de retas onde X e Y sempre são iguais.
Para demonstrar o funcionamento completo do algoritmo eu criei uma cena com retas em todos os octantes.:

Interpolação

A interpolação é quando se desenha uma linha entre dois pontos de cores diferentes, e no decorrer desta linha a cor da mesma transita da cor do ponto inicial para a cor do ponto final.
O algoritmo para implementar a interpolação é bem simples, só era necessário calcular a diferença de cor (Vermelho, Verde e Azul), depois calcular a Variação de X ou de Y da linha, seria escolhido o que varia de 1 em 1. Depois dividindo a variação das cores pela variação de x ou y, nós saberíamos quanto de cada cor incrementar ou decrementar em cada pixel. E pronto, o algoritmo funciona perfeitamente:
Linhas interpoladas com o ponto central de cor branca

Triângulo

Depois de feita a função DrawLine já interpolada (DrawLine2), para desenhar um triângulo só era necessário criar um função que chamasse DrawLine2 três vezes para ligar os três pontos do triângulo.
Note que não se fala "rasterizar o triângulo" pois para ser rasterizado o triângulo precisa estar preenchido.
Está abaixo o resultado:



E assim concluo este trabalho, com todos os objetivos alcançados e todos os conceitos aprendidos e executados.

Referências:
Slides do Professor Christian
Blog de Fleig ( Para instalar o FreeGlut no Windows)