Este é o terceiro de uma série de artigos que apresenta dicas de otimização para seus projetos Unity. Use-os como um guia para executar taxas de quadros mais altas com menos recursos. Depois de experimentar essas práticas recomendadas, não deixe de conferir as outras páginas da série:
- Configurando seu projeto Unity para obter um melhor desempenho
- Otimização do desempenho para gráficos de alta qualidade
- Programação avançada e arquitetura de código
- Desempenho aprimorado da física para uma jogabilidade suave
Entenda as limitações de seu hardware de destino e como criar o perfil da GPU para otimizar a renderização de seus gráficos. Experimente estas dicas e práticas recomendadas para reduzir a carga de trabalho da GPU.
Encontre muitas outras práticas recomendadas no livro eletrônico gratuito, Otimize o desempenho do seu jogo para console e PC.
Para desenhar um GameObject na tela, o Unity emite uma chamada de desenho para a API gráfica (por exemplo, OpenGL, Vulkan ou Direct3D). Cada chamada de sorteio consome muitos recursos.
As alterações de estado entre as draw calls, como a troca de materiais, podem causar sobrecarga de desempenho no lado da CPU. O hardware do PC e do console pode fazer muitas draw calls, mas a sobrecarga de cada chamada ainda é alta o suficiente para justificar a tentativa de reduzi-las. Em dispositivos móveis, a otimização de draw calls é fundamental. Você pode conseguir isso com o agrupamento de chamadas de desenho.
O agrupamento de chamadas de desenho minimiza essas alterações de estado e reduz o custo de CPU da renderização de objetos. O Unity pode combinar vários objetos em menos lotes usando várias técnicas com o Pipeline de Renderização de Alta Definição (HDRP) ou o Pipeline de Renderização Universal (URP):
- Dosagem de SRP: Ative o SRP Batcher no Pipeline Asset em Advanced. Ao usar shaders compatíveis, o SRP Batcher reduz a configuração da GPU entre as chamadas de desenho e torna os dados do material persistentes na memória da GPU. Isso também pode acelerar significativamente o tempo de renderização da CPU. Use menos variantes de shader com o mínimo de palavras-chave para melhorar o lote de SRP. Consulte a documentação do SRP para ver como seu projeto pode aproveitar esse fluxo de trabalho de renderização.
- Instanciamento de GPU: Se você tiver um grande número de objetos idênticos com a mesma malha e o mesmo material, use a instanciação da GPU para enviá-los em lote por meio do hardware gráfico. Para ativar a instanciação de GPU, selecione seu material na janela Projeto do Inspetor e, em seguida, marque Ativar instanciação.
- Loteamento estático: Para geometrias que não se movem, o Unity pode reduzir as chamadas de desenho para malhas que compartilham o mesmo material. Isso é mais eficiente do que o loteamento dinâmico, mas usa mais memória. Marque todas as malhas que nunca se movem como Batching Static no Inspector. O Unity combina todas as malhas estáticas em uma única malha grande no momento da compilação. A classe StaticBatchingUtility também permite que você crie esses lotes estáticos em tempo de execução (por exemplo, depois de gerar um nível processual de peças não móveis).
- Loteamento dinâmico: Para malhas pequenas, o Unity pode agrupar e transformar vértices na CPU e, em seguida, desenhá-los de uma só vez. Observe, no entanto, que você não deve usá-lo a menos que tenha malhas de baixo polígono suficientes (não mais do que 300 vértices cada e 900 atributos totais de vértice). Caso contrário, a ativação desse recurso desperdiçará o tempo da CPU procurando malhas pequenas para agrupar.
Você pode maximizar a formação de lotes de algumas maneiras simples:
- Use o menor número possível de texturas em uma cena. Menos texturas exigem menos materiais exclusivos, o que facilita a produção em lote. Além disso, use o Texture Atlases sempre que possível.
- Sempre prepare mapas de luz com o maior tamanho de atlas possível. Menos mapas de luz requerem menos alterações no estado do material, mas fique atento ao consumo de memória.
- Tenha cuidado para não instanciar materiais de forma não intencional. Acesso a Renderer.material em scripts duplica o material e retorna uma referência à nova cópia. Isso interrompe qualquer lote existente que já inclua o material. Se desejar acessar o material do objeto em lote, useRenderer.sharedMaterial em vez disso.
- Fique de olho no número de contagens de lotes estáticos e dinâmicos em relação à contagem total de chamadas de desenho usando o Profiler ou as estatísticas de renderização durante a otimização.
Para obter mais informações, consulte a documentação sobre lotes de chamadas do Draw.
Use o Frame Debugger para congelar a reprodução em um único quadro e percorrer o processo de construção de uma cena pelo Unity. Ao fazer isso, você pode identificar oportunidades de otimização. Procure GameObjects que renderizam desnecessariamente e desative-os para reduzir as chamadas de desenho por quadro.
Uma das principais vantagens do Frame Debugger é que você pode relacionar uma chamada de desenho a um GameObject específico na cena. Isso facilita a investigação de determinados problemas que talvez não sejam possíveis em depuradores de quadros externos.
Observação: O depurador de quadros não mostra chamadas de desenho individuais ou alterações de estado. Embora somente os Profilers de GPU nativos possam fornecer informações detalhadas sobre chamadas de desenho e tempo, o Frame Debugger ainda pode ser muito útil para depurar problemas de pipeline ou problemas de lote.
Leia a documentação do Frame Debugger para obter mais detalhes.
A taxa de preenchimento refere-se ao número de pixels que a GPU pode renderizar na tela a cada segundo. Se o seu jogo estiver limitado pela taxa de preenchimento, isso significa que ele está tentando desenhar mais pixels por quadro do que a GPU pode suportar.
Desenhar várias vezes sobre o mesmo pixel é chamado de overdraw. O overdraw diminui a taxa de preenchimento e custa mais largura de banda da memória. As causas mais comuns de saques a descoberto são:
- Sobreposição de geometria opaca ou transparente
- Shaders complexos, geralmente com várias passagens de renderização
- Partículas não otimizadas
- Sobreposição de elementos da interface do usuário
Embora você deva minimizar seu efeito, não existe uma abordagem única para resolver o problema do saque a descoberto. Experimente as seguintes técnicas para reduzir seu impacto.
Assim como em outras plataformas, a otimização nos consoles geralmente significa reduzir os lotes de draw call. Aqui estão algumas técnicas que podem ajudar:
- Utilização Ocultação de oclusão para remover objetos ocultos atrás de objetos em primeiro plano e reduzir o excesso de desenho. Esteja ciente de que isso requer processamento adicional da CPU, portanto, use o Profiler para verificar se a transferência do trabalho da GPU para a CPU é realmente benéfica.
- A instanciação de GPU também pode reduzir os lotes se você tiver muitos objetos que compartilham a mesma malha e o mesmo material. Limitar o número de modelos em sua cena pode melhorar o desempenho. Se isso for feito com arte, você poderá criar uma cena complexa sem que ela pareça repetitiva.
- O SRP Batcher pode reduzir a configuração da GPU entre as chamadas de desenho ao agrupar os comandos Bind e Draw GPU. Para se beneficiar desse lote de SRP, use quantos materiais forem necessários, mas restrinja-os a um pequeno número de shaders compatíveis (por exemplo, shaders Lit e Unlit em URP e HDRP).
A eliminação ocorre para cada câmera e pode ter um grande impacto no desempenho, especialmente quando várias câmeras são ativadas simultaneamente. O Unity usa dois tipos de seleção:
- O Frustum culling é realizado automaticamente em todas as câmeras. Ele garante que os GameObjects fora do Frustum de visualização não sejam renderizados para economizar no desempenho.
- Você pode definir manualmente as distâncias de seleção por camada por meio de Camera.layerCullDistances. Isso permite que você selecione GameObjects pequenos a uma distância menor do que o padrãofarClipPlane padrão. Para fazer isso, organize os GameObjects em camadas. Use a matriz .layerCullDistances para atribuir a cada uma das 32 camadas um valor menor que o farClipPlane (ou use 0 como padrão para o farClipPlane).
- O Unity faz o abate por camadas primeiro. Ele mantém apenas os GameObjects nas camadas que a câmera usa. Em seguida, o Frustum culling remove todos os GameObjects fora do Frustum da câmera.
- O Frustum culling é realizado como uma série de trabalhos que aproveitam os threads de trabalho disponíveis. Cada teste de seleção de camada é rápido (essencialmente apenas uma operação de máscara de bits). No entanto, esse custo ainda pode aumentar com um grande número de GameObjects. Se isso se tornar um problema para o seu projeto, talvez seja necessário implementar algum sistema para dividir o seu mundo em "setores" e desativar os setores que estão fora do Frustum da câmera para aliviar um pouco a pressão sobre o sistema de seleção de camadas/frustum do Unity.
- A seleção de oclusão remove todos os GameObjects da visualização do jogo se a câmera não puder vê-los. Os objetos ocultos atrás de outros objetos ainda podem ser renderizados e custar recursos. Use o Occlusion culling para descartá-los.
- Por exemplo, a renderização de uma sala é desnecessária se uma porta estiver fechada e a câmera não puder ver a sala. Se você ativar o Occlusion culling, ele poderá aumentar significativamente o desempenho, mas também usará mais espaço em disco, tempo de CPU e RAM. O Unity prepara os dados de oclusão durante a compilação e, em seguida, precisa carregá-los do disco para a RAM ao carregar uma cena.
- Enquanto a seleção de Frustum fora da visualização da câmera é automática, a seleção de Oclusão é um processo elaborado. Basta marcar seus objetos como Static, Occluders ou Occludees e, em seguida, fazer o bake por meio da caixa de diálogo Window > Rendering > Occlusion culling.
Consulte o tutorial Trabalhando com ocultação de o clusão para saber mais.
A configuração Allow Dynamic Resolution Camera permite dimensionar dinamicamente alvos de renderização individuais para reduzir a carga de trabalho na GPU. Nos casos em que a taxa de quadros do aplicativo diminui, você pode reduzir gradualmente a resolução para manter uma taxa de quadros consistente.
O Unity aciona esse dimensionamento se os dados de desempenho sugerirem que a taxa de quadros está prestes a diminuir como resultado do limite da GPU. Você também pode acionar preventivamente esse dimensionamento manualmente com um script. Isso é útil se você estiver se aproximando de uma seção do aplicativo com uso intenso de GPU. Se dimensionada gradualmente, a resolução dinâmica pode ser quase imperceptível.
Consulte a página de manual da resolução dinâmica para obter uma lista das plataformas compatíveis.
Às vezes, você precisa renderizar de mais de um ponto de vista durante o jogo. Por exemplo, é comum em um jogo de tiro em primeira pessoa (FPS) desenhar a arma do jogador e o ambiente separadamente com diferentes campos de visão (FOV). Isso evita que os objetos do primeiro plano pareçam distorcidos pelo FOV de grande angular do plano de fundo.
Você pode usar Empilhamento de câmera no URP para renderizar mais de uma visualização de câmera. No entanto, ainda é necessário fazer uma seleção e renderização significativas para cada câmera. Cada câmera incorre em alguma sobrecarga, esteja ela fazendo um trabalho significativo ou não.
Use apenas os componentes da câmera necessários para a renderização. Em plataformas móveis, cada câmera ativa pode usar até 1 ms de tempo de CPU, mesmo quando não renderiza nada.
À medida que os objetos se distanciam, o Level of Detail (LOD) pode ajustá-los ou alterá-los para usar malhas de resolução mais baixa com materiais e shaders mais simples. Isso melhora o desempenho da GPU.
Consulte o curso Trabalhando com LODs no Unity Learn para obter mais detalhes.
Faça o perfil de seus efeitos de pós-processamento para ver o custo deles na GPU. Alguns efeitos de tela cheia, como Bloom e Depth of Field, podem ser caros, portanto, vale a pena experimentar até encontrar o equilíbrio desejado entre qualidade visual e desempenho.
O pós-processamento não oscila muito no tempo de execução. Depois de determinar suas substituições de volume, atribua aos efeitos de pós-processamento uma parte estática de seu orçamento total de quadros.
A tesselação subdivide uma forma em versões menores dela mesma, o que pode melhorar os detalhes por meio do aumento da geometria. Embora existam exemplos em que o Tessellation faz mais sentido, como a casca de árvore na demonstração do Unity Book of the Dead, tente evitar o Tessellation em consoles, pois eles são caros para a GPU.
Leia mais sobre a demonstração do Book of the Dead aqui.
Assim como os sombreadores de tesselação, os sombreadores de geometria e vértice podem ser executados duas vezes por quadro na GPU - uma vez durante a pré-passagem de profundidade e outra vez durante a passagem de sombra.
Se você quiser gerar ou modificar dados de vértices na GPU, um sombreador de computação geralmente é a melhor opção, especialmente em comparação com um sombreador de geometria. Fazer o trabalho em um sombreador de computação significa que o sombreador de vértice que realmente renderiza a geometria pode operar muito mais rapidamente.
Saiba mais sobre os conceitos básicos do Shader.
Quando você envia uma chamada de desenho para a GPU, esse trabalho se divide em muitas frentes de onda que o Unity distribui pelos SIMDs disponíveis na GPU. Cada SIMD tem um número máximo de frentes de onda que podem ser executadas ao mesmo tempo.
A ocupação da frente de onda refere-se a quantas frentes de onda estão em uso no momento em relação ao máximo. Ele mede o quão bem você está usando o potencial da GPU. As ferramentas de criação de perfil para desenvolvimento de console mostram a ocupação da frente de onda em grande detalhe.
No exemplo acima do Book of the Dead do Unity, as frentes de onda do sombreador de vértices aparecem em verde e as frentes de onda do sombreador de pixels aparecem em azul. No gráfico inferior, muitas frentes de onda do sombreador de vértice aparecem sem muita atividade do sombreador de pixel. Isso mostra uma subutilização da GPU.
Se estiver fazendo muito trabalho com o sombreador de vértices que não resulta em pixels, isso pode indicar uma ineficiência. Embora a baixa ocupação da frente de onda não seja necessariamente ruim, é uma métrica que você pode usar para começar a otimizar seus shaders e verificar se há outros gargalos. Por exemplo, se você tiver um impasse devido a operações de memória ou computação, aumentar a ocupação pode melhorar o desempenho. Por outro lado, um número excessivo de frentes de onda em voo pode causar o travamento do cache e diminuir o desempenho.
Se houver intervalos em que a GPU esteja sendo subutilizada, você poderá aproveitar a computação assíncrona para mover o trabalho do sombreador de computação em paralelo para a fila de gráficos. Por exemplo, durante a geração do mapa de sombras, a GPU executa a renderização somente de profundidade. Muito pouco trabalho do sombreador de pixels ocorre nesse ponto e muitas frentes de onda permanecem desocupadas.
Se você puder sincronizar algum trabalho do sombreador de computação com a renderização somente de profundidade, isso fará um melhor uso geral da GPU. As frentes de onda não utilizadas podem ajudar com o Screen Space Ambient Occlusion (SSAO) ou qualquer tarefa que complemente o trabalho atual.
Assista a esta sessão sobre Otimização do desempenho para consoles de ponta da Unite.
Um dos nossos guias mais abrangentes reúne mais de 80 dicas práticas sobre como otimizar seus jogos para PC e console. Criadas por nossos engenheiros especializados em Success e Accelerate Solutions, essas dicas detalhadas o ajudarão a aproveitar ao máximo o Unity e a aumentar o desempenho do seu jogo.