Introdução teórica:
Essa introdução tem o propósito de esclarecer alguns conceitos que serão utilizados ao longo do desenvolvimento do trabalho. Tais conceitos serão essenciais para uma maior familiarização com algoritmos de rasterização descritos nessa publicação.
Rasterização:
Basicamente o conceito de rasterização utilizado nesse trabalho é compor uma imagem "raster" (formada por pixels ou pontos) para uma saída de video, nos aproximando da primitiva geométrica. Como o sistema operacional impede o acesso direto ao hardware de vídeo, foi utilizado uma framework específica para rasterizar a imagem na tela do computador.
Pontos, linhas retas e triângulos:
Na matemática, um ponto é uma noção primitiva pela qual outros conceitos são definidos. Um ponto determina uma posição no espaço, ou seja, a partir de coordenadas X e Y (exemplos para o espaço R²) conseguimos determinar onde o ponto está localizado.
Geometricamente, uma linha reta (que não faz curvas), nada mais é do que uma reta em si. Uma reta é em sua essência, um conjunto de pontos. Como o plano que vamos trabalhar (512 x 512) X e Y são dados por coordenadas inteiras, nossos pontos também possuirão coordenadas inteiras, limitadas por um ponto de inicio e fim, dando origem ao conceito de segmento de reta.
Triângulos são polígonos formados por três lados. Os polígonos, por sua vez, são figuras geométrica formadas por segmentos de reta que , dois a dois, tocam-se em seus pontos extremos.
Pontos = {E,F,G} / Segmentos de reta = {h,y,x} / Triângulo (EFG).
Rasterizando um ponto:
Para darmos inicio ao algoritmo de rasterização de um ponto, precisamos abstrair a ideia de que um ponto será representado na tela através de um pixel. Mas afinal, o que é um pixel?
Um pixel é o menor elemento em um dispositivo de exibição (um monitor, por exemplo), no qual se pode atribuir uma determinada cor. O pixel ocupa um espaço de 4 bytes, cada byte contendo um R,um G, um B e um A, onde o R,G,B representam respectivamente as cores vermelho, verde e azul que são as cores primárias em relação a luz, e o A, que representa a transparência de determinada cor. Com esses 4 bytes é possível representar uma grande parte das cores visíveis ao ser humano na tela, porém não todas.
Um pixel também recebe coordenada X e Y, da mesma maneira que um ponto é representado no espaço, porém sua representação na tela é feita através do calculo:
Temos que x = coordenada no eixo x, y = coordenada no eixo y, w = número de colunas.
Ciente do que é um pixel, é possível implementar um algoritmo que rasteriza um ponto na tela, através da função putPixel().
Para realizar tal implementação declara-se uma struct na linguagem C/C++ de nome "pixel" no arquivo "mygl.h" (especificado para a framework trabalhada). Essa struct recebe as coordenadas x,y e os tons de cores de R,G,B e A.
A partir das coordenadas X e Y podemos calcular o "Offset" do pixel e sua respectiva cor:
Chamando a função putPixel() na "main.cpp", obtemos os pixels definidos nas coordenadas X e Y:
pixel vermelho = (256,256,255,0,0,0) ; pixel azul = (226,226,0,0,255,0) ; pixel verde (286,286,0,255,0,0);
Rasterizando uma linha (Algoritmo de Bresenham):
O intuito deste tópico é fazer um algoritmo de rasterização de linhas utilizando o Algoritmo de Bresenham.
Algoritmo para um octante específico.
O algoritmo de Bresenham consiste basicamente em ter um ponto de partida e encontrar o próximo pixel que deve ser pintado na tela a partir da descoberta de um ponto médio.
Após analisar as duas imagens podemos concluir que achando "d", é possível decidir qual o próximo pixel a ser pintado. Para o código de Bresenham mostrado anteriormente, naquele octante, se d<= 0 o próximo pixel = leste, caso contrário, o próximo pixel = nordeste. Desse modo o X ou Y é incrementado, dependendo do octante trabalhado.
- Generalizando para os demais octantes:
O código mostrado anteriormente consiste parte do algoritmo de Bresenham, este por sua vez está generalizado para todos os octantes. O intuito deste tópico é generalizar esse trecho do algoritmo de Bresenham, de modo que ele funcione em todos os octantes. Entretanto, para isso é preciso conhecer mais sobre cada octante.
Sendo assim temos:
m = coeficiente angular.
abs = Função que calcula o módulo.
dx = xF-xI.
dy = yF-yI.
1º octante e 5º octante : 0<= m <= 1.
2º octante e 6º octante: m> 1.
3º octante e 7º octante: m<-1.
4ºoctante e 8º octante: 0>= m >= 1.
6º octante, 7º octante, 3º octante, 2º octante: abs(dy) > abs(dx).
5º octante, 4º octante, 8º octante, 1º octante: abs(dx) > abs(dy).
1º octante e 2º octante : dx e dy >0.
3º octante e 4º octante : dx<0 e dy >0.
5º octante e 6º octante : dx e dy <0.
7º octante e 8º octante : dx>0 e dy <0.
Após as especificações de cada octante é possível implementar um algoritmo de rasterização de linha (drawLine()), generalizando o 1º octante para os demais. Primeiramente é preciso descobrir em qual octante está a linha que será pintada na tela:
Exemplo para o segundo octante:
A implementação é feita por uma sequencia de if's que verifica o dx, dy e o módulo entre eles.
Após verificar em que octante a linha será pintada, é preciso verificar o incremento e o decremento do X e do Y, no algoritmo de Bresenham. Para isso foram feitos dois if's:
- Para abs(dx) > abs(dy):
- Para abs(dy) > abs(dx):
- Interpolando as cores das linhas:
A interpolação das cores é feita pelo seguinte algoritmo: (cor final - cor inicial)/tamanho da linha. As cores são incrementadas sempre que passam no código de Bresenham, dentro do while e antes da chamada de um novo pixel a ser pintado:
(cor final - cor inicial)/tamanho da linha
Incrementação das cores
- Chamando a drawLine() na "main.cpp" associado a interpolação das cores:
- Sem interpolação:
Desenhando um triângulo:
Como vimos introdutoriamente, uma triângulo é formado por três segmentos de retas, que compõem suas arestas. Deste modo iremos utilizar a função drawLine() para pintar as possíveis arestas desse triângulo. Sendo assim, implementamos a função drawTriangle():
A função drawTriangle() recebe como parâmetro três pixels que serão os vértices do triângulo. Sabendo os vértices, podemos chamar a função drawLine() e pintar as linhas interconectando esses vértices:
- Chamando a drawTriangle() na "main.cpp" associado a interpolação das cores:
Dificuldades encontradas e possíveis melhoras:
A implementação de oito ifs para saber onde a linha seria rasterizada na função drawLine() é um ponto a ser melhorado. O fato dos octantes terem características em comum, permitiria um número menor de ifs na função.
Nenhum comentário:
Postar um comentário