Geralmente existem dois métodos para lidar com isso. Atualmente, eles são chamados de renderização direta e renderização diferida. Há uma variação desses dois que discutirei abaixo.
Encaminhar renderização
Renderize cada objeto uma vez para cada luz que o afetar. Isso inclui a luz ambiente. Você usa um modo de mesclagem aditiva ( glBlendFunc(GL_ONE, GL_ONE)
), para que as contribuições de cada luz sejam adicionadas uma à outra. Uma vez que a contribuição de diferentes luzes é aditiva, o buffer de quadros acaba obtendo o valor
Você pode obter o HDR renderizando em um buffer de moldura de ponto flutuante. Em seguida, você passa pela cena final para reduzir os valores de iluminação HDR para um intervalo visível; também seria aqui que você implementaria o bloom e outros pós-efeitos.
Um aprimoramento de desempenho comum para esta técnica (se a cena tiver muitos objetos) é usar um "pré-passe", no qual você renderiza todos os objetos sem desenhar nada no buffer de estrutura de cores (use glColorMask
para desativar a gravação de cores). Isso apenas preenche o buffer de profundidade. Dessa forma, se você renderizar um objeto que está atrás de outro, a GPU poderá pular rapidamente esses fragmentos. Ele ainda precisa executar o shader de vértice, mas pode pular os cálculos de shader de fragmento normalmente mais caros.
É mais simples de codificar e mais fácil de visualizar. E em alguns hardwares (principalmente GPUs móveis e incorporados), pode ser mais eficiente que a alternativa. Mas no hardware de ponta, a alternativa geralmente vence em cenas com muitas luzes.
Renderização adiada
A renderização adiada é um pouco mais complicada.
A equação de iluminação usada para calcular a luz de um ponto em uma superfície usa os seguintes parâmetros de superfície:
- Posição da superfície
- Normais de superfície
- Cor difusa da superfície
- Cor especular da superfície
- Brilho especular de superfície
- Possivelmente outros parâmetros de superfície (dependendo da complexidade da sua equação de iluminação)
Na renderização direta, esses parâmetros atingem a função de iluminação do shader de fragmento, passando diretamente do shader de vértice, sendo puxados de texturas (geralmente através de coordenadas de textura passadas do shader de vértice) ou gerados a partir de um pano inteiro no shader de fragmento, com base em outros parâmetros. A cor difusa pode ser calculada combinando uma cor por vértice com uma textura, combinando várias texturas, qualquer que seja.
Na renderização adiada, tornamos tudo isso explícito. Na primeira passagem, renderizamos todos os objetos. Mas nós não renderizamos cores . Em vez disso, renderizamos parâmetros de superfície . Portanto, cada pixel na tela possui um conjunto de parâmetros de superfície. Isso é feito via renderização para texturas fora da tela. Uma textura armazenaria a cor difusa como seu RGB e, possivelmente, o brilho especular como o alfa. Outra textura armazenaria a cor especular. Um terço armazenaria o normal. E assim por diante.
A posição geralmente não é armazenada. Em vez disso, é reconstituído na segunda passagem pela matemática, que é muito complexa para entrar aqui. Basta dizer que usamos o buffer de profundidade e a posição do fragmento do espaço da tela como entrada para descobrir a posição do espaço da câmera do ponto em uma superfície.
Portanto, agora que essas texturas contêm essencialmente todas as informações de superfície para cada pixel visível na cena, começamos a renderizar quads em tela cheia. Cada luz recebe uma renderização quádrupla em tela cheia. Amostramos as texturas dos parâmetros de superfície (e reconstituímos a posição) e, em seguida, apenas as usamos para calcular a contribuição dessa luz. Isso é adicionado (novamente glBlendFunc(GL_ONE, GL_ONE)
) à imagem. Continuamos fazendo isso até ficar sem luz.
HDR novamente é uma etapa pós-processo.
A maior desvantagem da renderização adiada é o antialiasing. Requer um pouco mais de trabalho para antialias corretamente.
A maior vantagem, se sua GPU tiver muita largura de banda de memória, é o desempenho. Nós renderizamos a geometria real apenas uma vez (ou 1 + 1 por luz que possui sombras, se estivermos fazendo um mapeamento de sombras). Nós não gastar todo o tempo em pixels ou geometria escondidos que não é visível depois disso. Todo o tempo gasto na iluminação é gasto em coisas que são realmente visíveis.
Se sua GPU não possui muita largura de banda de memória, o passe de luz pode realmente começar a doer. Não é divertido extrair de 3 a 5 texturas por pixel da tela.
Pré-passe leve
Essa é uma espécie de variação na renderização diferida que possui vantagens e desvantagens interessantes.
Assim como na renderização adiada, você processa seus parâmetros de superfície em um conjunto de buffers. No entanto, você abreviou os dados da superfície; os únicos dados de superfície com os quais você se preocupa nesse momento são o valor do buffer de profundidade (para reconstruir a posição), normal e o brilho especular.
Então, para cada luz, você calcula apenas os resultados da iluminação. Sem multiplicação com cores de superfície, nada. Apenas o ponto (N, L) e o termo especular, completamente sem as cores da superfície. Os termos especulares e difusos devem ser mantidos em buffers separados. Os termos especulares e difusos para cada luz são resumidos nos dois buffers.
Em seguida, você renderiza novamente a geometria, usando os cálculos totais de iluminação especular e difusa para fazer a combinação final com a cor da superfície, produzindo assim a refletância geral.
As vantagens aqui são que você recebe multisampling de volta (pelo menos, mais fácil do que com adiado). Você faz menos renderização por objeto do que a renderização direta. Mas o principal adiado que isso fornece é um momento mais fácil para ter diferentes equações de iluminação para diferentes superfícies.
Com a renderização adiada, você geralmente desenha a cena inteira com o mesmo sombreador por luz. Portanto, todo objeto deve usar os mesmos parâmetros de material. Com o pré-passe da luz, você pode atribuir a cada objeto um sombreador diferente, para que ele possa executar sozinho a etapa final da iluminação.
Isso não oferece tanta liberdade quanto o caso de renderização direta. Mas ainda é mais rápido se você tiver a largura de banda de textura disponível.