thegoodfellas/blogset-br huggingface dataset:
as rotinas de multiplicacao entre outra matriz e entre um vetor. Em relacao ao trabalho anterior, resolvi modelar os pixels como objetos tambem. A funcao PutPixel() esta agora incorporada na classe Pixel, como a funcao put().class Pixel { public: unsigned short int x, y; unsigned char color[4]; Pixel(unsigned short int, unsigned short int, unsigned char, unsigned char, unsigned char, unsigned char); void put();}; Pixel::Pixel(unsigned short int inX, unsigned short int inY, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { x = inX; y = inY; color[0] = r; color[1] = g; color[2] = b; color[3] = a; }; void Pixel::put() { int offset = 4*(x + IMAGE_WIDTH*y); if( (x < IMAGE_WIDTH) && (y < IMAGE_HEIGHT) ) { FBptr[offset + RED] = color[RED]; FBptr[offset + GREEN] = color[GREEN]; FBptr[offset + BLUE] = color[BLUE]; FBptr[offset + ALPHA] = color[ALPHA]; } };Dito isto, a tarefa da funcao que converte as coordenadas do espaco canonico para o espaco de tela nada mais e do que receber o vetor daquele espaco, multiplicar pela matriz de transformacao, e retornar as coordenadas do pixel na tela. Por comodidade, o objeto Vector4 armazena as informacoes de cor daquele vertice, e que sao passadas adiante quando ha uma multiplicacao entre uma matriz e um vetor.Pixel canonicalToScreen(Vector4 coord) { float temp[4][4] = { { (IMAGE_WIDTH - 1)/2.0f, 0, 0, (IMAGE_WIDTH-1)/2.0f}, { 0, -(IMAGE_HEIGHT - 1)/2.0f, 0, (IMAGE_HEIGHT-1)/2.0f}, {0, 0, 1/2.0f, 0}, {0, 0, 0, 1} }; Matrix4x4 tMatrix(temp); // transformation matrix Vector4 buf = tMatrix * coord; // canonical -> screen space // composing pixel Pixel pix ((int)buf.m[0], (int)buf.m[1], 255, 255, 255, 255); return pix;}Onde temp e a matriz de conversao, calculada anteriormente. Dados, entao, tres vertices no espaco canonicoVector4 a (-0.5f, -0.5f, 0, 1);Vector4 b ( 0.5f, -0.5f, 0, 1);Vector4 c ( 0.0f, 0.5f, 0, 1);Para mostra-los na tela, converte-se para o espaco de tela:Pixel p0 = canonicalToScreen(a);Pixel p1 = canonicalToScreen(b);Pixel p2 = canonicalToScreen(c);E entao, adotando-os como vertices de um triangulo,DrawTriangle(p0, p1, p2);Obtem-se a figura a seguir (lado esquerdo). O lado direito apresenta o codigo de referencia do trabalho, onde sao utilizadas as funcoes do OpenGL.A esquerda, DrawTriangle(p0, p1, p2) utilizando o metodo de conversao desenvolvido. A direita, a imagem de referencia
simula uma memoria de video, ja que os sistemas operacionais nao permitem o acesso direto ao buffer da placa de video fisica."Pintando" os pixelsO primeiro algoritmo a ser desenvolvido, entao, deve desempenhar a funcao de "pintar" os pixels na tela, isto e, converter a localizacao de um determinado ponto (descrito por coordenadas cartesianas discretas) em um espaco de memoria, e escrever suas informacoes de cor neste espaco.O sistema de cores utilizado pelos monitores comuns e o RGB, sigla para Red-Green-Blue, os tres componentes de cor. Cada um dos componentes (canal) e representado por um numero inteiro de 8 bits, o que resulta em 256 niveis possiveis para cada componente. Sendo tres componentes, 256*256*256 = 16,78 milhoes de cores (aproximadamente) podem ser representadas pelo sistema RGB. Alem dos tres canais de cor, um canal adicional e utilizado, chamado de Alpha, que descreve a transparencia da cor (tambem em 256 niveis). Quanto aos canais de cores, 0 e a ausencia de cor, e 255 e a quantidade maxima daquela cor. Ja para a transparencia, 0 e a total transparencia, e 255 e a opacidade total.O color buffer e, entao, responsavel por armazenar as informacoes de cor de todos os pixels da tela. Cada pixel ocupa, entao, 4 bytes neste buffer, sendo 1 byte para cada canal de cor. Sabendo que o color buffer e uma area de memoria contigua, ou seja, as informacoes dos pixels estao armazenadas sequencialmente, e tendo em maos o endereco de memoria do primeiro byte deste buffer, basta converter a posicao do pixel na tela em um endereco de memoria.Considerando que a tela possui w pixels de largura e h pixels de altura, e que a origem do sistema de coordenadas esteja localizada no canto superior esquerdo da tela, como na figura abaixoOs pixels sao, assim, enderecados como pontos de coordenadas (x, y), onde x varia entre 0 e w-1, e y varia entre 0 e h-1. Ja no color buffer, como o armazenamento e sequencial, a primeira posicao corresponde ao primeiro componente do primeiro pixel, como na figura abaixo.Para a 1a linha, o endereco do primeiro componente do x-esimo pixel e 4x(+0), uma vez que cada pixel ocupa quatro espacos na memoria. E quanto ao 1o pixel da segunda linha?Dando continuidade ao raciocinio, o primeiro byte do pixel (0,2) e 4*w*2+0. Percebe-se, entao, que a formula geral para o endereco de memoria do 1o byte de um pixel qualquer (x,y) e4*w*y + 4*x = 4*(w*y + x)considerando que o endereco do primeiro byte do buffer (pixel 0,0) seja 0. Caso contrario, basta adicionar o valor deste endereco. Antes de apresentar o algoritmo de conversao, e conveniente fazer a modelagem dos pixels e das cores, isto e, agrupa-los em estruturas de forma que sejam identificaveis ao longo do programa como objetos, para que nao sejam apenas variaveis e numeros aleatorios ao longo do codigo. Assim, foram criadas as seguintes estruturas:enum color_t { RED = 0, GREEN, BLUE, ALPHA};struct pixel_t { unsigned char color[4]; unsigned short int x, y;};Cada pixel e uma estrutura composta por duas coordenadas x e y, e
um array com os valores de cada canal RGBA. Para poupar espaco, cada componente de cor e armazenado como um char unsigned, que possui exatamente 8 bits. O fato de ambas as variaveis serem unsigned ja evita que sejam armazenados valores negativos. Alias, nao evita, mas causara um underflow. O enumerador servira tanto para determinar o offset no buffer de memoria quanto para indexar os componentes do pixel.Assim, eis a funcao elementar de qualquer algoritmo de rasterizacao:void PutPixel(pixel_t pixel) { int offset = 4*(pixel.x + IMAGE_WIDTH*pixel.y); if( (pixel.x < IMAGE_WIDTH) && (pixel.y < IMAGE_HEIGHT) ) { FBptr[offset + RED ] = pixel.color[RED]; FBptr[offset + GREEN] = pixel.color[GREEN]; FBptr[offset + BLUE ] = pixel.color[BLUE]; FBptr[offset + ALPHA] = pixel.color[ALPHA]; }}Recebe-se o pixel ja formatado como o struct pixel_t, calcula-se o offset dele no frame buffer, e entao verifica-se se as coordenadas nao excedem o tamanho da tela.Para facilitar a composicao do pixel, isto e, a criacao do "objeto" pixel, criei uma funcao que recebe todos os parametros do pixel e um ponteiro para a estrutura destino.void composePixel(unsigned short int x, unsigned short int y, unsigned char r, unsigned char g, unsigned char b, unsigned char a, pixel_t* pixel) { pixel->color[RED ] = r; pixel->color[GREEN] = g; pixel->color[BLUE ] = b; pixel->color[ALPHA] = a; pixel->x = x; pixel->y = y;}Finalmente, eis o resultado do algoritmo. Executando o codigo pixel_t tempPixel; // x y r g b a composePixel(20, 30, 255, 255, 255, 255, &tempPixel); PutPixel(tempPixel);Obtem-se o resultado da figura abaixo.Destaque do pixel no ponto (20,30), de cor (255,255,255)Observa-se que a tela possui um tamanho de 512x512 pixels. Nos proximos screenshots, so serao exibidas apenas as partes relevantes.Para testar os componentes de cor, elaborei o codigo a seguir, que traca faixas de 20 pixels de altura por 256 de largura, mostrando cada um dos niveis de cor. pixel_t tempPixel; for(int i = 0; i < 60; i++) { // white line composePixel(19, 30+i, 255, 255, 255, 255, &tempPixel); PutPixel(tempPixel); } for(int i = 0; i < 256; i++) { int j; for(j = 0; j < 20; j++) { // all levels of RED composePixel(20+i, 30+j, i, 0, 0, 255, &tempPixel); PutPixel(tempPixel); } for( ; j < 40; j++) { // all levels of GREEN composePixel(20+i, 30+j, 0, i, 0, 255, &tempPixel); PutPixel(tempPixel); } for( ; j < 60; j++) { // all levels of BLUE composePixel(20+i, 30+j, 0, 0, i, 255, &tempPixel); PutPixel(tempPixel); } }Compilando e executando, obtem-se o resultado abaixo. A linha branca indica o inicio das faixas de cor.Resultado do bloco de codigo anterior, faixas de 20 pixels de altura e 256 pixels de comprimentoUm detalhe curioso e que a parte final (a direita) das barras parece estar desalinhada, nao e? Parece que a barra vermelha termina mais cedo que a verde, que por sua vez termina antes da azul. E, realmente, se voce olhar mais de perto, essa hipotese se confirma, como pode ser visto na foto a seguir.Foto macro do monitor. Observe o final desalinhado das barras coloridas.Se voce olhar mais de perto ainda, podera ver que as cores estao,
de fato, desalinhadas. Entretanto, os pixels estao alinhados. O que se ve desalinhado e, na verdade, cada componente do pixel: o componente R esta mais a esquerda, o G no meio, e o B esta mais a direita. Mas todos fazem parte do mesmo pixel, e a fronteira das barras esta na mesma coluna.Fim das barras ampliado. Pode-se ver a unidade elementar da imagem: o pixel.Algo que ainda nao consegui descobrir como funciona e o valor de alpha. Por mais que eu varie o valor de alpha para a mesma cor de pixel, o tom nao muda. Nao creio que seja algum problema do meu algoritmo, mas vou procurar a solucao. Continua na segunda parte
Continuacao da primeira parteTendo em maos a funcao que desenha um pixel na tela, o passo seguinte e criar um algoritmo que desenhe linhas, conectando dois pixels na tela e interpolando as cores entre eles. Nesta primeira parte, focarei apenas no algoritmo para desenhar as linhas.Conectando dois pixels: o algoritmo de BresenhamO algoritmo mais classico para desenhar linhas e o de Bresenham. Apesar de nao ser tao intuitivo, e elegante, pois nao emprega nenhuma operacao de divisao ou arredondamento, funcoes associadas diretamente a processos de discretizacao.Em resumo, representa-se a reta como uma funcao de duas variaveis (equacao 1), cujo resultado e zero para pontos que estejam contidos na reta, maior que zero se o ponto estiver acima, e menor que zero se estiver abaixo da reta. Manipulando a outra forma de representacao matematica da reta (equacao 2), onde (x0, y0) e o ponto inicial e (x1, y1) e o ponto final, obtem-se a equacao 3.Representacoes matematicas de uma retaUtilizando a equacao obtida, o algoritmo avalia a intersecao da reta com uma determinada coluna de pixels e o ponto medio entre dois pixels dessa coluna, entre os quais a reta passa. Se o ponto medio estiver abaixo da reta, ou seja, a reta passa acima do ponto medio, o pixel superior e aceso. Caso contrario, o pixel inferior e aceso. A partir do resultado dessa escolha, o proximo ponto medio a ser comparado com a reta e deslocado a cada coluna de pixels avaliada, de forma que a reta passe entre dois pixels. Uma representacao grafica pode ser vista a seguir.Figura 1: ponto medio acima da retaDado que o pixel c foi aceso, avalia-se a posicao do proximo ponto medio, m. Para isso, utiliza-se a funcao d = F(x,y) definida anteriormente. Sendo d = F(m), se d < 0 (ponto medio abaixo da reta), o pixel ne (nordeste) e aceso. Caso contrario (d >= 0, ponto medio acima da reta, como na figura), o pixel e (east, leste) e aceso. Assim, temos a equacaoSupondo que o pixel e tenha sido escolhido, para a coluna de pixels seguinte, temos:Figura 2: novo ponto medio dado que o anterior estava acima da retaO que nos da a equacaoOu seja, para avaliar o proximo ponto medio, so e necessario adicionar a variavel de decisao anterior. Se o pixel ne fosse escolhido na avaliacao anterior, teriamos entaoFigura 3: novo ponto medio dado que o anterior estava abaixo da retaO que nos daria a equacaoAssim, a cada pixel escolhido basta fazer os devidos incrementos na variavel de decisao d. Quanto ao 1o pixel,O que resulta emPara que nao seja feita nenhuma divisao, multiplica-se F(x,y) por 2 em todas as equacoes. Assim, esta descrito o algoritmo de Bresenham. Substituindo alfa e beta pelos valores apresentados anteriormente, chegamos a seguinte funcao:void DrawLine(pixel_t p0, pixel_t p1) { int dx = p1.x - p0.x; int dy = p1.y - p0.y; int d = 2*dy - dx; int incNE = 2*(dy-dx); int incE = 2*dy; pixel_t cur = p0; PutPixel(cur); while(cur.x < p1.x) { if(d > 0) { cur.x++; cur.y++; d += incNE; }
else { cur.x++; d += incE; } PutPixel(cur); } PutPixel(p1);}A funcao recebe dois pontos, p0 e p1, e utiliza a variavel de decisao d para determinar a posicao do proximo ponto medio, baseada na escolha do ponto medio atual. Porem, este algoritmo apresenta duas limitacoes: 1) so traca retas com coeficiente angular entre 0 e 1, ou seja, dx > dy; 2) se os pontos forem fornecidos em ordem diferente, nao funciona.A primeira limitacao se deve ao fato de o processo avancar no eixo x, isto e, incrementando a variavel x e avaliando a posicao y da reta, onde x varia entre o ponto inicial e o final. Se a reta possuir uma inclinacao maior que 1, este metodo e falho, visto que para um mesmo valor de x, pode haver mais de um pixel aceso na coluna. Foley explica:Considere uma aproximacao da reta de 1 pixel de espessura; Que propriedades deveria ter? Para linhas com inclinacoes entre -1 e 1 (inclusive), exatamente 1 pixel deve ser aceso em cada coluna; para linhas com inclinacoes fora desse intervalo, exatamente 1 pixel deve ser aceso em cada linha.Vamos, entao, estudar como adaptar o algoritmo para inclinacoes maiores que 1. Em seguida, trataremos de coeficientes negativos. Vejamos, entao, como se comporta uma reta do primeiro caso.Agora temos uma rotina diferente, porem a funcao de decisao F(x,y) descrita anteriormente ainda e valida. Se o ponto medio estiver a esquerda da reta (acima da reta, logo d > 0), acende-se o pixel n (norte). Caso contrario, se estiver a direita (abaixo da reta, logo d < 0), escolhe-se o pixel ne. Assim, temos a equacao abaixo para a variavel de decisao:Perceba que os incrementos em xc e yc sao diferentes, uma vez que a progressao do ponto medio tambem e diferente. Vamos agora para o proximo pixel.Se o pixel escolhido anteriormente for o norte, o proximo ponto medio sofrera apenas um incremento na sua coordenada y. Assim,Ja se o pixel escolhido anteriormente tivesse sido o ne,Teriamos a equacaoE quanto a 1o variavel de decisao,O que nos daComparando os dois casos vistos ate agora, percebe-se que sao bastante similares. Analisemos agora os casos em que o coeficiente angular e negativo. Primeiro, para a inclinacao entre -1 e 0.Percebe-se que, agora, o dy e negativo, e o ponto medio sofre decremento em y. Alem disso, se o ponto medio atual estiver acima da reta, o proximo ponto medio sofrera deslocamento tanto vertical (para baixo), quanto horizontal. Caso esteja abaixo da reta, sofrera apenas o deslocamento horizontal. Assim, e desnecessario mostrar todas as equacoes para os dois casos. Basta deduzir os incrementos e decrementos a partir da imagem.Agora, para inclinacoes menores que -1, o raciocinio e o mesmo para inclinacoes maiores que 1.Organizando todas as combinacoes possiveis para o coeficiente angular e para a variavel de decisao, montei a tabela abaixo, que sumariza tudo o que foi explicado ate agora.A coluna "d+=" representa o incremento na variavel de decisao baseado no resultado anterior. Ja a coluna "coord." mostra os incrementos nas coordenadas. E se os pontos forem fornecidos em
ordem diferente? Bem, isso significa que x1 < x0. Neste caso, ha dois casos possiveis:Isto pode ser resolvido facilmente trocando os pontos de posicao e, consequentemente, invertendo os sinais de dx e dy:Agora, finalmente, vamos ao algoritmo!void DrawLine(pixel_t p0, pixel_t p1) { int dx = p1.x - p0.x; int dy = p1.y - p0.y; int d, incDPos, incDNeg; if(dx < 0) { // inverter os pontos se em ordem contraria pixel_t temp = p0; p0 = p1; p1 = temp; dx = -dx; dy = -dy; } int dySig = (dy < 0)?(-1):1; // dy signal pixel_t cur = p0; // variavel temporaria PutPixel(p0); if( dySig*dy <= dx ) { // |m| <= 1 d = 2*dy - dySig*dx; // d_start incDPos = 2*(dy - dySig*dx); // if d >= 0 incDNeg = 2*dy; // if d < 0 while(cur.x < p1.x) { // para cada x if(d < 0) { d += incDNeg; cur.x++; } else { d += incDPos; cur.x++; cur.y += dySig; } PutPixel(cur); } } else { // |m| > 1 d = dy -2*dySig*dx; // d_start incDPos = -2*dx*dySig; // if d >= 0 incDNeg = 2*(dy - dySig*dx); // if d < 0 while( cur.y*dySig < p1.y*dySig ) { // for each y if(d < 0) { d += incDNeg; cur.x++; cur.y += dySig; } else { d += incDPos; cur.y += dySig; } PutPixel(cur); } } PutPixel(p1);}Para avaliar as quatro faixas de inclinacao, utilizei o codigo a seguir:int i = 0;void MyGlDraw(void) { pixel_t p0, p1; composePixel(100, 250, 255, 255, 255, 255, &p0); composePixel(200, 400-i, 255, 255, 255, 255, &p1); DrawLine(p0, p1); if(i++ >= 300) i = 0;}Me utilizei do fato que esta funcao e executada continuamente, porem com um pequeno delay entre chamadas consecutivas, o que permite visualizar cada loop. O resultado pode ser visto no video a seguir:Para m > 0, o algoritmo funcionou como deveria (lembrando que o ponto de origem da tela esta no canto superior esquerdo). O problema apareceu ao tracar retas com m < 0.Apos algumas tentativas de solucionar o problema, acidentalmente consegui resolver para -1 < m < 0, invertendo o sinal dos incrementos quando dy < 0 (linhas 21 e 22 do codigo anterior).incDPos = dySig*2*(dy - dySig*dx); // if d >= 0incDNeg = dySig*2*dy; // if d < 0Dai, fui tentar descobrir o porque de ter dado certo. Voltando aos calculos, percebi que cometi um erro. Quando m < 0, isto e, dy < 0 (dado que dx > 0 sempre), a funcao de decisao assume sinais opostos aos presumidos anteriormente.Cheguei a essa conclusao atraves de exemplos. Suponha a reta definida pela equacao y = -x/4. Como dito anteriormente, dado que dx > 0, dy < 0 e, portanto, assumamos dy = -1 e dx = 4.Usando a outra representacao da reta, F(x,y) = x + y + e substituindo os coeficientes, chega-se a funcao de decisao F(x,y) = -x - 4y. Tomemos como referencia o ponto (x,y) = (4,-1). Percebe-se que o mesmo esta contido na reta, pois F(4,-1) = -4 - 4*(-1) = 0.
Incrementemos a coordenada y em 1 unidade, obtendo o ponto (4,0), claramente acima da reta. Dai, F(4,0) = -4 - 4*0 = -4 < 0. Decrementemos a coordenada y do ponto de referencia em 1 unidade. Obtemos F(4,-2) = - 4 - 4*(-2) = 4 > 0. Ou seja, para pontos acima da reta, d < 0, e para pontos abaixo, d > 0.Isto muda drasticamente o algoritmo, pois, para -1 < m < 0, por exemplo, se o ponto estiver abaixo da reta (e, portanto, d > 0), incrementa-se apenas a coordenada x. Ja se estiver acima da reta (e, portanto, d < 0), incrementa-se ambas as coordenadas x e y. O mesmo raciocinio se aplica para m < -1 (com os devidos incrementos e decrementos, e claro).Assim, obtemos uma nova tabela de possibilidades, que pode ser vista a seguir.Corrigindo o algoritmo, percebi que nao adiantaria muito generalizar as variaveis de decisao e de incrementos, pois o trabalho seria praticamente o mesmo para trocar de sinal e/ou permutar os incrementos ao longo do programa, alem de nao ser tao intuitivo quanto discretizar os casos possiveis. Assim, segue a funcao corrigida.void DrawLine(pixel_t p0, pixel_t p1) { int dx = p1.x - p0.x; int dy = p1.y - p0.y; int d, incDPos, incDNeg; if(dx < 0) { // exchange points if order reversed pixel_t temp = p0; p0 = p1; p1 = temp; dx = -dx; dy = -dy; } int dySig = (dy < 0)?(-1):1; // dy signal pixel_t cur = p0; // buffer for current pixel PutPixel(p0); if( dySig*dy <= dx ) { // |m| <= 1 if(dy < 0) { d = 2*dy + dx; // d_first while(cur.x < p1.x) { // for each x if(d < 0) { // midpoint ABOVE the line d += 2*(dy + dx); cur.x++; cur.y--; } else { // midpoint BELOW the line d += 2*dy; cur.x++; } PutPixel(cur); } } else { d = 2*dy - dx; // d_first while(cur.x < p1.x) { // for each x if(d < 0) { //midpoint BELOW the line d += 2*dy; cur.x++; } else { // midpoint ABOVE the line d += 2*(dy - dx); cur.x++; cur.y++; } PutPixel(cur); } } } else { // |m| > 1 if(dy < 0) { d = dy + 2*dx; // d_first while( cur.y > p1.y ) { // for each y if(d < 0) { // midpoint ABOVE the line d += 2*dx; cur.y--; } else { // midpoint BELOW the line d += 2*(dy + dx); cur.x++; cur.y--; } PutPixel(cur); } } else { d = dy -2*dx; // d_first while( cur.y < p1.y ) { // for each y if(d < 0) { // midpoint BELOW the line d += 2*(dy - dx); cur.x++; cur.y++; } else { // midpoint ABOVE the line d += -2*dx; cur.y++; } PutPixel(cur); } } } PutPixel(p1);}O video a seguir prova o funcionamento do algoritmo, inclusive para os pontos fornecidos em ordem contraria. Executando o codigo a seguir,int i = 0;void MyGlDraw(void) { pixel_t p0, p1; composePixel( 0, 250, 255,
255, 255, 255, &p0); composePixel(200, 500-i, 255, 255, 255, 255, &p1); DrawLine(p0, p1); composePixel(500, 250, 255, 255, 255, 255, &p0); composePixel(300, i, 255, 255, 255, 255, &p1); DrawLine(p0, p1); if(i++ >= 500) i = 0;}Colorindo as linhas: interpolacao linear de corNao basta apenas desenhar as linhas. Elas tem que ter cor. E nao uma cor so, mas uma que varie entre as cores de suas extremidades. Para isso, utiliza-se a interpolacao linear, sugerida pelo professor.Interpolacao e "o metodo que permite construir um novo conjunto de dados a partir de um conjunto discreto de dados pontuais previamente conhecidos. (...) Atraves da interpolacao, pode-se construir uma funcao que aproximadamente se 'encaixe' nestes dados pontuais, conferindo-lhes, entao, a continuidade desejada" [Wikipedia]. Um dos metodos de interpolacao e o linear, que faz esse processo atraves de um polinomio de primeiro grau (uma reta).No nosso caso, temos uma funcao y = f(x), que recebe como parametro uma das coordenadas de um pixel intermediario (ou x ou y, como sera visto mais a frente), e retorna a cor desse pixel, baseada na cor dos pixels das extremidades do intervalo.Para visualizar o processo, considere dois pontos, Pi = (xi, yi) e Pi+1 = (xi+1, yi+1), onde yi = f(xi). Deseja-se encontrar o valor da funcao para uma determinada coordenada situada entre xi e xi+1, como na figura abaixo.Por semelhanca de triangulos, sabe-se que:Manipulando os termos, tem-se:O que nos da a equacao da reta que passa pelos dois pontos conhecidos. Adaptando para o nosso caso, consideramos que yi e yi+1 sao as cores dos pixels inicial e final da reta, respectivamente, e y e a cor que se deseja obter do pixel intermediario. Como sao quatro componentes de cor, faz-se o processo para cada um deles, considerando os respectivos componentes dos pixels das extremidades.Quanto a coordenada de entrada, segue-se o mesmo raciocinio utilizado no algoritmo de Bresenham (relembre): para |m| <= 1, sera a coordenada x; caso contrario, sera a coordenada y.Assim, segue a funcao que faz este processo. Ela recebe os dois pixels que definem a linha (extremidades), e o ponteiro para o pixel que sera sendo processado dentro da funcao DrawLine.void interpolateColor(pixel_t p0, pixel_t* p, pixel_t p1) { int dx = (p1.x - p0.x); int dy = (p1.y - p0.y); int dySig = (dy < 0)?-1:1; // checks dy signal if( dy*dySig <= dx ) { // |m| <= 1 for(int i = RED; i <= ALPHA; i++) { // for each component, interpolate p->color[i] = (unsigned char)(p0.color[i] + (p->x - p0.x)*(p1.color[i] - p0.color[i])/dx); } } else { // |m| > 1 for(int i = RED; i <= ALPHA; i++) { // for each component, interpolate p->color[i] = (unsigned char)(p0.color[i] + (p->y - p0.y)*(p1.color[i] - p0.color[i])/dy); } } }E, no arquivo main.cpp,int i = 0;void MyGlDraw(void) { pixel_t p0, p1; composePixel(250, 0, 255, 0, 0, 255, &p0); composePixel(500-i, 200, 0, 0, 255, 255, &p1); DrawLine(p0, p1); composePixel(250, 500, 0, 0, 255, 255, &p0); composePixel(0+i, 300, 0, 255, 0, 255, &p1); DrawLine(p0, p1); if(i++ >= 500) i = 0;}O resultado desse codigo pode ser visto no video a seguir.Desenhando