Não basta contar polígonos para otimizar modelos 3D

imagem

Depois de entender o básico do processo de renderização de malha, você pode aplicar várias técnicas para otimizar a velocidade de renderização.

1. Introdução


Quantos polígonos posso usar? Essa é uma pergunta muito comum que os artistas fazem ao criar modelos para renderização em tempo real. É difícil responder a essa pergunta, porque não se trata apenas de números.

Comecei minha carreira como artista 3D na era do primeiro PlayStation e depois me tornei programador de gráficos. Gostaria de ler este artigo antes de começar a criar modelos 3D para jogos. Os fundamentos fundamentais considerados nele são úteis para muitos artistas. Embora a maioria das informações neste artigo não afete significativamente a produtividade do seu trabalho diário, ela fornecerá um entendimento básico de como a unidade de processamento gráfico (GPU) processa as malhas que você cria.

A velocidade de sua renderização geralmente depende do número de polígonos na malha. No entanto, embora o número de polígonos geralmente se correlacione com a taxa de quadros por segundo (FPS), você pode descobrir que, mesmo depois de reduzir o número de polígonos, a malha ainda é renderizada lentamente. Porém, ao entender como as malhas são renderizadas em geral, você pode aplicar um conjunto de técnicas para aumentar a velocidade de renderização.

Como os dados poligonais são apresentados


Para entender como a GPU desenha polígonos, primeiro você precisa considerar a estrutura de dados usada para descrever os polígonos. Um polígono consiste em um conjunto de pontos chamados vértices e links. Os vértices geralmente são armazenados como matrizes de valores, por exemplo, como na Figura 1.


Figura 1. Uma matriz de valores simples de polígono.

Nesse caso, quatro vértices em três dimensões (x, ye z) nos fornecem 12 valores. Para criar polígonos, a segunda matriz de valores descreve os próprios vértices, como mostra a Figura 2.


Figura 2. Uma matriz de links para os vértices.

Esses vértices conectados juntos formam dois polígonos. Observe que dois triângulos, cada um com três ângulos, podem ser descritos por quatro vértices, porque os vértices 1 e 2 são usados ​​nos dois triângulos. Para que a GPU processe esses dados, supõe-se que cada polígono seja triangular. As GPUs esperam que você trabalhe com triângulos porque elas foram projetadas especificamente para desenhá-las. Se você precisar desenhar polígonos com um número diferente de vértices, precisará de um aplicativo que os divida em triângulos antes de renderizar para a GPU. Por exemplo, se você criar um cubo de seis polígonos, cada um com quatro lados, isso não será mais eficaz do que criar um cubo de 12 polígonos consistindo em três lados; são esses triângulos que a GPU desenha. Lembre-se da regra: você precisa contar não polígonos, mas triângulos.

Os dados do vértice usados ​​no exemplo anterior são tridimensionais, mas isso não é necessário. Duas dimensões podem ser suficientes para você, mas muitas vezes você precisa armazenar outros dados, por exemplo, coordenadas UV para texturas e normal para iluminação.

Desenho de polígono


Ao renderizar um polígono, a GPU primeiro determina onde desenhar o polígono. Para fazer isso, ele calcula a posição na tela onde os três vértices devem estar. Essa operação é chamada de transformação. Esses cálculos na GPU são executados por um pequeno programa chamado shader de vértice.

O sombreador de vértice geralmente executa outros tipos de operações, como processar animações. Depois de calcular as posições dos três vértices do polígono, a GPU calcula quais pixels estão nesse triângulo e começa a preencher esses pixels com outro pequeno programa chamado "fragment shader" (fragment shader). Um shader de fragmento geralmente é executado uma vez por pixel. No entanto, em alguns casos raros, ele pode ser executado várias vezes por pixel, por exemplo, para melhorar a suavização de serrilhado. Os shaders de fragmentos são freqüentemente chamados de shaders de pixel, porque na maioria dos casos os fragmentos correspondem a pixels (veja a Figura 3).


Figura 3. Um polígono desenhado na tela.

A Figura 4 mostra a sequência de ações executadas pela GPU ao renderizar o polígono.


Figura 4. A ordem da GPU que processa o polígono.

Se você dividir o triângulo em dois e desenhar os dois triângulos (veja a Figura 5), ​​o procedimento corresponderá à Figura 6.


Figura 5. Divisão do polígono em dois.


Figura 6. O procedimento da GPU desenhando dois polígonos.

Nesse caso, são necessárias duas vezes mais transformações e preparações, mas como o número de pixels permanece o mesmo, a operação não precisa rasterizar pixels adicionais. Isso mostra que dobrar o número de polígonos não necessariamente dobra o tempo de renderização.

Usando cache de vértice


Se você observar os dois polígonos do exemplo anterior, poderá ver que eles têm dois vértices comuns. Pode-se supor que esses vértices terão que ser calculados duas vezes, mas um mecanismo chamado cache de vértices permite reutilizar os resultados do cálculo. Os resultados dos cálculos do vertex shader para reutilização são armazenados no cache - uma pequena área de memória contendo os últimos vértices. O procedimento para desenhar dois polígonos usando o cache de vértices é mostrado na Figura 7.


Figura 7. Desenhando dois polígonos usando o cache de vértices.

Graças ao cache de vértices, você pode desenhar dois polígonos quase tão rápido quanto um, se eles tiverem vértices comuns.

Lidamos com os parâmetros dos vértices


Para que o vértice seja reutilizável, ele deve ser inalterado a cada uso. Obviamente, a posição deve permanecer a mesma, mas outros parâmetros também não devem mudar. Os parâmetros passados ​​para o topo dependem do mecanismo usado. Aqui estão dois parâmetros comuns:

  • Coordenadas de textura
  • Normal

Quando UV é aplicado a um objeto 3D, qualquer costura criada significa que os vértices ao longo da costura não podem ser compartilhados. Portanto, no caso geral, as costuras devem ser evitadas (veja a Figura 8).


Figura 8. Textura da sutura por UV.

Para uma iluminação adequada da superfície, cada vértice geralmente armazena um normal - um vetor direcionado da superfície. Devido ao fato de que todos os polígonos com um vértice comum são definidos por um normal, sua forma parece suave. Isso é chamado de sombreamento suave. Se cada triângulo tiver suas próprias normais, as bordas entre os polígonos serão pronunciadas e a superfície parecerá plana. Portanto, isso é chamado de sombreamento simples. A Figura 9 mostra duas malhas idênticas, uma com sombreamento suave e a outra com sombreamento plano.


Figura 9. Comparação de sombreamento suave com plano.

Essa geometria sombreada suave consiste em 18 triângulos e possui 16 vértices comuns. O sombreamento simples de 18 triângulos requer 54 (18 x 3) vértices, porque nenhum dos vértices é compartilhado. Mesmo que duas malhas tenham o mesmo número de polígonos, sua velocidade de renderização ainda será diferente.

Importância da forma


As GPUs trabalham rápido principalmente porque podem executar muitas operações em paralelo. Os materiais de marketing de GPU geralmente se concentram no número de pipelines que determinam quantas GPUs podem executar ao mesmo tempo. Quando a GPU desenha o polígono, ele fornece a tarefa de muitos pipelines para preencher os quadrados de pixels. Geralmente, esse é um quadrado de oito por oito pixels. A GPU continua fazendo isso até que todos os pixels estejam cheios. Obviamente, os triângulos não são quadrados, então alguns pixels do quadrado estarão dentro do triângulo e outros fora. O equipamento funciona com todos os pixels em um quadrado, mesmo aqueles que estão fora do triângulo. Depois de calcular todos os vértices no quadrado, o equipamento descarta os pixels fora do triângulo.

A Figura 10 mostra um triângulo, que requer três quadrados (ladrilhos) para desenhar. A maioria dos pixels calculados (ciano) é usada, e os mostrados em vermelho vão além dos limites do triângulo e serão descartados.


Figura 10. Três peças para desenhar um triângulo.

O polígono na Figura 11 com exatamente o mesmo número de pixels, mas esticado, requer mais blocos para preencher; A maioria dos resultados em cada bloco (área vermelha) será descartada.


Figura 11. Preenchendo blocos em uma imagem esticada.

O número de pixels renderizados é apenas um dos fatores. A forma do polígono também é importante. Para aumentar a eficiência, tente evitar polígonos longos e estreitos e dê preferência a triângulos com comprimentos aproximadamente iguais de lados, cujos ângulos são próximos a 60 graus. As duas superfícies planas da Figura 12 são trianguladas de duas maneiras diferentes, mas têm a mesma aparência quando renderizadas.


Figura 12. Superfícies trianguladas de duas maneiras diferentes.

Eles têm exatamente o mesmo número de polígonos e pixels, mas como a superfície da esquerda possui polígonos mais longos e mais estreitos que a direita, sua renderização será mais lenta.

Redesenhar


Para desenhar uma estrela de seis pontas, você pode criar uma malha de 10 polígonos ou desenhar a mesma forma a partir de apenas dois polígonos, como mostra a Figura 13.


Figura 13. Duas maneiras diferentes de renderizar uma estrela de seis pontas.

Você pode decidir que é mais rápido desenhar dois polígonos que 10. No entanto, nesse caso, isso provavelmente está incorreto, porque os pixels no centro da estrela serão desenhados duas vezes. Esse fenômeno é chamado de overdraw. Em essência, isso significa que os pixels são redesenhados mais de uma vez. O redesenho ocorre naturalmente durante todo o processo de renderização. Por exemplo, se um caractere estiver parcialmente oculto por uma coluna, ele será desenhado na íntegra, apesar de a coluna sobrepor parte do caractere. Alguns mecanismos usam algoritmos complexos para evitar a renderização de objetos invisíveis na imagem final, mas essa é uma tarefa difícil. A CPU geralmente é mais difícil de descobrir o que não precisa ser renderizado do que a GPU para desenhá-lo.

Como artista, você deve aceitar o fato de que não pode se livrar da repintura, mas é uma boa prática remover superfícies que não podem ser vistas. Se você estiver colaborando com uma equipe de desenvolvimento, peça para adicionar um modo de depuração ao mecanismo de jogo, no qual tudo se torna transparente. Isso facilitará a localização de polígonos ocultos que podem ser excluídos.

Implementando uma gaveta no chão


A Figura 14 mostra uma cena simples: uma caixa em pé no chão. O piso consiste em apenas dois triângulos e a caixa consiste em 10 triângulos. O redesenho nesta cena é mostrado em vermelho.


Figura 14. Uma gaveta de pé no chão.

Nesse caso, a GPU desenhará parte do piso com uma gaveta, apesar de não ser visível. Se, em vez disso, criamos um buraco no chão embaixo da caixa, teríamos recebido mais polígonos, mas muito menos redesenhados, como pode ser visto na Figura 15.


Figura 15. Um orifício sob a gaveta para evitar redesenho.

Nesses casos, tudo depende da sua escolha. Às vezes, vale a pena reduzir o número de polígonos, obtendo um redesenho em troca. Em outras situações, vale a pena adicionar polígonos para evitar redesenho. Outro exemplo: as duas figuras mostradas abaixo são as mesmas malhas de superfície aparente com pontos saindo dela. Na primeira malha (Figura 16), as pontas estão localizadas na superfície.


Figura 16. As dicas estão localizadas na superfície.

Na segunda malha da Figura 17, os furos são cortados na superfície sob as pontas para reduzir a quantidade de redesenho.


Figura 17. Os furos são cortados sob as pontas.

Nesse caso, muitos polígonos foram adicionados para fazer furos, alguns dos quais com formato estreito. Além disso, a superfície do redesenho, da qual nos livramos, não é muito grande, portanto, neste caso, essa técnica é ineficaz.

Imagine que você está modelando uma casa no chão. Para criá-lo, você pode deixar a terra inalterada ou fazer um buraco no chão embaixo da casa. Redesenhar é mais quando o buraco não é cortado debaixo da casa. No entanto, a escolha depende da geometria e do ponto de vista a partir do qual o jogador verá a casa. Se você desenhar terra sob a base da casa, isso criará uma grande quantidade de redesenho se você for para dentro da casa e olhar para baixo. No entanto, a diferença não será particularmente grande se você olhar para a casa de um avião. Nesse caso, é melhor ter um modo de depuração no mecanismo do jogo que torne as superfícies transparentes, para que você possa ver o que é desenhado sob as superfícies visíveis ao jogador.

Quando os buffers Z têm um conflito Z


Quando a GPU desenha dois polígonos sobrepostos, como determina qual deles está em cima do outro? Os primeiros pesquisadores de computação gráfica passaram muito tempo pesquisando esse problema. Ed Catmell (que mais tarde se tornou presidente da Pixar e Walt Disney Animation Studios) escreveu um artigo que descrevia dez abordagens diferentes para essa tarefa. Em uma parte do artigo, ele observa que a solução para esse problema será trivial se os computadores tiverem memória suficiente para armazenar um valor de profundidade por pixel. Nas décadas de 1970 e 1980, havia uma quantidade muito grande de memória. No entanto, hoje a maioria das GPUs funciona assim: esse sistema é chamado de buffer Z.

O buffer Z (também conhecido como buffer de profundidade) funciona da seguinte maneira: a cada pixel, seu valor de profundidade é associado. Quando o equipamento desenha um objeto, ele calcula a distância que um pixel é desenhado da câmera. Em seguida, verifica o valor da profundidade de um pixel existente. Se estiver mais distante da câmera que o novo pixel, o novo pixel será desenhado. Se um pixel existente estiver mais próximo da câmera do que um novo, o novo pixel não será desenhado. Essa abordagem resolve muitos problemas e funciona mesmo que os polígonos se cruzem.


Figura 18. Polígonos de interseção processados ​​pelo buffer de profundidade.

No entanto, o buffer Z não tem precisão infinita. Se duas superfícies estiverem quase à mesma distância da câmera, isso confunde a GPU e ela pode selecionar aleatoriamente uma das superfícies, como mostra a Figura 19.


Figura 19. Superfícies com a mesma profundidade apresentam problemas de exibição.

Isso se chama Z-fighting e parece muito incorreto. Freqüentemente, conflitos em Z pioram à medida que a superfície da câmera é afastada. Os desenvolvedores de mecanismos podem incorporar correções neles para amenizar esse problema, mas se um artista criar polígonos suficientemente próximos e se sobrepondo, um problema ainda poderá surgir. Outro exemplo é uma parede com um cartaz pendurado nela. O pôster está localizado quase na mesma profundidade da câmera que a parede atrás dela, então o risco de conflitos-Z é muito alto. A solução é fazer um buraco na parede sob o pôster. Isso também reduzirá a quantidade de redesenho.


Figura 20. Um exemplo de um conflito Z de polígonos sobrepostos.

Em casos extremos, um conflito Z pode ocorrer mesmo quando os objetos se tocam. A Figura 20 mostra a gaveta no chão e, como não fizemos um buraco no piso embaixo da gaveta, o buffer z pode ser confundido próximo à borda onde o piso encontra a gaveta.

Usando chamadas de desenho


As GPUs tornaram-se extremamente rápidas - tão rápidas que as CPUs podem não acompanhá-las. Como as GPUs são projetadas para executar uma tarefa, é muito mais fácil começar a trabalhar rapidamente. Os gráficos estão inerentemente relacionados ao cálculo de vários pixels, para que você possa criar equipamentos que calculem vários pixels em paralelo. No entanto, a GPU processa apenas o que é solicitado para desenhar a CPU. Se a CPU não conseguir "alimentar" rapidamente a GPU com dados, a placa de vídeo ficará inativa. Cada vez que a CPU ordena à GPU que desenhe algo, isso é chamado de chamada de empate. A chamada de desenho mais simples consiste em renderizar uma malha, incluindo um sombreador e um conjunto de texturas.

Imagine um processador lento que pode transferir 100 chamadas de desenho por quadro e uma GPU rápida que pode desenhar um milhão de polígonos por quadro. Nesse caso, uma chamada de empate ideal pode desenhar 10.000 polígonos. Se suas malhas consistirem em apenas 100 polígonos, a GPU poderá desenhar apenas 10.000 polígonos por quadro. Ou seja, 99% das vezes a GPU fica ociosa. Nesse caso, podemos aumentar facilmente o número de polígonos nas malhas sem perder nada.

Em que consiste a chamada de chamada e o custo dela, depende muito de arquiteturas e mecanismos específicos. Alguns mecanismos podem combinar várias malhas em uma chamada de empate (realizar lote, lote), mas todas as malhas precisarão ter o mesmo sombreador ou outras restrições. Novas APIs como Vulkan e DirectX 12 foram projetadas especificamente para solucionar esse problema, otimizando a maneira como o programa se comunica com o driver gráfico, aumentando assim o número de chamadas de desenho que podem ser transferidas em um único quadro.

Se sua equipe estiver criando seu próprio mecanismo, pergunte aos desenvolvedores do mecanismo quais limitações as chamadas de chamadas têm. Se você usar um mecanismo pronto como Unreal ou Unity, execute benchmarks de desempenho para determinar os limites dos recursos do mecanismo. Você pode achar que pode aumentar o número de polígonos sem causar uma diminuição na velocidade.

Conclusão


Espero que este artigo seja uma boa introdução para ajudá-lo a entender os vários aspectos do desempenho da renderização. Nas GPUs de diferentes fabricantes, tudo é implementado um pouco à sua maneira. Existem muitas reservas e condições especiais relacionadas a mecanismos e plataformas de hardware específicos. Sempre mantenha um diálogo aberto com os programadores de renderização para usar suas recomendações em seu projeto.

Sobre o autor


Eskil Steenberg é um desenvolvedor independente de jogos e ferramentas, e trabalha como consultor e em projetos independentes. Todas as capturas de tela foram tiradas em projetos ativos usando ferramentas desenvolvidas pela Esquil. Você pode saber mais sobre o trabalho dele no site da Quel Solaar e em sua conta do Twitter @quelsolaar.

Source: https://habr.com/ru/post/pt433186/


All Articles