O sombreamento adiado é apenas uma técnica para "adiar" a operação real de sombreamento para estágios posteriores; isso pode ser ótimo para reduzir o número de passes necessários (por exemplo) para renderizar 10 luzes que precisam de 10 passes. Meu argumento é que, independentemente da técnica de renderização que você está usando, há certas otimizações de renderização possíveis que reduzem o número de objetos (vértices, normais etc.) que seu pipeline de renderização precisa processar.
Não há um padrão de fato para otimizações de renderização, mas várias técnicas que podem ser usadas de forma intercambiável ou em conjunto para atingir determinadas características de desempenho. O uso de cada técnica depende muito da natureza da cena que está sendo renderizada.
A renderização adiada tenta resolver o problema quando o número de luzes aumenta, o que na renderização para frente pode fazer o número de passes explodir.
Essas técnicas não otimizam diretamente a parte de sombreamento adiado, mas, de acordo com sua descrição, a parte de sombreamento adiado NÃO é seu problema. Seu problema, porém, é que você está enviando a cena inteira para o pipeline de renderização. Portanto, seu mecanismo precisa processar (por exemplo, todos os 100 milhões de vértices) em sua cena apenas para poder enviar o resultado ao buffer g, enquanto a maioria desses 100 milhões de vértices pode ser descartada trivialmente e não enviada ao o vértice do pré-processo e os fragmentos passam.
No caso de um renderizador direto, o N vértice será processado pelo estágio de vértice como um total vertex count*lights count
e pelo estágio de fragmento como um total fragments count*number Lights
, o sombreamento adiado reduz efetivamente isso apenas vertex count
para o estágio de vértice e fragments count
a contagem de fragmentos, antes de resolver o problema. sombreamento real. Mas ainda assim N pode ser muito difícil de processar, especialmente quando a maioria deles pode ser descartada trivialmente.
Isso torna o abate mais eficaz no caso de renderização direta / passes múltiplos. Mas lembre-se de que a maioria dos mecanismos usará uma abordagem de renderização dupla, porque o sombreamento diferido por si só não pode resolver objetos transparentes, isso faz com que o uso dessas otimizações seja obrigatório. Não conheço nenhum mecanismo comercial que não faça todos eles.
Frustum Culling
Somente os objetos incluídos total ou parcialmente no perfil da exibição precisam ser enviados ao pipeline de renderização. Esse é o conceito básico de seleção de frustum, infelizmente, verificar se uma malha está dentro / fora da vista frustum pode ser uma operação cara; portanto, os projetistas de motores usam um volume limite aproximado, como uma caixa delimitadora ou esfera delimitadora de eixo (AABB) ou esfera delimitadora , mesmo que isso não seja tão preciso quanto usar a malha real, a diferença de precisão não vale a pena verificar com a malha real.
Mesmo com volumes delimitadores, você realmente não precisa verificar cada um deles; em alternativa, você pode construir uma hierarquia de volumes delimitadores para realizar uma seleção anterior, pois isso depende muito da complexidade da cena.
Essa é uma técnica boa e simples para um mecanismo menor e é quase usada em todos os mecanismos que já usei. Eu recomendo o uso de uma verificação de volume / volume limite "normal" sem hierarquias se o seu mecanismo não exigir a renderização de cenas muito complexas.
Seleção da face traseira
Esta é uma obrigação, por que desenhar rostos que não serão visíveis de qualquer maneira? As APIs de renderização fornecem uma interface para ativar / desativar o descarte da face traseira. A menos que você tenha um forte motivo para não ativá-lo, como alguns dos aplicativos CAD que precisam chamar a atenção em determinadas circunstâncias, isso é algo que deve ser feito.
Seleção de Oclusão
Usando o buffer Z, você pode resolver a determinação da visibilidade. Mas o problema é que o buffer Z nem sempre é ótimo em termos de desempenho, já que o buffer Z só pode ser resolvido em estágios posteriores do pipeline, os objetos ocluídos devem ser rasterizados e podem ser gravados no buffer Z e no Buffer de cores antes de falhar no teste Z.
A seleção por oclusão resolve isso, fazendo alguns testes iniciais para selecionar objetos ocluídos que estão no frustum de renderização. Uma implementação prática do descarte de oclusão é usar consultas baseadas em pontos e verificar se certos objetos são visíveis a partir de uma vista de ponto específica. Isso também pode ser usado para selecionar luzes que não contribuem para a imagem final, o que é especialmente útil em um renderizador de mecanismo adiado.
Um ótimo exemplo do mundo real dessa técnica está no GTA5, onde os arranha-céus estão estratigicamente posicionados no centro da cidade, não são apenas decorações, mas também funcionam como oclusores, efetivamente ocluindo o resto da cidade e impedindo que ela seja rasterizado.
Nível de detalhe
O nível de detalhe é uma técnica amplamente utilizada, a idéia é usar uma versão mais simples da malha quando a malha estiver menos contribuindo para a cena. existem duas implementações comuns; basta trocar a malha por uma mais simples quando não está mais contribuindo muito, a malha é selecionada com base em algum fator, como a distância e o número de pixels (área na tela) que a malha está ocupando. A outra versão dinamicamente mosaicos da malha, isso é amplamente utilizado na renderização do terreno.
E se tudo isso não funcionasse?
Bem, essa é uma boa pergunta.
A primeira coisa que você precisa fazer é criar um perfil do aplicativo usando um criador de perfil de gráficos e determinar onde está o gargalo. Lembre-se de que o gargalo pode mudar à medida que o conteúdo que está sendo renderizado é alterado. Os gargalos também podem fazer parte do código em execução na CPU, portanto você também precisa medir isso.
Depois disso, você precisa fazer algumas otimizações no gargalo, lembre-se de que não há resposta certa para isso e será diferente de hardware para outro.
Alguns truques comuns de otimização de GPU:
- Evite ramificações em shaders.
- Tente estruturas de vértices diferentes, por exemplo,
{VNT}
intercaladas na mesma matriz ou {V},{N},{T}
em matrizes diferentes.
- Desenhe a cena da frente para trás.
- Desative o buffer Z em alguns pontos, por exemplo, se uma imagem não precisar de teste Z.
- Use texturas compactadas.
Alguns truques comuns de otimização de CPU:
- Use funções embutidas para pequenas funções.
- Use SIMD (dados múltiplos de instrução única) quando possível.
- Evite cache de saltos de memória hostis.
- Use VBOs com a quantidade "correta" de dados. (dependendo do seu hardware), mas geralmente menos chamadas de empate são melhores.
Mas e se meu gargalo estivesse no sombreado adiado?
Nesse caso, como o sombreamento diferido está mais preocupado com as luzes, a parte mais óbvia é otimizar os cálculos reais do sombreamento. alguns dos pontos a serem observados:
- Renderize luzes que realmente afetam a imagem final. Em outras palavras, abasteça as luzes que não contribuem. Isso pode ser efetivamente implementado usando a seleção de oclusão mencionada anteriormente.
- Essa luz precisa do especular ou de alguns outros componentes? Talvez não.
- Essa luz lança sombra? Algumas luzes não precisam projetar sombras.
- Essa contribuição leve pode ser pré-calculada? Se não estiver em movimento, provavelmente alguns aspectos podem ser pré-calculados.