Shaders e stream processors

Os shaders são pequenos programas utilizados pelos jogos e aplicativos de renderização recentes para executar operações específicas dentro das imagens, criando efeitos diversos, fazendo com que objetos se movam ou sejam distorcidos de forma realística ou simplesmente criando objetos muito detalhados, que seriam complexos demais para criar usando apenas polígonos e texturas.

Um caso clássico é o cabelo dos personagens, onde só é possível obter um resultado realístico desenhando cada fio separadamente e fazendo com que eles se movam de forma mais ou menos independente, acompanhando os movimentos do personagem. Outros usos comuns são efeitos de explosões, grama (não o desenho das folhas propriamente ditas, mas sobretudo a movimentação), folhas de árvores e outros efeitos e detalhes diversos em objetos. Os shaders fazem um trabalho muito bom em todas essas áreas, consumindo relativamente pouco poder de processamento e oferecendo animações realísticas.

Em poucas palavras, os shaders permitem adicionar “profundidade” e realismo aos objetos da cena, contrastando com a aparência lisa e pouco realista dos gráficos usados em jogos antigos.

Antes dos shaders, todas as placas 3D ofereciam apenas um conjunto de efeitos pré-programados, que eram processados por componentes especializados dentro do pipeline gráfico da GPU. A lista incluía efeitos de luz, cálculo de perspectiva, filtros de textura e outros, uma lista de truques que foi evoluindo juntamente com as novas versões do DirectX e do OpenGL.

O grande problema era que os gráficos ficavam limitados e estes truques pré-programados, o que limitava muito o que se podia fazer, resultando em gráficos pouco realistas. Em um exemplo simplista, seria como se você precisasse se comunicar usando um vocabulário de apenas 100 palavras. Para complicar, a lista dos truques era fixa, por isso era necessário desenvolver novas GPUs e atualizar as APIs sempre que novos efeitos eram introduzidos.

Essa especialização era necessária para que as GPUs da época pudessem oferecer um desempenho aceitável, mas não resultava em gráficos muito convincentes, como você pode notar rodando qualquer jogo antigo, anterior a 2005. O uso dos pipelines de renderização e das caixinhas de truques pré-programados continuou até a era DirectX 7 (incluindo jogos como o Medal of Honour e o Call of Duty 1 e GPUs como as GeForce 2):

m249e8926
Medal of Honour: um exemplo de jogo antigo, que ainda não utiliza shaders

As coisas começaram a mudar com o DirectX 8.0, que trouxe o suporte a shaders programáveis, permitindo a criação de pequenos programas que adicionam novos efeitos à lista de truques suportados pela GPU. O DirectX 8.0 possuía muitas limitações com relação aos shaders, a começar pelo limite de apenas 128 instruções, que restringia o uso a efeitos simples.

As coisas melhoraram com o DirectX 9.0 (lançado em 2002), que trouxe suporte ao Shader Model 2.0, que incorporou muitas melhorias. Entretanto, a primeira versão definitiva veio em 2004, com o DirectX 9.0c, que trouxe o suporte ao Shader Model 3, uma versão sensivelmente aprimorada, que continuou sendo usada pela maioria dos jogos até o final de 2009, resistindo aos avanços do DirectX 10.

Embora as versões do Shader Model sejam um recurso independente, elas são atreladas às versões do DirectX. Ao ler que uma determinada placa suporta o Shader Model 4.1, por exemplo, você pode presumir que se trata de uma placa compatível com o DirectX 10.1 e vice-versa:

DirectX 8.0: Shader Model 1
DirectX 9.0: Shader Model 2
DirectX 9.0c: Shader Model 3
DirectX 10: Shader Model 4
DirectX 10.1: Shader Model 4.1
DirectX 11: Shader Model 5.0

Embora pareçam ser apenas uma superficialidade, os shaders foram os grandes responsáveis pelo aumento na qualidade gráfica dos jogos de alguns anos para cá e se tornaram o recurso mais importante em qualquer GPU, assumindo o posto que anteriormente era do fill-rate.

452658f5
Call of Duty World at War, com o DirectX 9.0c e o shader Model 3

Até o shader model 3 (DirectX 9.0c), existiam dois tipos de shaders: os vertex shaders e os pixel shaders. Os vertex shaders trabalham na estrutura dos objetos 3D que compõe a imagem, adicionando efeitos e criando animações, como no caso da grama ou dos cabelos, por exemplo.

Os pixel shaders atuam na etapa de renderização da imagem, analisando a estrutura dos objetos, as fontes de luz, cores e outras variáveis e usando estas informações para criar efeitos de luz e sombra, realce de cores, reflexos e outros efeitos bastante realísticos, complementando o trabalho iniciado pelos vertex shaders. Esta é considerada a parte mais importante do trabalho, já que determina a qualidade das imagens que serão finalmente mostradas no monitor e é também a parte que consome mais processamento.

Em placas das geração DirectX 9 (GeForce 7xxx e Radeon X1xxx) eles são processados em unidades independentes da placa, as vertex shader units (ou vertex processors) e as pixel shader units (ou pixels processors), que são especializadas no processamento de cada um dos dois tipos.

O problema com essa arquitetura é que a proporção de vertex shaders e pixel shaders varia de acordo com a situação e também de acordo com o tipo de efeitos usados pela engine do jogo. Isso faz com que sempre ou as unidades de vertex shader ou as de pixel shader sejam subutilizadas, criando um gargalo. Visando reduzir o problema, os fabricantes optam frequentemente por utilizar um número maior de unidades de pixel shader (a GeForce 7800 possui 24 unidades de pixel shader, para apenas 8 unidades de vertex shader, por exemplo), mas essa também estava longe de ser uma solução ideal.

O Shader Model 4 (DirectX 10) introduziu um terceiro tipo de shader, os geometry shaders, destinados à criação de grupos de vértices, que permitem criar objetos usando quase que exclusivamente processamento da GPU, com pouca carga sobre o processador principal. A grande vantagem dos geometry shaders é a possibilidade de criar cenas com um volume muito maior de objetos diferentes, sem que a engine do jogo fique restrita aos recursos do processador principal.

Em vez de um exército de soldados idênticos (como no Rome Total War, por exemplo), você poderia ter um exército onde cada soldado tem uma fisionomia própria. O mesmo poderia ser aplicado a florestas, castelos e assim por diante. Tudo o que o projetista precisaria fazer seria escrever vários geometry shaders diferentes, criando variações dos objetos e fazer com que a placa os executasse em uma ordem específica, em vez de simplesmente repetir a renderização do mesmo objeto ou personagem, como era feito tradicionalmente:

6fb6ad9f
Um dos exércitos de clones do Rome Total War

Este é o tipo de truque que também pode ser feito via software (como no caso do Empire Total War, por exemplo, sucessor do Rome), mas nesse caso a carga sobre o processador principal é muito maior, o que faz com que ele se torne um limitante muito antes da GPU.

Em vez de adicionarem um terceiro tipo de unidade dedicada (aumentando ainda mais o índice de ociosidade), tanto a nVidia quanto a ATI (e mais tarde também a Intel) optaram por migrar para arquiteturas unificadas, onde os vertex processors e pixels processors são substituídos por stream processors, que são capazes de executar vertex shaders, pixel shaders ou geometry shaders conforme a demanda.

Basicamente, cada uma destas unidades age como um pequeno processador de cálculos de ponto flutuante independente, que pode ser programado para executar praticamente qualquer tipo de operação. Isso abriu as portas para o uso da GPU como processador auxiliar, convertendo vídeos e executando tarefas diversas, uma possibilidade que pode ser explorada com a ajuda do OpenCL, Brook+ e CUDA.

Embora o uso de unidades de shader unificadas não seja necessariamente um pré-requisito para o suporte ao DirectX 10, acabou coincidindo de todos os fabricantes adotarem a nova arquitetura, de forma que as duas coisas acabaram relacionadas.

Sobre o Autor

Redes Sociais:

Deixe seu comentário

X