Voltar para o Blog
Quest Log

Draw Calls na Unity: SRP Batcher, Static e GPU Instancing

Centenas de cubos coloridos em grade sendo agrupados em blocos maiores por feixes de luz, ao lado de uma lista de eventos de renderização

Unity draw calls no URP pelo Frame Debugger: SRP Batcher, static batching, GPU instancing e GPU Resident Drawer, o que se anula e um teste com 500 objetos.

Ferramenta:Unity

Draw Calls na Unity: SRP Batcher, Static e GPU Instancing

Unity draw calls são os pedidos que a CPU faz à GPU para desenhar algo, e o custo real está menos no desenho e mais na preparação: trocar shader, material e dados antes de cada pedido. No URP do Unity 6.3 LTS, quatro mecanismos atacam isso: SRP Batcher (ligado por padrão), static batching, GPU instancing e GPU Resident Drawer. Eles não somam: a Unity escolhe um por objeto, numa ordem fixa, e alguns se anulam.

Este post explica cada um, mostra como ler o resultado no Frame Debugger e no painel Stats, e propõe um teste com 500 objetos que você roda no seu projeto para ver com os próprios olhos o que cada configuração muda.

O que é draw call e SetPass call

Para desenhar um objeto, a CPU precisa dizer à GPU: use este shader, com estas texturas e valores, nesta malha, nesta posição. Isso é um draw call. Antes dele, se o shader ou o passe mudou desde o último desenho, há uma troca de estado chamada SetPass call, que é mais cara.

No painel Stats da Game View (botão Stats no canto superior direito), você vê:

  • Batches: quantos lotes de desenho o frame teve.
  • SetPass calls: quantas trocas de estado de shader.
  • Saved by batching: quantos desenhos foram agrupados.

Mil objetos não significam mil problemas. Significam mil preparações de CPU se nada estiver agrupando. Em PC isso raramente pesa; em celular, é um dos gargalos de CPU mais comuns.

Unity draw calls: ler no Frame Debugger e no Stats

O Frame Debugger (Window > Analysis > Frame Debugger) congela um frame e lista cada evento de renderização em ordem.

  1. Dê Play e clique em Enable no Frame Debugger.
  2. À esquerda, a árvore de eventos. Expanda os passes do URP (como DrawOpaqueObjects).
  3. Eventos com o nome SRP Batch são lotes do SRP Batcher. Clique em um: à direita aparece quantos draw calls ele juntou.
  4. Quando dois eventos seguidos não foram agrupados, o painel direito mostra o motivo (por exemplo, shader diferente, variante de keyword diferente, objeto com MaterialPropertyBlock).
  5. Arraste o seletor de evento para ver a tela sendo desenhada passo a passo.

Esse "motivo" é a informação que você não acha no Stats e que diz exatamente o que corrigir.

SRP Batcher: como funciona e o que quebra

O SRP Batcher não junta malhas. Ele guarda os dados dos materiais na GPU de forma persistente e, enquanto o shader variant for o mesmo, faz os draw calls seguidos sem refazer a preparação. Materiais diferentes com o mesmo shader (o URP Lit, por exemplo) entram no mesmo lote.

Segundo o manual do URP, ele fica no URP Asset em Rendering > SRP Batcher (visível com Show All Advanced Properties no menu de três pontos) e vem ligado. Se SRP Batcher e Dynamic Batching estão ligados, o SRP Batcher tem precedência quando o shader é compatível.

O que quebra:

  • Shader customizado sem CBUFFER_START(UnityPerMaterial) envolvendo as propriedades do material. Selecione o shader no Project e veja no Inspector se aparece "SRP Batcher: compatible".
  • MaterialPropertyBlock: o manual avisa que usá-lo remove a compatibilidade com o SRP Batcher. No URP, para mudar cor por objeto, prefira materiais diferentes.
  • Variantes diferentes: o mesmo shader com keywords diferentes (normal map ligado num material e desligado em outro) vira variantes diferentes e quebra o lote.

Shaders do próprio URP (Lit, Simple Lit, Unlit) e os gerados pelo Shader Graph já saem compatíveis. O problema costuma aparecer com shader escrito à mão ou importado de asset antigo feito para a Built-In Render Pipeline, que a Unity marcou como descontinuada no Unity 6.5. Antes de comprar um pacote de shaders na Asset Store, confira na página se ele declara suporte a URP e SRP Batcher.

Static batching

Para objetos que nunca se movem (cenário, prédios, pedras), marque Static (ou só Batching Static no menu da caixa Static) no Inspector. Na build, a Unity combina as malhas que compartilham material em malhas grandes e desenha em poucos lotes.

Custo: memória extra, porque as malhas combinadas são guardadas. Objeto estático que se move depois deixa de ser desenhado no lugar certo. Funciona bem com SRP Batcher: na ordem de prioridade do manual, malhas estáticas usam static batching primeiro.

GPU instancing e por que não soma com SRP Batcher

GPU instancing desenha muitas cópias da mesma malha com o mesmo material num único draw call, passando posições e dados por instância. Ideal para grama, árvores, projéteis, multidões. Ative em Enable GPU Instancing no material.

A pegadinha: o manual do Unity 6.3 descreve a ordem em que os métodos são aplicados quando vários estão disponíveis:

  1. Malhas estáticas usam static batching.
  2. Malhas dinâmicas com shader compatível usam SRP Batcher (com o GPU Resident Drawer ou a API BatchRendererGroup, quando ligados).
  3. As restantes com shader compatível usam GPU instancing.
  4. O resto usa dynamic batching, se estiver ligado.

Como o URP Lit é compatível com SRP Batcher, marcar Enable GPU Instancing nele não muda nada: o objeto já foi pego no passo 2. A página de GPU instancing do manual confirma que, com shader customizado no URP, instancing só funciona com o SRP Batcher desligado. Na prática, no URP do Unity 6 o caminho para instanciar automaticamente é o GPU Resident Drawer.

GPU Resident Drawer no Unity 6

O GPU Resident Drawer usa a API BatchRendererGroup para desenhar objetos com MeshRenderer por instancing, automaticamente, sem você mudar material. Requisitos segundo o manual do URP no Unity 6.3:

  • Caminho de renderização Forward+ no Universal Renderer.
  • API gráfica com compute shader, exceto OpenGL ES.
  • Objeto com Mesh Renderer, sem script de callback por instância (como OnRenderObject) e sem MaterialPropertyBlock.
  • Em Project Settings > Graphics > Shader Stripping, BatchRendererGroup Variants: Keep All (a build fica mais demorada).
  • No URP Asset, SRP Batcher ligado e GPU Resident Drawer: Instanced Drawing.

O mesmo manual sugere desligar Static Batching em Project Settings > Player > Other Settings quando o GPU Resident Drawer está ativo, porque isso pode acelerar o drawer. Para excluir um objeto, adicione o componente Disallow GPU Driven Rendering. Ele brilha em cenas com muitas cópias da mesma malha. Em celular antigo com OpenGL ES, não funciona.

Entender as quatro técnicas é a parte didática. Aplicar num jogo inteiro é outra coisa: decidir atlas de textura, organizar prefabs, combinar arte e código para não precisar desmontar tudo no fim do projeto. Essa visão de produção, juntando arte, cena e script, é onde quem aprende sozinho mais tropeça.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Teste prático: 500 objetos

Rode este experimento numa cena vazia do URP. Arquivo BatchingTestSpawner.cs:

using UnityEngine;

public class BatchingTestSpawner : MonoBehaviour
{
    [SerializeField] private GameObject prefab;
    [SerializeField] private Material[] materials;
    [SerializeField] private int count = 500;
    [SerializeField] private float spacing = 1.5f;
    [SerializeField] private bool useInstancedMaterials;

    private void Start()
    {
        int side = Mathf.CeilToInt(Mathf.Sqrt(count));

        for (int i = 0; i < count; i++)
        {
            Vector3 position = new Vector3((i % side) * spacing, 0f, (i / side) * spacing);
            GameObject go = Instantiate(prefab, position, Quaternion.identity, transform);
            MeshRenderer meshRenderer = go.GetComponent<MeshRenderer>();
            Material chosen = materials[i % materials.Length];

            if (useInstancedMaterials)
            {
                meshRenderer.material = chosen;
                meshRenderer.material.color = Random.ColorHSV();
            }
            else
            {
                meshRenderer.sharedMaterial = chosen;
            }
        }
    }
}

O prefab é um cubo com MeshRenderer. Crie 4 materiais com o shader URP Lit em cores diferentes e arraste para o array. Rode cada configuração e anote Batches, SetPass calls e o tempo de renderização no Profiler:

  1. SRP Batcher ligado, useInstancedMaterials desligado: o Frame Debugger mostra poucos eventos SRP Batch, mesmo com 4 materiais, porque o shader é o mesmo.
  2. SRP Batcher desligado (no URP Asset): compare o número de Batches e SetPass calls com o caso 1.
  3. useInstancedMaterials ligado: cada cubo ganha seu próprio material. Veja o que acontece com o SRP Batcher (continua agrupando) e com a memória.
  4. GPU Resident Drawer ligado (com Forward+, os requisitos acima e useInstancedMaterials desligado): os cubos que compartilham cada um dos 4 materiais passam a ser desenhados por instancing.
  5. Cubos marcados como Static em vez de instanciados por script, com o GPU Resident Drawer desligado: veja o static batching assumir.

O ponto do teste não é um número mágico, que muda de máquina para máquina, mas ver no Frame Debugger qual mecanismo pegou cada objeto e por quê.

Erros comuns: material instanciado por script

  • renderer.material em vez de renderer.sharedMaterial: acessar .material cria uma cópia só para aquele objeto. Quebra static batching e GPU instancing, gasta memória e a cópia não é destruída sozinha quando o objeto some. Use .sharedMaterial quando não quer cópia.
  • Trocar por MaterialPropertyBlock "para otimizar": no URP, isso tira o objeto do SRP Batcher. Prefira um material por variação.
  • Ligar Enable GPU Instancing no URP Lit e esperar ganho: o SRP Batcher tem prioridade.
  • Marcar como Static um objeto que se move: ele fica preso na posição combinada.
  • Keywords diferentes por material: dois materiais Lit, um com normal map e outro sem, são variantes diferentes. Padronize.
  • Contar Batches e esquecer SetPass calls: no URP, SetPass calls costuma ser o indicador mais útil.

Próximo passo

Abra o Frame Debugger na cena mais pesada do seu jogo e anote os três motivos mais frequentes de "não agrupado". Corrija o primeiro, meça no Profiler e repita. Se você ainda não sabe se o gargalo é CPU ou GPU, comece pelo post sobre o Profiler da Unity. Para a teoria geral de performance, leia otimização para 60 FPS, e se você também mexe com Godot, o conceito equivalente ao instancing está em MultiMesh no Godot.

Perguntas frequentes

O que é SetPass call na Unity?

É uma troca de estado de renderização, normalmente de shader ou de passe de shader. O painel Stats da Game View mostra Batches e SetPass calls separados. No URP com SRP Batcher, os SetPass calls costumam ser o número que mais pesa na CPU, porque o batcher já barateia os draw calls de mesmo shader.

GPU instancing funciona junto com SRP Batcher?

Não no mesmo objeto. Segundo o manual do Unity 6.3, quando um objeto é compatível com os dois, a Unity usa o SRP Batcher, e o GPU instancing com shader customizado só funciona com o SRP Batcher desligado ou com o objeto tornado incompatível. O GPU Resident Drawer é o caminho do Unity 6 para instanciar automaticamente no URP.

Como ver os draw calls na Unity?

Abra o painel Stats no canto da Game View para ver Batches e SetPass calls, e use Window > Analysis > Frame Debugger para ver cada evento de desenho do frame. O Frame Debugger também diz por que dois objetos não foram agrupados.

Usar renderer.material num script quebra o batching?

Cria uma cópia do material para aquele objeto. Isso quebra static batching e GPU instancing e gasta memória, e no URP o SRP Batcher ainda agrupa porque o shader é o mesmo. O pior caso é trocar por MaterialPropertyBlock, que remove a compatibilidade com o SRP Batcher.