Nessa aula vamos apresentar alguns recursos típicos encontrados em sistemas gráficos usados para gerar imagens por computador.
Um sistema gráfico computacional possui os elementos típicos de um computador de mesa ou portátil comum, como ilustrado na Figura Fig. 3.1, ou seja, possui dispositivos de entrada (como teclado, mouse e joystick) e saída (como monitor, impressora e plotter), um processador central (CPU), um processador gráfico (GPU) e memória.
Fig. 3.1 Elementos de um sistema gráfico.¶
Os dispositivos de entrada, como teclado e mouse, são necessários para que o usuário possa interagir com computadores usando interfaces gráficas do tipo WIMP (Windows, Icons, Menus, and Pointg), ou seja, interfaces formadas por janelas, ícones, menus e um cursor para apontamento dos elementos gráficos. Outros dispositivos, como mesas digitalizadoras e controles para jogos, são comuns em muitos aplicativos gráficos interativos, para criação de desenhos e edição de imagens.
A saída gráfica pode ser feita por meio de impressoras e plotters, mas o dispositivo mais comum são os monitores de vídeo por permitirem interação em tempo real e a visualização de animações. Os monitores mais utilizados hoje são do tipo raster, ou seja, que representam informações gráficas por meio de uma matriz de pixels. Cada monitor pode utilizar uma tecnologia distinta na sua construção e funcionamento. Os tipos mais comuns de monitores são:
A resolução mais comum para monitores atualmente é chamada de “full HD”, que corresponde a 1920\(\times\)1080 pixels. Resoluções cada vez maiores estão se tornando comuns também, como monitores 4K (ou “ultra HD”) por exemplo, que possuem 4 vezes mais pixels que um monitor full HD.
Em sistemas mais simples, o framebuffer armazena a imagem a ser exibida no monitor. O Controlador de Vídeo é um módulo responsável para ler a imagem do framebuffer e gerar o sinal de vídeo para o monitor.
Para exibir uma imagem RGB full HD é necessário reservar um framebuffer com cerca de 6 MB (mega bytes). Essa quantidade de memória era indisponível em computadores mais antigos e por isso várias alternativas foram desenvolvidas para contornar essa limitação.
Uma forma para economizar memória em sistemas gráficos é reduzir a profundidade das imagens utilizadas, por exemplo, utilizando uma tabela ou palete de cores para imagens coloridas. Imagine, por exemplo, que nossas imagens possam ser representadas por um conjunto de 256 cores de 24 bits cada cor. Essas cores podem ser codificadas em uma tabela com 256 entradas, onde cada entrada corresponde a uma cor de 24 bits. Esses 256 códigos correspondem aos índices das linhas da tabela que precisam de 8 bits para serem codificadas. Esse código de 1 byte pode ser colocado em cada pixel, ao invés dos 3 bytes da cor, economizando assim 2/3 (dois terços) da memória necessária para armazenar a imagem original com 24 bits por pixel. Nesse caso, a imagem full HD pode ser agora representada com cerca de 2 MB, além da memória necessária para armazenar a tabela com os códigos das cores.
Uma alternativa capaz de reduzir ainda mais o consumo de memória é o uso de imagens vetoriais ao invés de imagens raster. Imagens vetoriais são apropriadas para representar desenhos formados por segmentos de linha (ou simplesmente linhas), onde um segmento é representado por dois pontos na tela do monitor. Assim, um desenho formado por centenas ou até mesmo milhares de linhas pode ser representado de forma bem mais compacta (em termos de memória) que uma imagem raster. Além de serem facilmente escaláveis, esses desenhos também podem ser exibidos de forma relativamente simples em monitores analógicos que utilizam tubo de raios catódicos, fazendo o feixe de elétrons do tubo varrer a lista de segmentos de forma contínua.
Observe que, para desenhar uma linha em monitores raster a partir de 2 pontos, é necessário calcular a posição de cada pixel interior ao segmento. Algoritmos de rasterização servem para calcular esses pixeis para a geração da imagem raster.
Em sistemas mais complexos, o framebuffer pode armazenar outras informações também, como profundidade ou opacidade na representação RGBA. Além disso, com mais memória disponível, não é mais necessário limitar cada cor a 256 níveis por exemplo. Framebuffers modernos permitem representar cada pixel usando 12 ou mais bits por cor, permitindo a renderização de imagens com larga faixa dinâmica (HDR ou high dynamic range).
Em sistemas mais simples, o processador gráfico ou GPU (Graphics Processing Unit) fica integrado ao processador central ou CPU (Central Processing Unit). Em sistemas mais complexos o processamento gráfico fica em um circuito ou placa dedicada, com memória própria. O framebuffer faz parte da memória dedicada ao processamento gráfico e por isso faz parte da memória utilizada pela GPU ou, em sistemas sem GPU, como parte da memória do sistema.
Embora a maioria dos monitores trabalhem com frequências de 60 Hz ou mais, a taxa de geração de imagens em animações costuma ser bem inferior. No entanto, para manter uma sensação de continuidade, uma animação precisa manter uma taxa de aproximadamente 15 novos quadros por segundo (fps – frames per second). Abaixo de 10 fps, a sensação de descontinuidade da animação começa a ser notada pela maioria das pessoas. Para garantir uma boa qualidade, os projetores antigos nos cinemas exibiam filmes a 24 fps e na TV a 30 fps.
Chamamos de renderização o processo de geração dos pixels (posição e cor) no framebuffer a partir de um modelo de cena (representação geométrica dos objetos). Muito desse processo ocorre atualmente dentro da GPU que, para obter um alto desempenho, utilizam uma arquitetura paralela conhecida como pipeline gráfico. A Figura Fig. 3.2 ilustra os principais módulos de um pipeline gráfico.
Fig. 3.2 Estágios do pipeline gráfico.¶
Para executar um comando simples como “desenhe um triângulo em 3D”, o pipeline primeiramente recebe o conjunto de vértices que definem o triângulo. O processador de vértices usa os parâmetros da câmera virtual para transformar as coordenadas dos vértices para um sistema centrado na câmera. As transformações são representadas por matrizes e a mudança de coordenadas envolve a multiplicação dessas matrizes.
Após a transformação de coordenadas, alguns vértices podem ficar fora do campo visual da câmera. O módulo de clipping (recorte) remove esses vértices e, caso alguma parte dos segmentos ainda sejam visíveis, esses segmentos são redefinidos na forma de novos vértices.
Os segmentos visíveis são passados para o módulo de rasterização, que usa os vértices visíveis para pintar os pixels da parte visível do triângulo, que são chamados de fragmentos. Observe que a cena pode ser composta por outros objetos que resultam em mais fragmentos. Esses fragmentos são combinados pelo processador de fragmentos para calcular a cor final de cada pixel do framebuffer.
As placas gráficas mais antigas possuíam um pipeline rígido com poucos recursos. As placas mais recentes permitem programar o processador de vértices e o processador de fragmentos. Com isso podemos implementar vários efeitos em tempo real que não era possível com as placas mais antigas (como bump mapping). Chamamos de shaders esses programas que são executados pela GPU.
Nas placas gráficas mais antigas era também comum avaliar o desempenho das placas pelo número de primitivas geométricas processadas por segundo (front end) ou então pela taxa de bits que podiam ser movidos para o framebuffer (back end). Hoje as GPUs mais poderosas podem usar ponto flutuante em todo o pipeline, até no framebuffer, um recurso útil para representar e manipular imagens com grande amplitude dinâmica (HDR – high dynamic range). Essas GPUs são chamadas de unified shading engines pois fazem o processamento dos vértices e fragmentos concorrentemente. Hoje, esse grande poder computacional é aproveitado por muitas aplicações não relacionadas à geração de imagens.
Mesmo sendo muito eficiente, programar o pipeline para criar 30 imagens por segundo necessárias para gerar boas animações ainda é um desafio.
Dizemos que cada redesenho faz parte de um ciclo de atualização (refresh cycle) pois o programa deve atualizar o conteúdo da imagem. O programa se utiliza de uma biblioteca ou API (application programming interface) para se comunicar com o sistema gráfico. Existem várias APIs diferentes usadas em sistemas gráficos modernos, cada uma fornecendo alguns recursos distintos em relação às outras. De um modo geral, as APIs gráficas podem ser classificadas em duas classes gerais:
O OpenGL se tornou uma API padrão largamente utilizada. Ela está disponível em praticamente todas as plataformas e pode ser acessada pelas mais utilizadas linguagem de programação como C/C++, Java e Python. Para que posso funcionar em plataformas tão diferentes, ela precisa ser genérica, em contraste por exemplo com o DirectX, que foi desenvolvido para funcionar principalmente em sistemas da Microsoft.
Para facilitar essa generalidade e se tornar o OpenGL independente do sistema operacional e do sistema que controla as janelas da interface, o OpenGL não fornece recursos para entrada e saída para interação com usuárias e usuários.
O OpenGL trabalha, em sua maior parte, no modo imediato. Isso significa que cada chamada de função resulta no envio de um comando diretamente para a GPU. No entanto, há alguns elementos retidos como, por exemplo, transformações, iluminação e texturização que precisam ser configurados previamente para que possam ser aplicadas no cálculo. Por exemplo, o OpenGL não oferece recursos para criar uma janela, redimensionar uma janela, determinar as coordenadas atuais do mouse ou detectar se uma tecla do teclado foi pressionada. Para atingir esses outros objetivos, é necessário usar um kit de ferramentas adicional. Nesse curso vamos adotar a versão WebGL, que permite criar aplicações gráficas que fazem uso do OpenGL a partir de qualquer navegador moderno.
Nessa aula continuamos a cobrir conceitos da computação gráfica, muitos deles associados ao desenvolvimento histórico dos sistemas gráficos. Com a evolução desses sistemas, temos muito mais recursos computacionais a disposição (tanto de hardwares quanto de software) e GPUs muito poderosas mas otimizadas para tratar de elementos geométricos básicos.
O OpenGL é uma API gráfica genérica que permite o desenvolvimento de aplicativos gráficos para praticamente qualquer plataforma. A versão WebGL permite usar o OpenGL dentro de um navegador. Do ponto de vista pedagógico, isso facilita bastante o ensino pois reduz a necessidade de instalação de ferramentas de desenvolvimento, pois necessitamos basicamente de um editor de texto, e estimula a prática pois os exercícios que você vai desenvolver podem ser executados em qualquer navegador.
Na próxima aula vamos começar a introduzir alguns elementos básicos de HTML e JavaScript para desenvolver nossas primeiras aplicações. Logo em seguida vamos introduzir o WebGL, usando esse ambiente Web de programação com HTML+JavaScript.
Recomendamos a seguinte leitura para complementar essas notas: