Recomendações de desempenho para o UnityPerformance recommendations for Unity

Este artigo se baseia na discussão descrita em Recomendações de desempenho para realidade misturada, mas se concentra em aprendizados específicos do ambiente do mecanismo do Unity.This article builds on the discussion outlined in performance recommendations for mixed reality but focuses on learnings specific to the Unity engine environment.

A primeira etapa mais importante ao otimizar o desempenho de aplicativos de realidade misturada no Unity é verificar se você está usando as configurações de ambiente recomendadas para o Unity.The most important first step when optimizing performance of mixed reality apps in Unity is to be sure you are using the recommended environment settings for Unity. Esse artigo traz um conteúdo com algumas das configurações de cena mais importantes para a criação de aplicativos do Mixed Reality de alto desempenho.That article contains content with some of the most important scene configurations for building performant Mixed Reality apps. Algumas dessas configurações recomendadas também são realçadas abaixo.Some of these recommended settings are highlighted below, as well.

Como criar um perfil com o UnityHow to profile with Unity

O Unity fornece o Unity Profiler interno, que é um ótimo recurso para reunir informações de desempenho úteis para seu aplicativo específico.Unity provides the Unity Profiler built-in, which is a great resource to gather valuable performance insights for your particular app. Embora seja possível executar o criador de perfil no editor, essas métricas não representam o ambiente de runtime verdadeiro e, portanto, os resultados dessa tarefa devem ser usados com cautela.Although one can run the profiler in-editor, these metrics do not represent the true runtime environment and thus, results from this should be used cautiously. É recomendável criar o perfil do aplicativo remotamente durante a execução no dispositivo para obter insights mais precisos e práticos.It is recommended to remotely profile your application while running on device for most accurate and actionable insights. Além disso, o Depurador de Quadros do Unity também é uma ferramenta de insights muito avançada a ser utilizada.Further, Unity's Frame Debugger is also a very powerful and insight tool to utilize.

O Unity fornece uma excelente documentação para:Unity provides great documentation for:

  1. Como conectar o Unity Profiler aos aplicativos UWP remotamenteHow to connect the Unity profiler to UWP applications remotely
  2. Como diagnosticar problemas de desempenho com o Unity Profiler de maneira eficazHow to effectively diagnose performance problems with the Unity Profiler

Observação

Com o Unity Profiler conectado e após a adição do criador de perfil de GPU (confira Adicionar Criador de Perfil no canto superior direito), é possível ver quanto tempo está sendo gasto na CPU e na GPU, respectivamente, no meio do criador de perfil.With the Unity Profiler connected and after adding the GPU profiler (see Add Profiler in top right corner), one can see how much time is being spent on the CPU & GPU respectively in the middle of the profiler. Isso permite que o desenvolvedor obtenha uma aproximação rápida caso o aplicativo esteja limitado pela CPU ou pela GPU.This allows the developer to get a quick approximation if their application is CPU or GPU bounded.

CPU vs GPU do Unity

Recomendações de desempenho da CPUCPU performance recommendations

O conteúdo abaixo aborda mais práticas de desempenho aprofundadas, especialmente direcionadas ao desenvolvimento em C# e Unity.The content below covers more in-depth performance practices, especially targeted for Unity & C# development.

Referências de cacheCache references

É uma melhor prática armazenar em cache as referências a todos os componentes relevantes e GameObjects na inicialização.It is best practice to cache references to all relevant components and GameObjects at initialization. Isso porque as chamadas de função repetitivas como GetComponent<T>() são significativamente mais caras em relação ao custo de memória necessário para armazenar um ponteiro.This is because repeating function calls such as GetComponent<T>() are significantly more expensive relative to the memory cost to store a pointer. Isso também se aplica ao Camera.main usado com muita frequência.This also applies to to the very regularly used Camera.main. Na verdade, o Camera.main usa apenas FindGameObjectsWithTag() abaixo dele, que pesquisa com alto custo o grafo de cena em busca de um objeto da câmera com a marca "MainCamera" .Camera.main actually just uses FindGameObjectsWithTag() underneath, which expensively searches your scene graph for a camera object with the "MainCamera" tag.

using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour
{
    private Camera cam;
    private CustomComponent comp;

    void Start() 
    {
        cam = Camera.main;
        comp = GetComponent<CustomComponent>();
    }

    void Update()
    {
        // Good
        this.transform.position = cam.transform.position + cam.transform.forward * 10.0f;

        // Bad
        this.transform.position = Camera.main.transform.position + Camera.main.transform.forward * 10.0f;

        // Good
        comp.DoSomethingAwesome();

        // Bad
        GetComponent<CustomComponent>().DoSomethingAwesome();
    }
}

Observação

Evitar GetComponent(string)Avoid GetComponent(string)
Ao usar GetComponent() , há várias sobrecargas diferentes.When using GetComponent(), there are a handful of different overloads. É importante sempre usar as implementações baseadas em tipo e nunca a sobrecarga de pesquisa baseada em cadeia de caracteres.It is important to always use the Type-based implementations and never the string-based searching overload. A pesquisa por cadeia de caracteres na cena é significativamente mais cara do que a pesquisa por tipo.Searching by string in your scene is significantly more costly than searching by Type.
(Adequado) Component GetComponent(Type type)(Good) Component GetComponent(Type type)
(Adequado) T GetComponent<T>()(Good) T GetComponent<T>()
(Inadequado) Component GetComponent(string)>(Bad) Component GetComponent(string)>

Evitar operações carasAvoid expensive operations

  1. Evite o uso do LINQAvoid use of LINQ

    Embora o LINQ possa ser muito limpo e de fácil leitura e escrita, em geral, ele exige muito mais computação e, particularmente, mais alocação de memória do que a escrita manual do algoritmo.Although LINQ can be very clean and easy to read and write, it generally requires much more computation and particularly more memory allocation than writing the algorithm out manually.

    // Example Code
    using System.Linq;
    
    List<int> data = new List<int>();
    data.Any(x => x > 10);
    
    var result = from x in data
                 where x > 10
                 select x;
    
  2. APIs comuns do UnityCommon Unity APIs

    Algumas APIs do Unity, embora úteis, podem ser muito caras de serem executadas.Certain Unity APIs, although useful, can be very expensive to execute. A maioria deles envolve a pesquisa de todo o grafo de cena em busca de alguma lista correspondente de GameObjects.Most of these involve searching your entire scene graph for some matching list of GameObjects. Em geral, essas operações podem ser evitadas por meio do cache de referências ou da implementação de um componente de gerenciador para os GameObjects em questão, a fim de acompanhar as referências em runtime.These operations can generally be avoided by caching references or implementing a manager component for the GameObjects in question to track the references at runtime.

     GameObject.SendMessage()
     GameObject.BroadcastMessage()
     UnityEngine.Object.Find()
     UnityEngine.Object.FindWithTag()
     UnityEngine.Object.FindObjectOfType()
     UnityEngine.Object.FindObjectsOfType()
     UnityEngine.Object.FindGameObjectsWithTag()
     UnityEngine.Object.FindGameObjectsWithTag()
    

Observação

SendMessage() e BroadcastMessage() devem ser eliminados a qualquer custo.SendMessage() and BroadcastMessage() should be eliminated at all costs. Essas funções podem estar em uma ordem 1.000 vezes mais lenta do que as chamadas de função diretas.These functions can be on the order of 1000x slower than direct function calls.

  1. Cuidado com a conversão boxingBeware of boxing

    A conversão boxing é um conceito fundamental do runtime e da linguagem C#.Boxing is a core concept of the C# language and runtime. É o processo de encapsular variáveis de tipo de valor, como char, int, bool etc., em variáveis de tipo de referência.It is the process of wrapping value-typed variables such as char, int, bool, etc. into reference-typed variables. Quando é feita a conversão boxing de uma variável de tipo de valor, ela é encapsulada dentro de um System.Object que é armazenado no heap gerenciado.When a value-typed variable is "boxed", it is wrapped inside of a System.Object which is stored on the managed heap. Assim, a memória é alocada e, por fim, quando descartada, precisa ser processada pelo coletor de lixo.Thus, memory is allocated and eventually when disposed must be processed by the garbage collector. Essas alocações e essas desalocações geram um custo de desempenho e, em muitos cenários, são desnecessárias ou podem ser substituídas com facilidade por uma alternativa menos cara.These allocations and deallocations incur a performance cost and in many scenarios are unnecessary or can be easily replaced by a less expensive alternative.

    Uma das formas mais comuns de conversão boxing no desenvolvimento é o uso de tipos de valor que permitem valor nulo.One of the most common forms of boxing in development is the use of nullable value types. É comum querer poder retornar nulo para um tipo de valor em uma função, especialmente quando a operação pode falhar ao tentar obter o valor.It is common to want to be able to return null for a value type in a function, especially when the operation may fail trying to get the value. O possível problema dessa abordagem é que a alocação agora ocorre no heap e, consequentemente, precisa ser coletada como lixo mais tarde.The potential problem with this approach is that allocation now occurs on the heap and consequently needs to be garbage collected later.

    Exemplo de conversão boxing em C#Example of boxing in C#

    // boolean value type is boxed into object boxedMyVar on the heap
    bool myVar = true;
    object boxedMyVar = myVar;
    

    Exemplo de conversão boxing problemática por meio de tipos de valor que permitem valor nuloExample of problematic boxing via nullable value types

    Esse código demonstra uma classe de partícula fictícia que pode ser criada em um projeto do Unity.This code demonstrates a dummy particle class that one may create in a Unity project. Uma chamada a TryGetSpeed() causará a alocação de objeto no heap, que precisará ser coletado como lixo posteriormente.A call to TryGetSpeed() will cause object allocation on the heap which will need to be garbage collected at a later point in time. Esse exemplo é particularmente problemático, pois pode haver mais de mil ou muito mais partículas em uma cena, cada uma sendo solicitada pela velocidade atual.This example is particularly problematic as there may be 1000+ or many more particles in a scene, each being asked for their current speed. Assim, milhares de objetos serão alocados e, consequentemente, desalocados a cada quadro, o que reduzirá muito o desempenho.Thus, 1000's of objects would be allocated and consequently de-allocated every frame, which would greatly diminish performance. A reescrita da função para retornar um valor negativo, como -1, a fim de indicar uma falha evitará esse problema e manterá a memória na pilha.Re-writing the function to return a negative value such as -1 to indicate a failure would avoid this issue and keep memory on the stack.

        public class MyParticle
        {
            // Example of function returning nullable value type
            public int? TryGetSpeed()
            {
                // Returns current speed int value or null if fails
            }
        }
    

Repetição de caminhos de códigoRepeating code paths

Uma função de retorno de chamada do Unity repetida (ou seja,Any repeating Unity callback functions (i.e Update) executada muitas vezes por segundo e/ou quadro deve ser escrita com muito cuidado.Update) that are executed many times per second and/or frame should be written very carefully. Qualquer operação cara aqui terá um impacto enorme e consistente sobre o desempenho.Any expensive operations here will have huge and consistent impact on performance.

  1. Funções de retorno de chamada vazioEmpty callback functions

    Embora possa parecer não ter problema deixar o código abaixo em seu aplicativo, especialmente porque cada script do Unity é inicializado automaticamente com esse bloco de código, esses retornos de chamada vazios podem realmente ficar muito caros.Although the code below may seem innocent to leave in your application, especially since every Unity script auto-initializes with this code block, these empty callbacks can actually become very expensive. O Unity opera em um limite de código não gerenciado/gerenciado, entre o código do UnityEngine e o código do aplicativo.Unity operates back and forth over an unmanaged/managed code boundary, between UnityEngine code and your application code. A alternância de contexto nessa ponte é bem cara, mesmo que não haja nada a ser executado.Context switching over this bridge is fairly expensive, even if there is nothing to execute. Isso se torna especialmente problemático se o aplicativo tem centenas de GameObjects com componentes que tenham retornos de chamada repetitivos e vazios do Unity.This becomes especially problematic if your app has 100's of GameObjects with components that have empty repeating Unity callbacks.

    void Update()
    {
    }
    

Observação

Update() é a manifestação mais comum desse problema de desempenho, mas outros retornos de chamada repetitivos do Unity, como os seguintes, podem ser igualmente tão ruins, se não piores: FixedUpdate(), LateUpdate(), OnPostRender", OnPreRender(), OnRenderImage() etc.Update() is the most common manifestation of this performance issue but other repeating Unity callbacks, such as the following can be equally as bad, if not worse: FixedUpdate(), LateUpdate(), OnPostRender", OnPreRender(), OnRenderImage(), etc.

  1. Operações para favorecer a execução uma vez por quadroOperations to favor running once per frame

    As APIs do Unity a seguir são operações comuns para muitos aplicativos holográficos.The following Unity APIs are common operations for many Holographic Apps. Embora nem sempre seja possível, os resultados dessas funções podem ser computados com muita frequência, e os resultados são reutilizados no aplicativo para determinado quadro.Although not always possible, the results from these functions can very commonly be computed once and the results re-utilized across the application for a given frame.

    a) Em geral, é uma boa prática ter uma classe singleton dedicada ou um serviço para processar o foco Raycast na cena e, em seguida, reutilizar esse resultado em todos os outros componentes de cena, em vez de fazer operações Raycast repetidas e essencialmente idênticas por componente.a) Generally it is good practice to have a dedicated Singleton class or service to handle your gaze Raycast into the scene and then re-use this result in all other scene components, instead of making repeated and essentially identical Raycast operations by each component. É claro que alguns aplicativos podem exigir raycasts de diferentes origens ou em diferentes LayerMasks.Of course, some applications may require raycasts from different origins or against different LayerMasks.

     UnityEngine.Physics.Raycast()
     UnityEngine.Physics.RaycastAll()
    

    b) Evite operações GetComponent() em retornos de chamada repetitivos do Unity, como Update(), com o cache de referências em Start() ou Awake()b) Avoid GetComponent() operations in repeated Unity callbacks like Update() by caching references in Start() or Awake()

     UnityEngine.Object.GetComponent()
    

    c) É uma boa prática criar uma instância de todos os objetos, se possível, na inicialização e usar o pool de objetos para reciclar e reutilizar GameObjects por todo o runtime do aplicativoc) It is good practice to instantiate all objects, if possible, at initialization and use object pooling to recycle and re-use GameObjects throughout runtime of your application

     UnityEngine.Object.Instantiate()
    
  2. Evite interfaces e constructos virtuaisAvoid interfaces and virtual constructs

    A invocação de chamadas de função por meio de interfaces em comparação com objetos diretos ou chamada de funções virtuais muitas vezes pode ser muito mais custosa do que a utilização de constructos diretos ou chamadas de função diretas.Invoking function calls through interfaces vs direct objects or calling virtual functions can often times be much more expensive than utilizing direct constructs or direct function calls. Se a interface ou a função virtual for desnecessária, ela deverá ser removida.If the virtual function or interface is unnecessary, then it should be removed. No entanto, o impacto no desempenho dessas abordagens geralmente vale a pena se a utilização delas simplifica a colaboração de desenvolvimento, bem como a legibilidade e a manutenção do código.However, the performance hit for these approaches are generally worth the trade-off if utilizing them simplifies development collaboration, code readability, and code maintainability.

    Em geral, a recomendação é não marcar campos e funções como virtuais, a menos que haja uma expectativa bem definida de que esse membro precise ser substituído.Generally, the recommendation is to not mark fields and functions as virtual unless there is a clear expectation that this member needs to be overwritten. É preciso ser especialmente cuidadoso em relação aos caminhos de código de alta frequência que são chamados muitas vezes por quadro ou, até mesmo, uma vez por quadro, como um método UpdateUI().One should be especially careful around high-frequency code paths that are called many times per frame or even once per frame such as an UpdateUI() method.

  3. Evite transmitir structs por valorAvoid passing structs by value

    Ao contrário das classes, os structs são tipos de valor e, quando transmitidos diretamente para uma função, o conteúdo deles é copiado para uma instância recém-criada.Unlike classes, structs are value-types and when passed directly to a function, their contents are copied into a newly created instance. Essa cópia adiciona custo de CPU, bem como memória adicional na pilha.This copy adds CPU cost, as well as additional memory on the stack. Para structs pequenos, o efeito é geralmente muito mínimo e, portanto, aceitável.For small structs, the effect is usually very minimal and thus acceptable. No entanto, para as funções invocadas repetidamente a cada quadro, bem como as funções que usam structs grandes, se possível, modifique a definição de função para transmiti-la por referência.However, for functions repeatedly invoked every frame as well as functions taking large structs, if possible modify the function definition to pass by reference. Saiba mais aquiLearn more here

DiversosMiscellaneous

  1. FísicaPhysics

    r) Em geral, a maneira mais fácil de aprimorar a física é limitar o tempo gasto na física ou no número de iterações por segundo.a) Generally, the easiest way to improve physics is to limit the amount of time spent on Physics or the number of iterations per second. É claro que isso reduzirá a precisão da simulação.Of course, this will reduce simulation accuracy. Confira TimeManager no UnitySee TimeManager in Unity

    b) Os tipos de colisores no Unity têm características de desempenho amplamente diferentes.b) The type of colliders in Unity have widely different performance characteristics. A ordem abaixo lista os colisores com melhor desempenho até os colisores com pior desempenho, da esquerda para a direita.The order below lists the most performant colliders to least performant colliders from left to right. O mais importante é evitar os colisores de malha, que são consideravelmente mais caros do que os colisores primitivos.It is most important to avoid Mesh Colliders, which are substantially more expensive than the primitive colliders.

     Sphere < Capsule < Box <<< Mesh (Convex) < Mesh (non-Convex)
    

    Confira Melhores práticas de física do Unity para obter mais informaçõesSee Unity Physics Best Practices for more info

  2. AnimaçõesAnimations

    Desabilite as animações ociosas desabilitando o componente Animator (a desabilitação do objeto de jogo não terá o mesmo efeito).Disable idle animations by disabling the Animator component (disabling the game object won't have the same effect). Evite padrões de design em que um animador fica em um loop configurando um valor para o mesmo item.Avoid design patterns where an animator sits in a loop setting a value to the same thing. Há uma sobrecarga considerável nessa técnica, sem nenhum efeito no aplicativo.There is considerable overhead for this technique, with no effect on the application. Saiba mais aqui.Learn more here.

  3. Algoritmos complexosComplex algorithms

    Se o aplicativo estiver usando algoritmos complexos, como cinemática inversa, localização de caminho etc., procure encontrar uma abordagem mais simples ou ajustar as configurações relevantes para o desempenho deleIf your application is using complex algorithms such as inverse kinematics, path finding, etc, look to find a simpler approach or adjust relevant settings for their performance

Recomendações do desempenho de CPU para GPUCPU-to-GPU performance recommendations

Em geral, o desempenho de CPU para GPU se resume às chamadas de desenho enviadas à placa gráfica.Generally, CPU-to-GPU performance comes down to the draw calls submitted to the graphics card. Para aprimorar o desempenho, as chamadas de desenho precisam ser estrategicamente a) reduzidas ou b) reestruturadas para resultados ideais.To improve performance, draw calls need to be strategically a) reduced or b) restructured for optimal results. Como as próprias chamadas de desenho fazem uso intensivo de recursos, a redução delas diminuirá o trabalho geral necessário.Since draw calls themselves are resource-intensive, reducing them will reduce overall work required. Além disso, as alterações de estado entre chamadas de desenho exigem etapas caras de validação e conversão no driver gráfico e, portanto, a reestruturação das chamadas de desenho do aplicativo para limitar as alterações de estado (ou seja,Further, state changes between draw calls requires costly validation and translation steps in the graphics driver and thus, restructuring of your application's draw calls to limit state changes (i.e materiais diferentes etc.) podem aumentar o desempenho.different materials, etc) can boost performance.

O Unity conta com um ótimo artigo que fornece uma visão geral e se aprofunda no envio em lote de chamadas de desenho para a plataforma.Unity has a great article that gives an overview and dives into batching draw calls for their platform.

Renderização com uma instância de passagem únicaSingle pass instanced rendering

A renderização com uma instância de passagem única no Unity permite que as chamadas de desenho para cada olho sejam reduzidas a uma chamada de desenho com instância.Single Pass Instanced Rendering in Unity allows for draw calls for each eye to be reduced down to one instanced draw call. Devido à coerência de cache entre duas chamadas de desenho, também há alguma melhoria de desempenho na GPU.Due to cache coherency between two draw calls, there is also some performance improvement on the GPU as well.

Como habilitar esse recurso no seu projeto do UnityTo enable this feature in your Unity Project

  1. Abra Configurações de XR do Player (acesse Editar > Configurações do Projeto > Player > Configurações de XR)Open Player XR Settings (go to Edit > Project Settings > Player > XR Settings)
  2. Selecione Instância de Passagem Única no menu suspenso Método de Renderização de Estéreo (a caixa de seleção Realidade Virtual Compatível precisa estar marcada)Select Single Pass Instanced from the Stereo Rendering Method drop-down menu (Virtual Reality Supported checkbox must be checked)

Leia os artigos a seguir do Unity para obter detalhes dessa abordagem de renderização.Read the following articles from Unity for details with this rendering approach.

Observação

Um problema comum na renderização com uma instância de passagem única ocorre se os desenvolvedores já têm sombreadores personalizados existentes não escritos para a criação de instância.One common issue with Single Pass Instanced Rendering occurs if developers already have existing custom shaders not written for instancing. Depois de habilitar esse recurso, os desenvolvedores poderão perceber que alguns GameObjects são renderizados apenas em um olho.After enabling this feature, developers may notice some GameObjects only render in one eye. Isso ocorre porque os sombreadores personalizados associados não têm as propriedades apropriadas para a criação de instância.This is because the associated custom shaders do not have the appropriate properties for instancing.

Confira Renderização de estéreo de passagem única para o HoloLens do Unity para saber como resolver esse problemaSee Single Pass Stereo Rendering for HoloLens from Unity for how to address this problem

Envio em lote estáticoStatic batching

O Unity pode enviar em lote muitos objetos estáticos para reduzir chamadas de desenho para a GPU.Unity is able to batch many static objects to reduce draw calls to the GPU. O envio em lote estático funciona para a maioria dos objetos do renderizador no Unity que 1) compartilha o material e 2) é marcada como Estático (selecione um objeto no Unity e clique na caixa de seleção no canto superior direito do inspetor).Static Batching works for most Renderer objects in Unity that 1) share the same material and 2) are all marked as Static (Select an object in Unity and click the checkbox in the top right of the inspector). Os GameObjects marcados como Estáticos não podem ser movidos durante todo o runtime do aplicativo.GameObjects marked as Static cannot be moved throughout your application's runtime. Portanto, o envio em lote estático pode ser difícil de ser aproveitado no HoloLens, no qual praticamente todos os objetos precisam ser colocados, movidos, dimensionados etc. Para headsets imersivos, o envio em lote estático pode reduzir drasticamente as chamadas de desenho e, portanto, aprimorar o desempenho.Thus, static batching can be difficult to leverage on HoloLens where virtually every object needs to be placed, moved, scaled, etc. For immersive headsets, static batching can dramatically reduce draw calls and thus improve performance.

Leia Envio em lote estático em Envio em lote de chamadas de desenho no Unity para obter mais detalhes.Read Static Batching under Draw Call Batching in Unity for more details.

Envio em lote dinâmicoDynamic batching

Como é problemático marcar objetos como Estáticos para o desenvolvimento no HoloLens, o envio em lote dinâmico pode ser uma ótima ferramenta para compensar esse recurso.Since it is problematic to mark objects as Static for HoloLens development, dynamic batching can be a great tool to compensate for this lacking feature. É claro que isso também pode ser útil em headsets imersivos.Of course, it can also be useful on immersive headsets, as well. No entanto, o envio em lote dinâmico no Unity pode ser difícil de ser habilitado, porque os GameObjects precisam a) compartilhar o material e b) atender a uma lista longa de outros critérios.However, dynamic batching in Unity can be difficult to enable because GameObjects must a) share the same Material and b) meet a long list of other criteria.

Leia Envio em lote dinâmico em Envio em lote de chamadas de desenho no Unity para obter a lista completa.Read Dynamic Batching under Draw Call Batching in Unity for the full list. Normalmente, os GameObjects se tornam inválidos para serem enviados em lote dinamicamente, porque os dados de malha associados não podem ter mais de 300 vértices.Most commonly, GameObjects become invalid to be batched dynamically, because the associated mesh data can be no more than 300 vertices.

Outras técnicasOther techniques

O envio em lote só pode ocorrer se vários GameObjects podem compartilhar o material.Batching can only occur if multiple GameObjects are able to share the same material. Normalmente, isso será bloqueado pela necessidade de os GameObjects terem uma textura exclusiva para o respectivo material.Typically, this will be blocked by the need for GameObjects to have a unique texture for their respective Material. É comum combinar as texturas em uma textura grande, um método conhecido como atlas de textura.It is common to combine Textures into one big Texture, a method known as Texture Atlasing.

Além disso, geralmente é preferível combinar malhas em um só GameObject sempre que possível e razoável.Furthermore, it is generally preferable to combine meshes into one GameObject where possible and reasonable. Cada renderizador do Unity terá as chamadas de desenho associadas em vez de enviar uma malha combinada em um renderizador.Each Renderer in Unity will have its associated draw call(s) versus submitting a combined mesh under one Renderer.

Observação

A modificação das propriedades de Renderer.material em runtime criará uma cópia do material e, portanto, poderá interromper o envio em lote.Modifying properties of Renderer.material at runtime will create a copy of the Material and thus potentially break batching. Use Renderer.sharedMaterial para modificar as propriedades de material compartilhadas em GameObjects.Use Renderer.sharedMaterial to modify shared material properties across GameObjects.

Recomendações de desempenho da GPUGPU performance recommendations

Saiba mais sobre como otimizar a renderização de gráficos no UnityLearn more about optimizing graphics rendering in Unity

Otimizar o compartilhamento de buffer de profundidadeOptimize depth buffer sharing

Geralmente, é recomendável habilitar o Compartilhamento de buffer de profundidade nas Configurações de XR do Player para otimizar a estabilidade do holograma.It is generally recommended to enable Depth buffer sharing under Player XR Settings to optimize for hologram stability. No entanto, ao habilitar a reprojeção de etapa tardia baseada em profundidade com essa configuração, é recomendável selecionar o formato de profundidade de 16 bits em vez do formato de profundidade de 24 bits.When enabling depth-based late-stage reprojection with this setting however, it is recommended to select 16-bit depth format instead of 24-bit depth format. Os buffers de profundidade de 16 bits reduzem drasticamente a largura de banda (e, portanto, a energia) associada ao tráfego do buffer de profundidade.The 16-bit depth buffers will drastically reduce the bandwidth (and thus power) associated with depth buffer traffic. Isso pode ser um grande ganho na redução de energia e na melhoria do desempenho.This can be a big win both in power reduction and performance improvement. No entanto, há dois resultados negativos possíveis com o uso do formato de profundidade de 16 bits.However, there are two possible negative outcomes by using 16-bit depth format.

Luta zZ-Fighting

A fidelidade de intervalo de profundidade reduzida possibilita a ocorrência da luta z mais com 16 bits do que com 24 bits.The reduced depth range fidelity makes z-fighting more likely to occur with 16-bit than 24-bit. Para evitar esses artefatos, modifique os planos de clipes próximos/distantes da câmera do Unity para considerar a precisão mais baixa.To avoid these artifacts, modify the near/far clip planes of the Unity camera to account for the lower precision. Para aplicativos baseados no HoloLens, um plano de recorte distante de 50 m em vez dos 1.000 m padrão do Unity geralmente pode eliminar qualquer luta Z.For HoloLens-based applications, a far clip plane of 50m instead of the Unity default 1000m can generally eliminate any z-fighting.

Buffer de estêncil desabilitadoDisabled Stencil Buffer

Quando o Unity cria uma textura de renderização com profundidade de 16 bits, nenhum buffer de estêncil é criado.When Unity creates a Render Texture with 16-bit depth, there is no stencil buffer created. A seleção do formato de profundidade de 24 bits, conforme a documentação do Unity, criará um buffer z de 24 bits, bem como um [buffer de estêncil de 8 bits] (https://docs.unity3d.com/Manual/SL-Stencil.html) (se 32 bits for aplicável em um dispositivo, que geralmente é o caso, como o HoloLens).Selecting 24-bit depth format, per Unity documentation, will create a 24-bit z-buffer, as well as an [8-bit stencil buffer] (https://docs.unity3d.com/Manual/SL-Stencil.html) (if 32-bit is applicable on a device, which is generally the case such as HoloLens).

Evitar efeitos de tela inteiraAvoid full-screen effects

As técnicas que operam na tela inteira podem ser bastante custosas, já que a ordem de magnitude é milhões de operações a cada quadro.Techniques that operate on the full screen can be quite expensive since their order of magnitude is millions of operations every frame. Portanto, é recomendável evitar efeitos de pós-processamento como suavização, gestos de abrir a mão, entre outros.Thus, it is recommended to avoid post-processing effects such as anti-aliasing, bloom, and more.

Configurações de iluminação ideaisOptimal lighting settings

A Iluminação Global em Tempo Real do Unity pode fornecer resultados visuais excepcionais, mas envolve cálculos de iluminação bastante custosos.Real-time Global Illumination in Unity can provide outstanding visual results but involves quite expensive lighting calculations. É recomendável desabilitar a Iluminação Global em Tempo Real em cada arquivo de cena do Unity por meio de Janela > Renderização > Configurações de Iluminação > Desmarcar Iluminação Global em Tempo Real.It is recommended to disable Realtime Global Illumination for every Unity scene file via Window > Rendering > Lighting Settings > Uncheck Real-time Global Illumination.

Além disso, é recomendável desabilitar toda a conversão de sombra, pois ela também adiciona passagens de GPU custosas a uma cena do Unity.Furthermore, it is recommended to disable all shadow casting as these also add expensive GPU passes onto a Unity scene. As sombras podem ser desabilitadas conforme a luz, mas também podem ser controladas de maneira holística por meio das configurações de Qualidade.Shadows can be disable per light but can also be controlled holistically via Quality settings.

Editar > Configurações do Projeto e, em seguida, selecione a categoria Qualidade > selecione Baixa Qualidade para a plataforma UWP.Edit > Project Settings, then select the Quality category > Select Low Quality for the UWP Platform. Também é possível definir apenas a propriedade Shadows como Desabilitar Sombras.One can also just set the Shadows property to Disable Shadows.

Recomendamos usar a iluminação integrada com seus modelos no Unity.It is recommended that you use baked lighting with your models in Unity.

Reduzir a contagem de polígonosReduce poly count

A contagem de polígonos geralmente é reduzida por meio dos métodos a seguirPolygon count is usually reduced by either

  1. Remoção de objetos de uma cenaRemoving objects from a scene
  2. Eliminação de ativos, que reduz o número de polígonos para determinada malhaAsset decimation which reduces the number of polygons for a given mesh
  3. Implementação de um sistema de LOD (Nível de Detalhe) no aplicativo que renderiza objetos distantes com a versão de polígono mais baixo da mesma geometriaImplementing a Level of Detail (LOD) System into your application which renders far away objects with lower-polygon version of the same geometry

Noções básicas sobre os sombreadores do UnityUnderstanding shaders in Unity

Uma aproximação fácil para comparar os sombreadores em desempenho é identificar o número médio de operações que cada um executa em runtime.An easy approximation to compare shaders in performance is to identify the average number of operations each executes at runtime. Isso pode ser feito com facilidade no Unity.This can be done easily in Unity.

  1. Selecione o ativo do sombreador ou um material e, no canto superior direito da janela do inspetor, selecione o ícone de engrenagem seguido de "Selecionar Sombreador"Select your shader asset or select a material, then in the top right corner of the inspector window, select the gear icon followed by "Select Shader"

    Selecionar um sombreador no Unity

  2. Com o ativo do sombreador selecionado, clique no botão "Compilar e mostrar código" na janela do inspetorWith the shader asset selected, click the "Compile and show code" button under the inspector window

    Compilar o código do sombreador no Unity

  3. Após a compilação, procure a seção de estatísticas nos resultados com o número de operações diferentes para o vértice e o sombreador de pixel (observação: os sombreadores de pixel costumam ser chamados de sombreadores de fragmento)After compiling, look for the statistics section in the results with the number of different operations for both the vertex and pixel shader (Note: pixel shaders are often also called fragment shaders)

    Operações de sombreador padrão do Unity

Otimizar sombreadores de pixelOptimize pixel shaders

Observando os resultados da estatística compilada usando o método acima, em média, o sombreador de fragmento geralmente executará mais operações do que o sombreador de vértice.Looking at the compiled statistic results using the method above, the fragment shader will generally execute more operations than the vertex shader, on average. O sombreador de fragmento, também conhecido como sombreador de pixel, é executado por pixel na saída da tela, ao passo que o sombreador de vértice só é executado por vértice de todas as malhas que estão sendo desenhadas na tela.The fragment shader, also known as the pixel shader, is executed per pixel on the screen output while the vertex shader is only executed per-vertex of all meshes being drawn to the screen.

Portanto, os sombreadores de fragmento têm mais instruções do que os sombreadores de vértice devido a todos os cálculos de iluminação e quase sempre são executados em um conjunto de dados maior.Thus, not only do fragment shaders have more instructions than vertex shaders because of all the lighting calculations, fragment shaders are almost always executed on a larger dataset. Por exemplo, se a saída da tela for uma imagem de 2.000 por 2.000, o sombreador de fragmento poderá ser executado 2.000 * 2.000 = 4.000.000 vezes.For example, if the screen output is a 2k by 2k image, then the fragment shader can get executed 2,000*2,000 = 4,000,000 times. Se a renderização de dois olhos estiver sendo feita, esse número dobrará, pois há duas telas.If rendering two eyes, this number doubles since there are two screens. Se um aplicativo de realidade misturada tiver várias passagens, efeitos de pós-processamento de tela inteira ou estiver renderizando várias malhas no mesmo pixel, esse número aumentará drasticamente.If a mixed reality application has multiple passes, full-screen post-processing effects, or rendering multiple meshes to the same pixel, this number will increase dramatically.

Portanto, a redução do número de operações no sombreador de fragmento pode, em geral, proporcionar ganhos de desempenho muito maiores em otimizações no sombreador de vértice.Therefore, reducing the number of operations in the fragment shader can generally give far greater performance gains over optimizations in the vertex shader.

Alternativas do sombreador padrão do UnityUnity Standard shader alternatives

Em vez de usar uma PBR (renderização baseada em física) ou outro sombreador de alta qualidade, examine a utilização de um sombreador mais barato e com melhor desempenho.Instead of using a physically based rendering (PBR) or another high-quality shader, look at utilizing a more performant and cheaper shader. O Mixed Reality Toolkit fornece o sombreador padrão do MRTK que foi otimizado para projetos de realidade misturada.The Mixed Reality Toolkit provides the MRTK standard shader that has been optimized for mixed reality projects.

O Unity também fornece um sombreador apagado, com vértice iluminado, difuso e outras opções de sombreador simplificadas que são significativamente mais rápidas em comparação com o sombreador padrão do Unity.Unity also provides an unlit, vertex lit, diffuse, and other simplified shader options that are significantly faster compared to the Unity Standard shader. Confira Uso e desempenho de sombreadores internos para obter informações mais detalhadas.See Usage and Performance of Built-in Shaders for more detailed information.

Pré-carregamento de sombreadorShader preloading

Use o Pré-carregamento de sombreador e outros truques para otimizar o tempo de carregamento do sombreador.Use Shader preloading and other tricks to optimize shader load time. Em particular, o pré-carregamento de sombreador significa que você não verá nenhum problema devido à compilação do sombreador em runtime.In particular, shader preloading means you won't see any hitches due to runtime shader compilation.

Limitar a sobreposiçãoLimit overdraw

No Unity, é possível exibir sobreposições para a cena alternando o menu do modo de desenho no canto superior esquerdo da Exibição de cena e selecionando Sobreposição.In Unity, one can display overdraw for their scene, by toggling the draw mode menu in the top-left corner of the Scene view and selecting Overdraw.

Em geral, a sobreposição pode ser atenuada com a remoção de objetos antecipadamente ao envio para a GPU.Generally, overdraw can be mitigated by culling objects ahead of time before they are sent to the GPU. O Unity fornece detalhes sobre como implementar a Remoção de Oclusão para o mecanismo.Unity provides details on implementing Occlusion Culling for their engine.

Recomendações de memóriaMemory recommendations

As operações de alocação e desalocação de memória excessiva podem ter efeitos adversos no aplicativo holográfico, resultando em desempenho inconsistente, quadros congelados e outros comportamentos prejudiciais.Excessive memory allocation & deallocation operations can have adverse effects on your holographic application, resulting in inconsistent performance, frozen frames, and other detrimental behavior. É especialmente importante entender as considerações de memória no desenvolvimento no Unity, pois o gerenciamento de memória é controlado pelo coletor de lixo.It is especially important to understand memory considerations when developing in Unity since memory management is controlled by the garbage collector.

Coleta de lixoGarbage collection

Os aplicativos holográficos perderão o tempo de computação do processamento para o GC (coletor de lixo) quando o GC for ativado para analisar objetos que não estão mais no escopo durante a execução, e a memória deles precisará ser liberada, de modo que possa ser disponibilizada para reutilização.Holographic apps will lose processing compute time to the garbage collector (GC) when the GC is activated to analyze objects that are no longer in scope during execution and their memory needs to be released, so it can be made available for re-use. As alocações e as desalocações constantes geralmente exigirão que o coletor de lixo seja executado com mais frequência, prejudicando o desempenho e a experiência do usuário.Constant allocations and de-allocations will generally require the garbage collector to run more frequently, thus hurting performance and user experience.

O Unity forneceu uma página excelente que explica em detalhes como o coletor de lixo funciona e dicas para escrever um código mais eficiente em relação ao gerenciamento de memória.Unity has provided an excellent page that explains in detail how the garbage collector works and tips to write more efficient code in regards to memory management.

Uma das práticas mais comuns que leva ao excesso de coleta de lixo não é armazenar em cache referências a componentes e classes no desenvolvimento no Unity.One of the most common practices that leads to excessive garbage collection is not caching references to components and classes in Unity development. Todas as referências devem ser capturadas durante Start() ou Awake() e reutilizadas em funções posteriores, como Update() ou LateUpdate().Any references should be captured during Start() or Awake() and re-used in later functions such as Update() or LateUpdate().

Outras dicas rápidas:Other quick tips:

  • Use a classe C# StringBuilder para criar dinamicamente cadeias de caracteres complexas em runtimeUse the StringBuilder C# class to dynamically build complex strings at runtime
  • Remova as chamadas a Debug.Log() quando não for mais necessário, pois elas ainda são executadas em todas as versões de build de um aplicativoRemove calls to Debug.Log() when no longer needed, as they still execute in all build versions of an app
  • Se o aplicativo holográfico geralmente exige muita memória, considere a possibilidade de chamar System.GC.Collect() durante o carregamento de fases, como ao apresentar uma tela de carregamento ou de transiçãoIf your holographic app generally requires lots of memory, consider calling System.GC.Collect() during loading phases such as when presenting a loading or transition screen

Pool de objetosObject pooling

O pool de objetos é uma técnica popular para reduzir o custo de alocações e desalocações contínuas de objetos.Object pooling is a popular technique to reduce the cost of continuous allocations & deallocations of objects. Isso é feito pela alocação de um grande pool de objetos idênticos e pela reutilização das instâncias disponíveis inativas desse pool em vez da criação e da destruição constantes de objetos ao longo do tempo.This is done by allocating a large pool of identical objects and re-using inactive, available instances from this pool instead of constantly spawning and destroying objects over time. Os pools de objetos são ótimos para componentes reutilizados que têm um tempo de vida variável durante um aplicativo.Object pools are great for re-useable components that have variable lifetime during an app.

Desempenho de inicializaçãoStartup performance

Você deve considerar iniciar seu aplicativo com uma cena menor e, em seguida, usar SceneManager.LoadSceneAsync para carregar o restante da cena.You should consider starting your app with a smaller scene, then using SceneManager.LoadSceneAsync to load the rest of the scene. Isso permite que o aplicativo chegue a um estado interativo o mais rápido possível.This allows your app to get to an interactive state as fast as possible. Esteja ciente de que pode haver um grande pico de CPU enquanto a nova cena está sendo ativada e que qualquer conteúdo renderizado pode tremer ou apresentar algum problema.Be aware that there may be a large CPU spike while the new scene is being activated and that any rendered content might stutter or hitch. Um modo de resolver isso é definir a propriedade AsyncOperation.allowSceneActivation como "false" na cena que está sendo carregada, aguardar a cena ser carregada, limpar a tela para preto e, em seguida, defini-la novamente como "true" para concluir a ativação da cena.One way to work around this is to set the AsyncOperation.allowSceneActivation property to "false" on the scene being loaded, wait for the scene to load, clear the screen to black, and then set it back to "true" to complete the scene activation.

Lembre-se de que, durante o carregamento da cena de inicialização, a tela inicial holográfica será exibida para o usuário.Remember that while the startup scene is loading, the holographic splash screen will be displayed to the user.

Veja tambémSee also