Como a Toca Boca construiu um backend de renderização escalável e de alto desempenho

BERTRAND GUAY-PAQUET / TOCA BOCASenior Graphics Engineer
Mar 20, 2026
Toca Boca
Esta página da Web foi automaticamente traduzida para sua conveniência. Não podemos garantir a precisão ou a confiabilidade do conteúdo traduzido. Se tiver dúvidas sobre a precisão do conteúdo traduzido, consulte a versão oficial em inglês da página da Web.

Para atender às demandas de desempenho do nosso ambicioso jogo móvel 3D multiplayer, Toca Boca Days – especialmente em dispositivos de menor capacidade – precisávamos inovar. O jogo (agora em pausa) apresentava níveis expansivos e suportava até 32 jogadores com avatares altamente personalizáveis.

No final de 2022, lançamos nosso sistema de backend Batch Renderer, construído sobre a API BatchRendererGroup do Unity. Esse novo sistema proporcionou ganhos significativos de desempenho, que continuamos refinando.

Neste artigo, compartilharei insights sobre a arquitetura do sistema enquanto nos preparamos para usá-lo em nosso próximo jogo.

Visão geral da arquitetura de dados

Em um nível alto, nosso sistema divide os dados de um GameObject e seu componente Renderer em três camadas.

Arquitetura de dados em camadas
Arquitetura de dados em camadas

Na primeira camada, cada componente Renderer corresponde a uma única instância de culling. Essa camada contém todos os dados necessários para determinar a visibilidade com base nos planos de culling da câmera, máscara de camada e qualquer grupo LOD pai.

A segunda camada representa o(s) material(is) de cada Renderer, onde cada par Renderer-material forma uma instância de desenho. As instâncias de desenho armazenam dados relacionados à renderização, como a malha e o material.

A terceira camada consiste em lotes BatchRendererGroup (daí o nome Batch Renderer) e suas instâncias. Cada lote possui seu próprio GraphicsBuffer contendo propriedades por instância integradas, como a matriz ObjectToWorld, juntamente com um conjunto opcional de propriedades de material por instância.

O conjunto de propriedades de material por instância de um Renderer determina seu tipo de lote.

Toca Boca Days | Toca Boca
Vários livros com valores de propriedades de material por instância

Nosso sistema armazena todos os dados do lado da CPU em uma estrutura compacta, orientada a dados, no formato de arrays (SoA). Ele se baseia em tipos blitáveis dentro de contêineres nativos sempre que possível, permitindo o uso de trabalhos de Burst de alto desempenho para tarefas como culling de instâncias e geração de comandos de desenho. Essa arquitetura permite que o sistema lide eficientemente com grandes quantidades de instâncias.

Propriedades de material por instância

Para atribuir propriedades de material por instância a um Renderer, adicionamos um componente que referencia um ativo de "conjunto de propriedades"ScriptableObject. Os conjuntos de propriedades devem ser limitados em número, pois cada um define um tipo de lote separado – instâncias de lotes diferentes não são renderizadas juntas.

Para facilitar a autoria, fornecemos um componente para definir valores de propriedades instanciadas iniciais, juntamente com uma janela de Inspector personalizada. Sem propriedades por instância, esses valores precisariam ser definidos em materiais únicos, reduzindo a eficiência do lote.

Inspector de propriedades de material por instância
Inspector de propriedades de material por instância

Skinning e esqueletos

Em Toca Boca Days, os avatares apresentavam extensa personalização de personagens. Para suportar roupas e acessórios variados, dividimos as partes esqueléticas da malha de cada avatar em 17 componentes Skinned Mesh Renderer (por exemplo, braço superior esquerdo). A maioria dos itens de vestuário adiciona componentes Renderer esqueléticos adicionais, que podem se acumular rapidamente à medida que o número de jogadores aumenta.

Toca Boca Days | Toca Boca
Avatares de Toca Boca Days

Como nossos avatares são complexos, evitamos usá-los para mapas de sombras, que seriam caros. Em vez disso, usamos sombras em forma de blob, e na maioria dos casos, os avatares são renderizados apenas uma vez por quadro. Isso significa que não podemos amortizar o custo de skinning de vértices embutido no Unity em vários comandos de desenho para uma única malha.

Em vez disso, lidamos com o skinning diretamente no shader de vértice. Todas as matrizes de skinning são armazenadas em um único GraphicsBuffer global, e cada Renderer recebe um deslocamento para esse buffer para ajustar os índices de osso por vértice e buscar suas matrizes de skinning correspondentes.

O deslocamento do esqueleto é armazenado como dados de instância do BatchRendererGroup, permitindo que agrupemos chamadas de desenho entre personagens que compartilham a mesma malha (por exemplo, todos os braços superiores direitos juntos), melhorando o desempenho.

Calcular matrizes de skinning normalmente requer contagem de renderizadores × contagem média de ossos por renderizador em cálculos. Para reduzir isso, combinamos duas estratégias:

Certifique-se de que todos os componentes Renderer com pele em um avatar usem índices de ossos consistentes – por exemplo, o índice do osso 2 sempre se refere ao peito, mesmo para um Renderer do pé direito que não o referencia.

Pré-processar malhas para identificar conjuntos únicos de índices de ossos e matrizes de pose de ligação, idealmente resultando em um único conjunto por avatar.

Componente pré-processador de rig de esqueleto
Componente pré-processador de rig de esqueleto

Com essa abordagem, podemos consolidar o skinning de todos os componentes Skinned Mesh Renderer de um avatar em um único par de "esqueleto de animação" e "esqueleto de renderer".

Esqueletos de animação e renderer (mostrando dois esqueletos de renderer)
Esqueletos de animação e renderer (mostrando dois esqueletos de renderer)

Em cada quadro, nosso gerenciador de esqueletos realiza os seguintes passos:

Extração de animação: Os trabalhos de animação puxam as transformações atuais dos ossos do esqueleto de animação via a API Playables (Mecanim).

Cálculo de pose: Os trabalhos de Burst geram matrizes de pose animadas a partir da hierarquia de ossos e das transformações extraídas.

Montagem da matriz de skinning: Os trabalhos de Burst combinam matrizes de pose animadas e matrizes de pose de ligação em matrizes de skinning.

Upload para GPU: As novas matrizes de skinning são enviadas para o GraphicsBuffer global usando um shader de computação.

Esse processo é complexo, mas altamente eficiente, reduzindo as matrizes de skinning por um fator de dois para um avatar nu e por um fator de quatro ou mais quando a roupa é aplicada.

Olhando para o futuro

Desenvolver um backend de renderização personalizado para Toca Boca Days e agora modernizá-lo para Unity 6.3 provou ser valioso. A atualização do Unity 2022.3 foi tranquila, e nosso sistema de Batch Renderer continua a oferecer um desempenho forte para jogos com extensa personalização de personagens e propriedades de material por instância.

Ter controle total sobre a geração de comandos de desenho via a API BatchRendererGroup permite estratégias de culling personalizadas – como portais de oclusão e culling de oclusão personalizado – e otimizações de animação, como limitar as atualizações da matriz de skinning com base na proximidade e importância da câmera.

A extensibilidade do Unity, combinada com soluções eficientes prontas para uso, como o Universal Render Pipeline (URP) e o GPU Resident Drawer (com acesso ao código-fonte), nos permitiu alcançar nossas metas de desempenho enquanto mantínhamos um pipeline de renderização personalizado altamente otimizado.

Toca Boca Days | Toca Boca
O Batch Renderer voltará

Toca Boca está desenvolvendo um jogo ainda não anunciado; siga o estúdio no Instagram, TikTok e YouTube para atualizações. Explore mais histórias de desenvolvedores de Unity mobile em nossa página de soluções mobile e em nosso hub de Recursos.