quinta-feira, 5 de maio de 2016

Pipeline Gráfico

Este trabalho tem como objetivo a implementação do Pipeline Gráfico aprendido durante as aulas de Introdução à Computação ministradas pelo professor Christian Azambuja Pagot . A rasterização do resultado final do objeto foi feito utilizando a implementação do trabalho anterior .

Pipeline Gráfico:
O Pipeline Gráfico, em computação gráfica, é uma sequência de passos que deve ser seguida para se criar uma representação 2D de uma cena 3D. 

O Pipeline Gráfico tem a seguinte estrutura: 

Espaço do Objeto -> Espaço do Universo
O espaço do objeto é um espaço tridimensional onde, geralmente, cada objeto tem seu próprio sistema de coordenadas, objetos os quais posteriormente, irão se juntar e passarem a dividir o espaço do universo. Aqui, poderemos aplicar qualquer transformação sobre o objeto: rotação, translação, shear e/ou escala. Após aplicado tais transformações, o objeto passará para o espaço do universo.

Matriz Model
A construção da Matriz Model é feita a partir de transformações dos pontos do espaço do objeto para o espaço do universo, então, precisamos considerar que, o espaço do objeto é composto pelos pontos e sistema de coordenadas do objeto, pontos estes que contém valores em cada uma de suas coordenadas x, y e z. Desta forma, para transportar pontos do espaço do objeto para o espaço do universo, nós precisamos utilizar transformações geométricas sobre cada ponto desta matriz.


Espaço do Universo -> Espaço da Câmera
O próximo passo é levar o objeto do espaço do universo para o espaço da câmera, para isso devemos definir de onde serão visualizados os objetos, ou seja, quais as coordenadas da câmera e para onde ela está olhando. Na verdade a câmera estará sempre localizada na origem olhando para o sentido negativo do eixo z, o que muda são as coordenadas dos vértices dos objetos, que passam por uma mudança de sistema de coordenadas. Quando este processo ocorre, dizemos levar os vértices do espaço do universo paro o “espaço da câmera”.


Para “mudar” a posição da câmera, basta definir o ponto no espaço em que ela deverá estar, e nesse ponto definir uma base ortonormal. Situação mostrada na imagem a seguir:



Em seguida, realiza-se sobre as coordenadas dos vértices o processo chamado de “mudança de base”, que irá levar os vértices para o sistema de coordenadas definido pela base da câmera.


Matriz View
O processo de levar os vértices do espaço do universo para o espaço da câmera, é montando a Matriz View. Com os nossos objetos reunidos no espaço do universo e com o seus respectivos sistemas de coordenadas agora tridimensionais, nós precisamos definir o posicionamento da nossa câmera, para através do produto dos nossos objetos pela Matriz View possamos chegar ao espaço de câmera. Dessa forma, as três informações importantes que precisamos definir a respeito da nossa câmera são: Posição da câmera, Direção da câmera e o vetor Up.

Matriz ModelView

Espaço da Câmera -> Espaço de Recorte -> Espaço Canônico
Neste momento, os vértices deverão ser multiplicados por uma matriz especial chamada Projeção, fazendo com que os vértices do espaço da câmera sejam levados para o espaço de recorte. A multiplicação por essa matriz irá definir o tipo de projeção, podendo ser ortogonal ou perspectiva.

Em sala, pudemos estudar dois tipos de projeção, sendo elas a projeção perspectiva que permite a sensação de profundidade dos objetos na tela e a projeção ortogonal que preserva o paralelismo das retas, como mostra a imagem:




Matriz Projection




Matriz ModelViewProjection
Os vértices do espaço projetivo são transformados para o espaço canônico, para isto divide-se as coordenadas dos vértices no espaço projetivo pela sua coordenada homogênea. Isto gera uma mudança na geometria da cena, os objetos próximos da câmera ficam maiores, e os mais afastados ficam menores.


Espaço Canônico -> Espaço de Tela
No espaço canônico é garantido que todos os vértices da cena visível possui os valores de suas coordenadas entre -1 e 1. Este espaço é obtido quando, após multiplicar os vértices pelas matrizes model, view e projection, dividi-se as coordenadas dos vétices por sua coordenada w (coordenada homogênea). 



Após os espaço canônico é preciso preparar os vértices para serem rasteirados na tela. Este processo e feito multiplicando os vértice por uma matriz chamada viewport. Essa matriz leva os vértices do espaço canônico para o “espaço da tela e é formada pela multiplicação da matrizes mostradas na figura abaixo:




Resultados Obtidos:

IMG 1 exemplo cedido pelo professor - IMG2 meu resultado



Referências:
Slides de aula
Exemplo feito no Octave

quarta-feira, 2 de março de 2016

Rasterização de Primitivas


Este primeiro post é referente à primeira atividade prática da Disciplina de Introdução à Computação Gráfica. Atualmente, os sistemas operacionais não permitem o acesso direto na memória de vídeo, então será utilizado um framework, desenvolvido pelo professor Christian Azambuja Pagot, que tem como objetivo simular a memória de vídeo, o frame buffer e o color buffer, sendo utilizado em conjunto com o OpenGl.

Rasterização, o que é?
O processo de converter uma imagem vetorial em uma imagem raster (pontos ou píxel) para a saída em vídeo ou em impressora, dá-se o nome de Rasterização. O termo Rasterização também é utilizado para converter uma imagem em vector para um arquivo de formato bitmap (como converter SVG para PNG). É a aproximação de uma primitiva matemática ideal usando píxels da tela.

Rasterização de Pontos

Framebuffer é a região da memória responsável por armazenar as definições de imagem que será representada na tela ou monitor. O framebuffer é subdividido em partes, sendo uma delas o color buffer que é responsável pela representação das cores. As cores possuem diversas formas de representação, sendo a RGBA (Red, Green, Blue, Alpha) uma das mais comuns, entre elas. O RGBA é normalmente definido por um byte para cada cor RGB, sendo o Alpha responsável por calcular elementos de "transparência".

Para acessar o pixel localizado na posição (0,0) da memória, fiz o uso do ponteiro FBptr. Como foi dito anteriormente, em cada pixel encontramos quatro informações de cor (RGBA), sendo cada uma delas representada por um byte, totalizando quatro bytes por pixel. 

 O pixel é organizado no Color Buffer (um tipo de "array" que guarda as informações de cor do pixel) da seguinte forma: o primeiro pixel corresponde as posições 0-3, o segundo pixel 4-7, e assim sucessivamente. Para fazer a rasterização de pontos na tela, utilizei os seguintes códigos:


Como base, foi definida as seguintes estruturas:

Onde, a struct tCor armazena as informações de cor do pixel, e a struct tPonto as informações de posição+cor do pixel.


Função putPixel() : Desenha um pixel

A primeira função e mais básica à ser implementada foi a função putPixel, que é responsável por desenhar um pixel na tela, indo na memória de vídeo e escrevendo o RGBA de cor do pixel. A função putPixel é necessária, pois com o uso dela, faremos os pontos de referencia para construir as linhas e depois, a partir de três pontos, construir o triângulo.
Como mencionado anteriormente, os pixels ficam alinhados em sequência na memória. Para acessarmos o pixel no ponto 0,0 acessamos o primeiro elemento do array representante da memória. Lembrando que cada pixel constitui 4 bytes na memória, logo 4 elementos no array. A informação de posição do pixel foi calculado utilizando a seguinte fórmula: 

(4*x)+(4*y*IMAGE_WIDTH);



Chamando a função putPixel, temos os seguintes resultados:


Rasterização de Linhas : O algoritmo de Bresenham
O algoritmo de Bresenham foi desenvolvido com o intuito de melhorar a eficiência de procedimentos gráficos, como é o caso do desenho de uma reta. Ao invés de analisarmos cada ponto do eixo X e recorrer à equação para todos os pontos, usamos o estudo da simetria da reta. 

Qual pixel escolher?
O algoritmo consiste da avaliação de um determinado ponto médio, onde a cada coluna de píxels, temos duas opções de pixels à serem escolhidos (denominados em sala por E e NE) e a partir da avaliação do ponto médio entre os dois píxeis em relação à reta, teremos uma forma simples de determinar qual dos píxeis deve ser seleccionado.

No exemplo que vimos em sala, mnew é o ponto médio que servirá de referência para a escolha do próximo pixel a ser destacado, já que o algoritmo trabalha primeiramente com dois pontos, inicial e final, e a partir do primeiro vai determinando qual o próximo ponto que dever ser destacado, até chegar ao ponto final.

Exemplo: Como podemos perceber, o pixel azul posicionado abaixo do ponto médio mnew está posicionado mais próximo da intersecção da reta, do que o pixel acima do mnew, sendo assim, como pixel à ser destacado deve ser o mais próximo da reta, então o pixel abaixo do mnew deverá ser destacado.

Print do slide do professor

OBS: Neste caso, o algoritmo de Bresenham trabalha com 1º octante, onde o coeficiente angular varia entre 0-1 (máx 45º). Desta forma, para que a linha seja rasterizada da forma mais "perfeita" possível, não podemos destacar 2 pixels na mesma coluna. Caso o algoritmo seja utilizado baseado em uma variação de 45º-90º, não se pode destacar 2 pixels na mesma linha. A utilização da ideia do ponto médio é exatamente para escolher o pixel que melhor define a reta, como no exemplo:



O problema é que o algoritmo funciona apenas para retas do primeiro octante, isto é, quando Δx>0 , Δy>0 e |Δx|>|Δy|, porém, algumas modificações podem ser feitas para generalizar o algoritmo para os outros octantes. Foi observado em relação ao modelo matemático temos uma inversão na posição dos octantes. Isto acontece porque para os computadores o eixo y cresce para baixo.

Então, porque usar o algoritmo de Bresenham?
Alguns algoritmos de desenho de retas apresentam vários inconvenientes, tanto quanto a precisão, como no que se refere ao tipo de aritmética usada - que é muito importante para o desempenho da rasterização - desta forma, uma operação entre inteiros é mais rápida do que a correspondente operação entre operandos em vírgula flutuante. O algoritmo de Bresenham realiza a rasterização de segmentos de reta empregando apenas operações de aritmética de inteiros e, portanto, permite um maior desempenho do que outras variações de algoritmos para o desenho de retas.

Função drawLine()
Na rasterização de linhas, a função recebe como parâmetros as coordenada (x0,y0) e (x1,y1) e duas estruturas de cor, referente ao primeiro ponto e o segundo ponto.  Para fazer a variação da cor ao longo da reta, eu fiz uma media considerando o quanto cada cor varia. O algoritmo de Bresenham é falho em casos onde o y varia mais que o x, então, nesse caso, temos que inverter os eixos.  Para forçar uma variação maior de x ao invés do y , quando for pintar os pixels, é necessário fazer uma inversão novamente dos eixos para que a linha seja desenhada corretamente, fazendo o espelhamento de eixos com a função.

Segue o código com as aplicações anteriores:




Resultados obtidos:

Rasterização de Triângulos

Função drawTriangle(): Desenha triângulos

A função drawTriangle nada mais é, do que uma função que recebe como parâmetros as estruturas das três vértices do triangulo, com informação de coordenadas de cada ponto e cor, e chama a função drawLine() para rasterizar as linhas. 

Resultados obtidos:



Maiores dificuldades encontradas: 

A maior dificuldade foi o entendimento do funcionamento do algoritmo de Bresenham e o funcionamento do algoritmo para os demais octantes, que foi minimizado após pesquisas sobre o assunto. 
Após conseguir construir as linhas com ajuda do algoritmo, a segunda dificuldade mais aparente foi em relação à como deveria ser feita a interpolação das cores nas retas. 


Possíveis melhoras:
Conseguir construir um código menor.
Tentar melhorar o código para preencher triângulos.

Referências: 


- FOLEY, JAMES. D. - Computer Graphics: Principles and Practice in C. 2nd Edition: Addison-Wesley, 1990.

- Notas de aula. 









segunda-feira, 1 de fevereiro de 2016

Inicio

Blog criado para a disciplina de Computação Gráfica (2015.2), do curso de Bacharelado em Ciência da Computação da Universidade Federal da Paraíba.