segunda-feira, 22 de agosto de 2016

Abrindo os portões do inferno do GLUT

Hoje eu resolvi dar uma olhada nos tutoriais de http://nehe.gamedev.net/ para começar a brincar com OpenGL. Baixei o Visual Studio(Community Edition, creio eu) mas os tutoriais de lá usam uma tal tecnologia paleolítica chamada GLAWX, que não é suportada mais hoje. 

Depois de quase trocar de curso, encontrei uma página que explica que existe um tal de nuopengl, que aparentemente inclui todos os gluts, freegluts, glawx, a porra toda, é bem fácil de instalar. Só tome cuidado com o código exemplo do site, a função glutinit deveria ser iniciada glutInit(&argc, argv); 

Amanhã eu tento fuçar mais um pouco no OpenGL. Fiquem ligados para o próximo episódio de "Erro de Compilação"!
Álbum da vez:

sexta-feira, 12 de agosto de 2016

Interpolação de cores e Triângulos

Finalmente, vamos falar sobre a interpolação linear de cores em linhas e a função de rasterizar triângulos, que na verdade nada mais é que uma função que rasteriza três linhas em ordem.

A interpolação de cores em uma linha significa começar a rasterização da linha utilizando uma cor e, pouco a pouco, modificar essa cor para que no fim da rasterização da linha tenhamos outra cor. É como um degradê, basicamente.

Para isso, precisamos primeiramente passar para a função de rasterizar linhas nossa cor inicial e final. Utilizaremos as cores que definimos anteriormente por praticidade, mas podemos, sem problema nenhum, utilizar qualquer cor do sistema RGBA.

Já que o algoritmo de rasterização funciona em etapas de pixel por pixel, só podemos modificar nossa cor a cada pixel, ou ciclo, do algoritmo. Então, temos que definir qual será nosso incremento de cor para cada canal de cor da linha por ciclo de rasterização. O cálculo do incremento do canal vermelho, por exemplo, é dado por:

dr = (color1.r = color0.r)/dx ou dy

onde dr é o incremento do canal vermelho, color1.r é o valor do canal vermelho no fim da linha, color0.r é o valor do canal vermelho no início da linha, e dx ou dy refere-se ao comprimento da linha projetada no eixo com o qual a função de rasterização trabalha. dx ou dy também é o número de vezes que o algoritmo de rasterização roda. Agora entendemos como e porque utilizamos este valor de dr. Para os outros canais de cores, a definição do incremento é análogo.

No final de cada ciclo do algoritmo, a cor que está sendo utilizada para o pixel atual recebe os respectivos incrementos, aproximando pouco a pouco da cor final. Abaixo temos o código da função de rasterização no primeiro quadrante com as modificações para a interpolação de cores:

void DrawLine1O(int x0, int y0, int x1, int y1, cor color0, cor color1){

    int dx = x1 - x0;
    int dy = y1 - y0;

    cor color = color0;

    float dr = (color1.r - color0.r)/dx;
    float dg = (color1.g - color0.g)/dx;
    float db = (color1.b - color0.b)/dx;

    int d = 2*dy - dx; //valor inicial de d

    int incE = 2*dy;            //valores iniciais para incrementos E e NE
    int incNE = 2*(dy - dx);

    int x = x0; //valores iniciais para rasterizacao
    int y = y0;

    PutPixel(x, y, color); // bota primeiro pixel

    while(x < x1){
        if(d<=0){
            d += incE;
            x++;
        }else{
            d +=incNE;
            x++;
            y++;
        }

        color.r = color.r+dr; // incrementa cor por pixel  ate a cor final no ultimo pixel
        color.g = color.g+dg;
        color.b = color.b+db;

        PutPixel(x, y, color);
    }
}


Inicialmente, quando utilizava os canais de cores como tipo inteiro, a interpolação não funcionava corretamente. Se eu tentasse rasterizar uma linha que começava azul e terminava vermelha, por exemplo, a cor azul ia aos poucos esmanhecendo enquanto o canal vermelho não era incrementado corretamente. Trocando os tipos dos canais para float, o problema foi corrigido. Outros bugs psicodélicos ocorreram, mas somente quando tentava utilizar outro cálculo de incremento.

Linhas com interpolação de cores!
Por último, vamos entender como funciona a rasterização de triângulos. A função recebe três coordenadas(os vértices do triângulo) e três cores, uma para cada vértice. Aí são chamadas as funções de rasterização de linha: do ponto 1 ao ponto 2, começando com a cor 1 e terminando com a cor 2;  do ponto 2 ao ponto 3, começando com a cor 2 e terminando com a cor 3; e do ponto 3 ao ponto 1, começando com a cor 3 e terminando com a cor 1. Dessa forma, as cores sempre se unem nas vértices. É nesse momento que vemos outra vantagem de utilizar funções de rasterização "de trás para frente", que é essa coincidência de cores nos vértices.

Três (3) triângulos rasterizados!

Concluindo, o projeto foi um sucesso. Os conceitos foram entendidos completamente e implementations em código. Mal posso esperar para aprender mais sobre OpenGL. PS: Este programa contém GLUT.

REFERENCIAS:
Slides do professor Christian
http://stackoverflow.com/questions/25878029/smooth-color-interpolation-along-a-bresenham-line
http://stackoverflow.com/questions/13488957/interpolate-from-one-color-to-another

Álbum da vez: 

terça-feira, 9 de agosto de 2016

Rasterizando Linhas

Agora vamos desenhar, ou, mais precisamente, rasterizar, linhas. Vamos utilizar como parâmetros de entrada para a nossa função os pontos da coordenada inicial (x0, y0), da coordenada final (x1, y1), a cor do ponto inicial e do final, para depois fazermos a interpolação de cores.

Para esclarecer logo, o sistema de coordenadas do framework tem origem no canto superior esquerdo da tela, por isso temos que fazer uma leve ginástica mental para entendermos logo como o algoritmo funciona!

Vamos dividir a rasterização em oito funções, cada uma especializada em trabalhar com linhas em um octante diferente. Octantes são divisões angulares, então temos cada octante abrangendo 45°, totalizando 360°, ou uma rotação copleta, como um relógio. Por exemplo, uma linha reta que é desenhada a partir do canto superior esquerdo da tela,  para baixo e para a direita fazendo um ângulo de 30° com o topo da tela estaria no primeiro octante.

Para desenharmos uma linha, que é, na matemática, infinitamente fina, em um uma tela de pixels, onde linhas precisam ter pelo menos um pixel de espessura, precisamos utilizar um método de rasterização, ou seja, um algoritmo que decide quais pixels devem ser utilizados para representar uma linha infinitamente fina qualquer.

O método utilizado é o algoritmo de incremental de linha de Bresenham. É um algoritmo bem simples e muito rápido por fazer apenas multiplicações e incrementos.Utilizando o algoritmo padrão, implementamos uma função que pode rasterizar linhas no primeiro quadrante. Podemos facilmente modificar esta função para aplicá-la aos outros octantes. Vejamos como alguns quadrantes diferem logicamente do primeiro:

Para uma linha no segundo octante, |dy| > |dx|, então vamos precisa ter mais do que um pixel aceso por coluna da tela, ou a linha ficaria descontínua. Por isso, precisamos rasterizar a linha em função do eixo y ao invés do eixo x, como no primeiro octante. No código, precisamos trocar os dx por dy e vice-versa e colocar o while dependendo de y e y1. Estamos, de fato, espelhando a função para o eixo y!


void DrawLine2O(int x0, int y0, int x1, int y1, cor color0, cor color1){

    int dx = x1 - x0;
    int dy = y1 - y0;

    int d = 2*dx - dy; //valor inicial de d

    cor color = color0;
    
    int incS = 2*dx;            //valores iniciais para incrementos S e SE
    int incSE = 2*(dx - dy);

    int x = x0; //valores iniciais para rasterizacao
    int y = y0;

    PutPixel(x, y, color); // bota primeiro pixel

    while(y < y1){
        if(d<=0){
            d += incS;
            y++;
        }else{
            d +=incSE;
            y++;
            x++;
        }
        
        PutPixel(x, y, color);
    }
}

Outro caso: o quadrante 5. Nele, voltariamos a rasterizar em função de x, já que |dx|>=|dy|, ou seja, a linha está fazendo um ângulo menor ou igual a 45° com o eixo x. No entanto, dx é negativo, então a rasterização ocorre de trás para frente - da direita para a esquerda! Para isso, precisamos fazer o loop rodar enquanto x>x1(trocamos x e x1), e decrementar x e y ao invés de incrementá-los, já que o linha está também sendo rasterizada de baixo para cima. Finalmente, negativamos o dx e dy pois dentro da função eles servem apenas como comprimentos.

As funções para octantes onde os valores finais do eixo da função sao menores que os iniciais utilizam esta mesma logica de escrever "de tras pra frente", sendo diferenciadas apenas pelas mudanças lógicas para cada octante. 

A função principal que chamamos para rasterizar nossas linhas apenas decide, a partr das coordenadas, qual das outras oito funções deverá ser chamada, passando os seus parâmetros para ela. E assim, uma linha é criada!

Teste de linhas em todos os octantes.
O número de funções poderia ser reduzido se implementássemos elas sempre colocando os pixel da esquerda para a direita, ou até mesmo utilizar apenas um única função que se adapta dependendo dos valores de entrada. Desse modo, nos livraríamos até da função de decisão. Decidi seguir pelo caminho de uma funções por octante pois sua lógica é bem explícita e faz uso de pequenas modificações para acomodar cada quadrante. De toda forma, é necessáro criar algum tipo de estrutura de decisão para acomodar todas as possíveis possibilidades, como decidir em função de qual eixo devemos colocar os pixels, ou se devemos colocá-los de trá pra frente etc. Talvez uma única função universal seja mais eficiente, mas acho que as oito funções são mais didáticas e de fácil manutenção. De toda forma, as linhas são rasterizadas sem nenhum problema.

Referências:
Notas de aula do professor Christian.
Capítulo 3.2 do Foley.

Álbum da vez:

domingo, 7 de agosto de 2016

Cores + PutPixel

Para colocarmos um pixel de determinada cor na tela precisamos implementar uma função que receba a posição do pixel em coordenadas x,y e sua cor em RGBA. Vamos falar um pouco sobre esse sistema de cores.

O sistema RGBA contém quatro canais: R(red), G(green), B(blue), A(alpha), que são valores numéricos que, para o projeto, variam de 0 a 255. Cada um dos três primeiros canais define a intensidade luminosa das três cores primárias que temos em um pixel na tela do computador. Por exemplo, se tivermos o canal R com um valor de 255 e os outros dois canais de cores primárias com 0, o pixel ficará vermelho. Se todas os três canais receberem 255, teremos o branco, e se todos receberem 0, teremos o preto. Assim, a mistura de diferentes intensidades de cada cor primária gera as diferentes cores que podemos enxergar! O último canal, Alpha, determina a "transparência" da cor do pixel, mas não vamos utilizar ele neste projeto.

Então, precisamos de quatro variáveis para representar a cor de um pixel. Vamos definir um novo tipo de variável para facilitar testes no futuro, o tipo cor:

struct cor{
    float r;
    float g;
    float b;

    float a = 255;    
};

Definimos as cores como float pois vamos modificá-las quando estivermos fazendo a interpolação linear. Como não vamos utilizar o canal Alpha, podemos definir ele como sendo sempre 255, ou seja, a cor do pixel é completamente opaca.

Agora, por conveniência, vamos inicializar algumas variáveis do tipo cor para fazermos testes no futuro:

cor white = (white = cor(), white.r = 255, white.g = 255, white.b = 255, white);
cor red = (red = cor(), red.r = 255, red.g = 0, red.b = 0, red);
cor green = (green = cor(), green.r = 0, green.g = 255, green.b = 0, green);
cor blue = (blue = cor(), blue.r = 0, blue.g = 0, blue.b = 255, blue);

Um dos problemas que eu encontrei foi como definir e inicializar variáveis desse struct, já que o código é em C++. Assim é feio, mas funciona.

Pronto, temos cores, agora vamos ver como escrever esses valores na "memória de vídeo" fake do framework. Sabendo que cada canal pode varias entre 0 e 255, temos 256 estados possíveis para cada canal, ou seja, podemos utilizar 1 byte para cada um dos quatro canais de cor, totalizando 4 bytes por pixel.

Para calcular em que posição de memória devemos escrever os valores de cada, utilizamos a equação

4*(Posição x do pixel) + 4*(Posição y do pixel)*(Largura da tela) + 0, 1, 2 ou 3, dependendo de qual canal queremos escrever na memória.

Assim, podemos finalmente implementar a função PutPixel em toda sua glória:


void PutPixel(int x, int y,  cor color){

    FBptr[4*x + 4*y*IMAGE_WIDTH + 0] = color.r;   // componente R
    FBptr[4*x + 4*y*IMAGE_WIDTH + 1] = color.g;   // componente G
    FBptr[4*x + 4*y*IMAGE_WIDTH + 2] = color.b;   // componente B
    FBptr[4*x + 4*y*IMAGE_WIDTH + 3] = color.a;   // componente A
}

A função recebe a coordenada x,y do pixel e uma variável tipo cor, escrevendo na memórias os valores de cada canal de color. O comando FBptr faz parte da framework do professor. IMAGE_WIDTH é definido em definitions.h como o número de colunas na tela, ou largura da tela, com valor 512.

Referências: Material do professor Christian.

Álbum da vez:

segunda-feira, 1 de agosto de 2016

Setup do ambiente de trabalho para o projeto de CG

A primeira coisa que tive que resolver quando comecei a trabalhar no projeto de CG foi como compilar o código do framework do professor Christian. Pesquisei o erro de compilação(alguma coisa GL_undefined) no google e descobri que eu precisava do tal do GLUT para programar com OpenGL.

Primeiramente, precisamos fazer o download de uma versão do GLUT(freeglut), um pacote de ferramentas utilitárias para trabalhar com OpenGL, e instalá-lo de acordo com as instruções daqui. Para testarmos se o freeglut foi instalado corretamente, criamos um novo projeto "GLUT Project" no Code::Blocks e compilamos/rodamos o código que já vem com o projeto. Se aparacer uns objetos giratórios não-identificados acompanhados de seus esqueletos na tela do seu computador, você instalou o freeglut!

"O Sol, a Lua, e os anéis de Saturno"

Depois disso, aproveite o projeto criado e adicione a ele os arquivos .h e .cpp do framework do professor(botão direito no seu projeto, na parte esquerda). Pode deletar o main que vem com o projeto do GLUT, já que vamos usar o main do framework. 

Infelizmente outro erro de compilação ocorreu, dessa vez por causa de, se não me engano, funções ou variáveis indefinidas. Outra pesquisa no Google me levou ao GLEW. Instalei ele e tudo começou a funcionar sem problemas. 

Um colega só conseguiu fazer o código funcionar ao adicionar a linha "#define GL_CLAMP_TO_EDGE 0x812F" no header main.h. Cheguei a usar ela também mas testei sem ela e funcionou, então não sei se é algo essencial.

Álbum da vez: