sexta-feira, 8 de setembro de 2017

Algoritmo de rasterização: Pontos, linhas e triângulos.

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:



          De fato, a maior dificuldade encontrada foi generalizar o trecho do algoritmo de Bresenham, descrito inicialmente, para os demais octantes. Além dos estudos para compreender a característica de cada octante, a ação de implementa-los foi extremamente trabalhosa, seguidas por uma série de testes com o objetivo de cada if (ou else if) implementado conter o conteúdo específico para aquele octante. 

             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.


Referências:

Nenhum comentário:

Postar um comentário

Algoritmo de rasterização: Pontos, linhas e triângulos.

Introdução teórica :                      Essa introdução tem o propósito de esclarecer alguns conceitos que serão utilizados ao longo d...