Skip to content

Construindo um mundo aberto no navegador, parte 8: Integração sem perder nossa base de referência

Por Oleg Sidorkin, CTO e cofundador da Cinevva

Chegou agora? Consulte o guia da série. Ele explica o que é um spike e reúne links para todas as partes.

É na integração que os projetos ficam complicados. Você tem componentes que funcionam isoladamente. Então, conecta tudo e, de repente, parece que qualquer bug pode estar em qualquer lugar.

Os spikes 13 e 14 foram nossa resposta a essa armadilha. O spike 13 estabeleceu uma base limpa de Three.js com WebGPU. Apenas um renderizador, uma cena, uma câmera e uma malha simples. Sem terreno, computação ou efeitos. Confirmamos que o backend WebGPU do Three.js era inicializado corretamente, que o loop de renderização estava estável e que os materiais baseados em nós da TSL (Three.js Shading Language) funcionavam como esperado. Só depois que esse ponto de verificação foi aprovado começamos a adicionar camadas.

Abrir o spike 13 em uma nova aba ↗ · Ver código-fonte

O spike 14 foi um processo de fortalecimento incremental. Adicionamos um recurso de cada vez: primeiro os controles da câmera, depois a iluminação, em seguida a malha gerada por computação pelo pipeline de marching cubes e, por fim, a infraestrutura de buffers para enviar a saída da GPU diretamente aos atributos de geometria do Three.js. Após cada adição, verificamos se a camada anterior continuava funcionando corretamente.

Abrir o spike 14 em uma nova aba ↗ · Ver código-fonte

Isso parece lento. E foi lento por exatamente um dia, mas logo depois nos poupou vários dias quando a lógica de emendas e a troca de políticas ficaram complicadas.

A categoria específica de bug que justificou essa disciplina foram os artefatos finíssimos. Pequenas lascas que pareciam corrupção de geometria, mas que, na verdade, eram dados obsoletos. O shader de computação gravava N vértices em um buffer, mas a chamada de desenho continuava configurada para renderizar N+M vértices do quadro anterior. Esses vértices adicionais continham lixo da execução anterior. O resultado visual eram triângulos oscilantes e extremamente finos, que apareciam e desapareciam de forma imprevisível.

Você não vence esse tipo de bug com intuição. Você o vence com alterações controladas, sabendo exatamente o que mudou entre o último estado funcional e o estado atual com defeito.

A integração com WebGPU também nos ensinou sobre o ciclo de vida dos buffers. Os buffers de GPU no WebGPU são imutáveis depois de mapeados para um uso específico. Se for necessário redimensionar um buffer de vértices porque a saída do marching cubes cresceu, é preciso criar um novo buffer e atualizar o vínculo. Não existe realloc. Acertar esse ciclo de vida e destruir buffers antigos sem entrar em conflito com operações da GPU ainda em andamento exigiu um gerenciamento explícito de fences que não existe no WebGL.

Na parte 9, avançamos para o trabalho nas emendas com Transvoxel. Esse capítulo começa propositalmente com uma estrutura básica. A essa altura, já tínhamos assimilado por completo a lição de que apressar a integração produz mistérios, enquanto uma preparação controlada produz problemas que podem ser depurados.

Tecnologias mencionadas neste capítulo

WebGPU. O sucessor do WebGL, que oferece acesso de baixo nível à GPU no navegador, com shaders de computação e renderização indireta. Os dois recursos essenciais do WebGPU para mundos abertos são: os shaders de computação permitem gerar terrenos, posicionar vegetação e fazer culling na GPU; a renderização indireta permite que a GPU decida o que desenhar com base na saída da computação, eliminando gargalos de CPU em cenas densas. Disponível no Chrome, Edge e Firefox em computadores. Consulte WebGPU como um salto de desempenho.

Three.js Shading Language (TSL). O sistema de shaders baseado em nós do Three.js, que substitui GLSL/WGSL puros por expressões JavaScript combináveis. Nós da TSL como texture(), positionWorld, smoothstep() e fog() criam, em tempo de execução, um grafo de shader que é compilado para o backend apropriado (GLSL do WebGL ou WGSL do WebGPU). A TSL permite escrever a lógica de materiais uma única vez e usá-la nos dois renderizadores. O grafo de nós é avaliado a cada quadro, portanto uniforms dinâmicos e ramificações condicionais funcionam de forma natural.

Ciclo de vida dos buffers de GPU no WebGPU. Os buffers do WebGPU são criados com sinalizadores de uso específicos (VERTEX, STORAGE, COPY_DST etc.) e não podem ser redimensionados após a criação. Se uma execução de marching cubes produzir mais vértices do que o buffer comporta, será necessário criar um novo buffer, atualizar o vínculo e destruir o antigo. Destruir um buffer que ainda esteja referenciado por um comando da GPU em andamento causa erros. O gerenciamento explícito de fences (por meio de device.queue.onSubmittedWorkDone()) garante que o buffer antigo não seja destruído antes que a GPU termine de usá-lo. Essa disciplina de ciclo de vida não existe no WebGL, em que o driver gerencia a memória implicitamente.

Fortalecimento incremental. Uma disciplina de processo para integração: estabelecer uma base comprovadamente funcional, adicionar um recurso de cada vez e verificar se a camada anterior continua funcionando após cada adição. Essa abordagem é mais lenta por um dia e economiza dias durante a depuração posterior, pois cada regressão pode ser rastreada até uma alteração específica e controlada. O padrão de primeiro criar uma base e depois avançar incrementalmente é comum no desenvolvimento de mundos abertos AAA, em que os sistemas são integrados em uma ordem específica para gerenciar riscos.


Parte 8 de 12.
Anterior: Parte 7 — Marching cubes e as primeiras cavernas de verdade
Próxima: Parte 9 — O Transvoxel começou com uma estrutura básica
Guia da série: /pt-BR/blog/2026-02-25-open-world-browser-series-guide