OpenGL

Página  4  de  8  

4. Pipeline do OpenGL

Vértices e Primitivas
   No OpenGL, a maior parte dos objectos geométricos são desenhados, englobando uma série de conjuntos de coordenadas que especificam vértices ou opcionalmente as normas (comprimento de um vector), coordenadas das texturas e cores entre o par de comandos glBegin/glEnd. Por exemplo, para especificar um triangulo com os vértices (0, 0, 0) e, (1, 0, 1), podíamos fazer da seguinte maneira:

glBegin(GL_POLYGON);
  glVertex3i(0,0,0);
  glVertex3i(0,1,0);
  glVertex3i(1,0,1);
glEnd();

   Os dez tipos de objectos geométricos que são desenhados desta maneira estão sumariados na Tabela 1. Este grupo particular de objectos é seleccionado porque a geometria destes é especificada por uma simples lista de vértices, uma vez que cada um admite um algoritmo eficiente de renderização, e foi determinado que estes objectos combinados satisfazem as necessidades de quase todas as aplicações gráficas.
   Cada vértice pode ser especificado por duas, três ou quatro coordenadas (quatro coordenadas indicam uma localização tridimensional homogénea). Adicionalmente, a norma, as coordenadas da textura e a cor correntes, podem ser usadas para processar cada vértice. O OpenGL usa normais no cálculo de iluminações; a normal corrente é um vector tridimensional que pode ser definido enviando três coordenas que a especifiquem. As cores podem consistir em vermelho, verde, azul e valores alfa (quando o OpenGL foi inicializado para o modo RGBA), ou através do valor que define a cor. Uma, duas, três ou quatro coordenadas de texturas determinam como a textura de uma imagem é mapeada para uma primitiva.

Objecto

Interpretação dos Vertices

point

cada vértice descreve a localização de um ponto

line strip

séries de segmentos de recta ligados; cada vértice posterior descreve primeiro o ponto final do próximo segmento

line loop

igual ao line strip mas o segmento final é adicionado do ultimo vértice para o primeiro vértice

separate line

cada par de vértices descreve um segmento de linha

polygon

o line loop é formado por vértices da fronteira de um polígono convexo

triangle strip

cada vértice posterior aos dois primeiros descreve um triangulo dado por esse vértice, mais os dois anteriores

triangle fan

cada vértice posterior aos dois primeiros descreve um triangulo dado por esse vertice, mais o anterior e pelo primeiro vértice

separate triangle

cada trio de vértices descrevem um triangulo

quadrilateral strip

cada par de vértices posterior aos dois primeiros, descreve um quadrilátero, dado por esse par e pelo par anterior

independent quad

cada grupo consecutivo de 4 vértices descreve um quadrilátero

Tabela 1 - Objectos pertencentes ao glBegin/glEnd [1].

   Cada comando que especifique as coordenadas, a normal, as cores, ou a textura de um vértice é disponibilizado em vários formatos para acomodar aos diferentes formatos de aplicações de dados e a números de coordenadas. Os dados podem ser transferidos para estes comandos como listas de argumentos ou como ponteiros para blocos de armazenamento que contenham dados. As variantes são distinguidas por (na linguagem C) mnemónicas de sufixos.
   A maior parte dos comandos do OpenGL que não especifiquem vértices ou informação associada não podem aparecer entre o glBegin e o glEnd. Esta restrição permite às implementações executar de um modo optimizado enquanto são processadas simultaneamente as especificações de primitivas, de modo a garantir um processamento o mais eficiente possível [4].

Figura 2 - Associação de valores correntes com um vértice

   Quando um vértice é especificado, a sua cor, a normal e as coordenadas da textura são usadas para obter valores que mais tarde serão associados ao vértice (Figura 2). O próprio vértice é transformado pelo matriz de visualização do modelo (model-view matrix), a qual pode representar transformações translacionais e lineares. A cor é obtida através de cálculos da cor pelas iluminações ou, caso as iluminações estejam desactivadas, obtida pela própria cor. As coordenadas das texturas são passadas pela função de texture coordinate generation. As coordenadas das texturas resultantes são transformadas pela matriz de texturas (texture matrix) (esta matriz pode ser usada para redimensionar ou rodar a textura que é para ser aplicada a uma primitiva).
   Existem grupos de comandos que controlam os valores dos parâmetros usados no processamento de vértices. Um desses grupos de comandos manipula transformações de matrizes. Estes comandos são desenhados de modo a formar uma maneira eficiente de gerar e manipular transformações que ocorrem numa hierarquia de cenários 3D. Uma matriz pode ser carregada ou multiplicada através de redimensionamento, rotação, translação ou por uma outra matriz. Outro comando controla qual a matriz afectada por uma manipulação: a matriz de visualização do modelo, a matriz de texturas ou a matriz de projecção (projection matrix). A cada um destes três tipos de matrizes está associada uma pilha, na qual as matrizes podem ser inseridas ou removidas (push ou pop).
   Os parâmetros de iluminação são agrupados em três categorias: parâmetros de material, que descrevem as características dos reflexos da superfície iluminada; os parâmetros da fonte de iluminação, que descrevem as propriedades de emissão de cada fonte de luz; e os parâmetros do modelo de luz, que descrevem propriedades globais do modelo de luz. As iluminações são aplicadas com base nos vértices; os resultados das iluminações são interpolados por um segmento de recta ou por um polígono. A forma geral de uma equação de iluminação inclui termos constantes, de difusão e de iluminação focal, cada um deles pode ser atenuado pela distância do vértice da fonte de luz. O programador pode sacrificar o realismo em favor de cálculos para efeitos luminosos mais rápidos, indicando que quem vê a imagem, os pontos de luz ou ambos, estão a uma grande distância do cenário.

Projecção e Corte (Projection and Clipping)
   Assim que uma primitiva é construída a partir de um grupo de vértices, esta é sujeita a um processo de corte através de superfícies de corte (clip planes). As posições destas superfícies (cada implementação em OpenGL tem de conter pelo menos seis) são especificadas usando o comando glClipPlane. Cada superfície pode ser activada ou desactivada individualmente.
   No caso de um ponto, as superfícies de corte não têm qualquer efeito no ponto ou então simplesmente elimina-o, dependendo se o ponto está dentro ou fora da intersecção dos half-spaces determinados pelas superfícies de corte. No caso de se tratar de um segmento de recta ou de um polígono, as superfícies de corte podem, não ter qualquer efeito, eliminar, ou alterar o valor original da primitiva. No último caso, os novos vértices podem ser criados entre os cantos descritos pelos vértices originais; a cor e os valores das coordenadas das texturas para estes novos vértices são determinados interpolando os valores atribuídos relativamente aos vértices originais.
   Após o processo das superfícies de corte ter sido aplicado (caso tenha sido necessário), as coordenadas dos vértices das primitivas resultantes são transformadas pela matriz de projecção. Ocorre então o view frustum clipping. O view frustum clipping é parecido à aplicação das superfícies de corte, mas para superfícies fixas: se as coordenadas depois de uma transformação são dadas por (x, y, z, w), então os seis half-spaces definidos por estas superfícies são -w ≤ x, x ≤ w, -w ≤ y, y ≤ w, -w ≤ z, z ≤ w.
   Com a operação de view frustum clipping terminada, cada grupo de coordenadas de vértices é projectada calculando x/w, y/w e z/w. O resultado (que tem que estar entre [-1, 1]) é multiplicado e compensado pelos parâmetros que controlam o tamanho do viewport, onde as primitivas são desenhadas. Os comandos glViewport (para x/w e y/w) e glDepthRange (para z/w) controlam estes parâmetros.

Rasterização
   O processo de rasterização converte uma primitiva viewport-scaled numa série de fragmentos. Cada fragmento contém a localização de um pixel no framebuffer, a cor, as coordenadas das texturas e a profundidade (Z). Quando um segmento de linha ou um polígono são rasterizados, os dados associados são interpolados através de primitivas de forma a obter o valor para cada fragmento [4].
   A rasterização de cada tipo de primitiva é controlada por um correspondente grupo de parâmetros. Uma largura afecta a rasterização de um ponto e a outra afecta a rasterização de um segmento de linha. Adicionalmente, uma stipple sequence pode ser especificada para segmentos de recta e, um stipple pattern pode ser especificado para polígonos.
   O antialiasing pode ser activado ou desactivado individualmente para cada primitiva. Quando for activado, um valor de cobertura é computado para cada fragmento descrevendo uma porção desse fragmento que é coberto pela primitiva projectada. Este valor de cobertura é usado depois do processo de texturização ter terminado, para modificar o valor do fragmento alfa (quando em modo RGBA) ou o valor do índice da cor (quando em modo de indexação de cor).

Texturização e Nevoeiro (Texturing and Fog)
   O OpenGL fornece meios gerais para gerar primitivas mapeadas de texturas. Quando a texturização está activada, cada fragmento das texturas coordena a indexação de textura das imagens, gerando uma texel. Esta texel pode ter entre um a quatro componentes, de modo que a textura da imagem possa representar, por exemplo, apenas a intensidade (uma componente), cor RGB (três componentes), ou RGBA (quatro componentes). Quando uma texel é obtida, ela modifica a cor do fragmento de acordo com a especificação do ambiente de texturização.
   A imagem da textura é especificada usando o comando glTexImage, que recebe argumentos semelhantes aos do comando glDrawpixels de modo a que o mesmo formato da imagem possa ser usada com o framebuffer ou a memória de texturas (todas as placas gráficas têm alocada uma fracção de memória para as texturas). Adicionalmente, o glTexImage pode ser usado para especificar mipmaps, de modo que a textura possa ser filtrada à medida que é aplicada a uma primitiva. A função responsável pelo filtro é controlada por um número específico de parâmetros usando o comando glTexParameter. O ambiente da textura é seleccionado com o glTexEnv.
   Finalmente, após o processo de texturização, a função do nevoeiro (se estiver activada) é aplicada a cada fragmento. Esta função mistura as cores que recebe com uma cor constante do nevoeiro de acordo com um factor ponderado computado. Este factor é a função da distância (ou uma aproximação à distância) do ponto de vista de quem vê até ao ponto tridimensional que corresponde ao fragmento. Nevoeiro exponencial simula nevoeiro atmosférico e neblina, enquanto o nevoeiro linear pode ser usado para produzir uma perda gradual de qualidade com a distância (depth-cueing).

O Framebuffer
   O destino dos fragmentos rasterizados é o framebuffer, onde os resultados da renderização do OpenGL podem ser mostrados. No OpenGL, o framebuffer consiste num tabela rectangular de pixels correspondentes à janela destinada para a renderização OpenGL. Cada pixel é simplesmente um conjunto de alguns números de bits. Bits correspondentes a cada pixel no framebuffer são agrupados juntamente num bitplane, onde cada bitplane contém apenas um bit de cada pixel [4, 5].
   Os bitplanes são agrupados em vários buffers lógicos: de cor, profundidade, stencil, e de acumulação. O buffer de cor é onde a informação sobre o fragmento da cor é colocado. No buffer de profundidade é colocado informação sobre o fragmento de profundidade. No buffer de stencil existem valores que são sempre actualizados quando o correspondente fragmento chega ao framebuffer. Os valores de stencil são úteis em algoritmos multi-passo, onde cada cenário é renderizado várias vezes, até atingir um efeito como o das operações CGS (união, diferença e intersecção) num dado número de objectos e capping de objectos cortados por superfícies de corte.
   O buffer de acumulação também é útil para algoritmos multi-passo e, pode ser manipulado de modo a calcular a média dos valores armazenados no buffer de cores. Isto pode aplicar efeitos como antialiasing no écran inteiro (agitando o ponto de vista a cada passagem), profundidade de campo (agitando o ângulo de visão), suavização de movimento (avançando o cenário no tempo). Algoritmos multi-passo são simples de implementar em OpenGL, simplesmente porque um pequeno número de parâmetros têm de ser manipulados antes de cada passagem, e alterar os valores destes parâmetros é eficaz e não traz efeitos secundários aos outros parâmetros que têm de permanecer constantes.
   O OpenGL suporta buffering duplo e em estéreo (double-buffering e stereo-buffering), de modo que o buffer de cor seja subdividido em quatro buffer: os buffers de frente, esquerdo e direito e os buffers traseiros, esquerdo e direito. Os buffers frontais são aqueles que são tipicamente mostrados enquanto os buffers traseiros (numa aplicação com double-buffering) são usados para compor a próxima frame. Uma aplicação monoscopic usaria apenas buffers esquerdos. Adicionalmente, podem existir buffers auxiliares (que nunca são mostrados) para onde os fragmentos podem ser renderizados. Cada um dos buffers pode ser activado ou desactivado individualmente para escrita de fragmentos [4].
   Uma cópia particular do OpenGL pode não dispor de buffers de profundidade, stencil, acumulação ou buffers auxiliares. Apenas alguns subconjuntos do buffer frontal esquerdo e direito e do buffer traseiro esquerdo e direito é que podem estar presentes. Diferentes tipos de buffers podem estar disponíveis (dependendo cada um do número de bits) dependendo da plataforma e do sistema de janelas onde o OpenGL está a executar. Todos os sistemas de janelas têm, no entanto, de fornecer pelo menos uma janela com um buffer frontal (esquerdo) de cor, de profundidade, stencil e de acumulação. Isto garante um mínimo para a configuração que um programador pode assumir que está presente independentemente onde o programa OpenGL se encontra.

Operações por fragmento
   Antes ser colocado na localização correspondente do framebuffer, um fragmento é sujeito a vários testes e modificações, as quais podem ser individualmente activados, desactivados ou controlados. Os testes e modificações incluem o teste de stencil, o teste de profundidade do buffer (é tipicamente utilizado para remover superfícies escondidas), e misturas.
   O teste de stencil, quando activado, compara o valor no stencil buffer correspondente ao fragmento com o valor de referência. Se a comparação é bem sucedida, então o valor de stencil guardado pode ser alterado por uma função que incrementa, decrementa ou limpa, avançando o fragmento para o teste seguinte. Se o teste falhar, o valor guardado é actualizado por uma função diferente e o fragmento é descartado. De forma similar, o teste de profundidade do buffer compara o valor da profundidade do fragmento com o correspondente valor guardado no buffer de profundidade. Se a comparação tiver sucesso, o fragmento, é passado para a fase seguinte sendo os valores do buffer de profundidade substituídos pelo valor guardado no buffer de profundidade (se este buffer estiver activo para escritas). Caso a comparação falhe, o fragmento é descartado, não havendo alterações no buffer de profundidade.
   A mistura (blending) une a cor de um fragmento com a cor correspondente já guardada no framebuffer (a mistura ocorre uma vez sempre que um buffer de cor é activado para escrita). A função de mistura pode ser especificada por glBlendFunction.
Misturar é uma operação que permite alcançar o antialiasing para cores RGBA. Se lembrarmos que a computação de cobertura apenas altera o valor alfa do fragmento, vemos que este valor deve ser usado para misturar a cor do fragmento com a cor de fundo já guardada, de forma a obter o efeito de antialiasing. Este efeito é também usado para alcançar transparências

1 | 2 | 3 | 4 | 5 | 6 | 7 | 8