Construindo um mundo aberto no navegador, parte 22: Nuvens que podem ser iluminadas e um sistema de descarte que precisa ser alimentado
Por Oleg Sidorkin, CTO e cofundador da Cinevva
Chegou agora? Consulte o guia da série. Ele explica o que é um spike e traz links para todas as partes.
A Parte 21 tratou de uma técnica de renderização que não compensou. Esta parte apresenta uma que compensou e outra que precisou de uma correção cuidadosa para funcionar. O Spike 43 é o céu: uma atmosfera fisicamente baseada e nuvens volumétricas, a base que faz uma cena parecer um lugar em vez de uma demonstração técnica. O Spike 44 é o descarte de meshlets na GPU, no qual aprendemos que o teste de oclusão é tão bom quanto os oclusores que fornecemos a ele.
Um céu criado pela física, não por um gradiente
Abrir o Spike 43 em uma nova aba ↗ · Ver código-fonte
Quase todo efeito climático cinematográfico depende de dois elementos de infraestrutura: uma atmosfera fisicamente baseada, para que as cores do céu e do sol acompanhem o horário do dia segundo a física, em vez de usar um gradiente ajustado manualmente; e um volume de nuvens volumétricas, para que o céu tenha estrutura 3D em vez de um cubemap pré-renderizado. O Spike 43 implementa exatamente esse par sobre a pilha existente de WebGPU e TSL, e nada mais, porque, quando esses dois elementos existem, o restante da pilha meteorológica — neblina, raios divinos, superfícies molhadas e neve — se transforma em uma série de extensões menores e conhecidas.
A atmosfera usa o modelo Hillaire 2020, um conjunto de tabelas de consulta calculadas em shaders de computação WGSL. Uma tabela de transmitância integra a luz solar através do perfil de densidade de Rayleigh, Mie e ozônio e só é recalculada quando o sol se move. Uma tabela de visualização do céu é recalculada a cada quadro porque isso é barato o suficiente para que condicionar a operação não justifique o código adicional, usando uma parametrização não linear ao redor do horizonte para evitar faixas visíveis. Por enquanto, o espalhamento múltiplo usa uma aproximação analítica em vez da tabela correta, e os pores do sol parecem corretos, então o atalho está bem disfarçado. As nuvens usam uma marcha de raios no estilo Schneider Nubis através de uma camada horizontal, com a forma definida por uma textura Perlin-Worley de 128³ erodida por uma textura Worley de 32³, ambas geradas na inicialização por computação, sem nenhuma busca pela rede. A iluminação usa extinção pela lei de Beer, uma função de fase de lóbulo duplo e uma aproximação de powder. O acoplamento essencial é que a cor solar das nuvens consulta a mesma tabela de transmitância a cada passo, fazendo a iluminação das nuvens acompanhar o pôr do sol sem uma segunda etapa de ajustes.
Em um M1, o conjunto inteiro leva de 1,1 a 2,0 ms com as nuvens em meia resolução, bem abaixo do orçamento de 6 ms, usa cerca de 14 MB de memória da GPU e roda acima de 100 FPS. As duas teses se confirmaram na prática. O pôr do sol é a cena de destaque, o momento que faz o renderizador parecer cinematográfico, e surge naturalmente da física sem ajustes específicos para cada horário do dia. E houve um feliz acidente: com a camada de nuvens parametrizada entre 800 m e 4.000 m, nuvens distantes no horizonte parecem cordilheiras escuras vistas de uma câmera baixa, oferecendo ao mundo um terreno de fundo sem que ninguém precise modelá-lo.
Uma observação de arquitetura que vale a pena preservar. A abordagem natural é desenhar primeiro o céu na swap chain e depois deixar o three.js desenhar a geometria por cima com autoClear = false. Isso não funciona com o renderizador WebGPU na r184, porque a flag não controla a operação de carregamento de cor como ocorre no WebGL, então o three.js sobrescreve o céu a cada quadro. A correção é renderizar o three.js em um alvo fora da tela e fazer a composição final (mix(skyCloud, scene, scene.alpha), seguida por ACES e depois sRGB) em uma passagem própria que controla a swap chain.
Um sistema de descarte tão bom quanto seus oclusores
Abrir o Spike 44 em uma nova aba ↗ · Ver código-fonte
O Spike 44 compara o desempenho de quatro modos de renderização: forward simples, descarte de clusters na CPU, descarte por computação na GPU e um buffer de visibilidade com descarte por oclusão Hi-Z. O caminho Hi-Z é o mais interessante e tinha um bug discreto: o HUD dizia que a oclusão estava ativada, mas o contador “Descartados por Hi-Z” permanecia para sempre em exatamente 0,0%. O descarte por frustum funcionava, portanto a etapa anterior ao shader de descarte estava correta. A metade responsável pela oclusão não fazia nada, mas ainda cobrava o custo integral.
Um teste de oclusão Hi-Z projeta na tela a caixa delimitadora de um cluster, seleciona um nível mip da pirâmide de profundidade para que o retângulo na tela tenha cerca de 2×2 texels, amostra a profundidade do oclusor mais distante nesse retângulo e rejeita o cluster se seu ponto mais próximo ainda estiver além desse oclusor. A pirâmide de profundidade é construída a cada quadro, inicializando o mip 0 com uma pré-passagem de profundidade opaca e aplicando reduções por máximo nos níveis seguintes. A pré-passagem opaca inclui deliberadamente apenas oclusores sólidos — o solo e proxies individuais dos troncos das árvores — porque a folhagem com teste de alfa criaria lacunas capazes de enganar uma redução por máximo.
O bug era geométrico, não lógico. O proxy do tronco era uma caixa de 0,5 m por 4 m por 0,5 m. A 30 m, ela ocupa cerca de 17 pixels na tela. Mas um cluster típico de grama a 50 m seleciona o mip 5, no qual cada texel cobre 32 pixels da imagem original. Um tronco de 17 pixels não cobre por completo nem mesmo um único texel do mip 5, portanto todo texel que toca o tronco também toca o solo ao redor. A primeira redução por máximo de 2×2 seleciona o maior valor de profundidade, que corresponde ao solo mais distante atrás do tronco, e a profundidade do tronco é apagada já na primeira redução. Ao chegar ao mip 5, a pirâmide contém a profundidade do solo em quase toda parte; o cluster nunca está além do solo e, portanto, nada é ocultado.
A correção é tornar o proxy grande o bastante para dominar os texels em que aparece, dimensionando-o de acordo com a silhueta da árvore, e não com a madeira. Um proxy de aproximadamente 2 m por 6 m por 2 m ainda é menor do que a copa real, então folhas visíveis através de lacunas nunca são descartadas em excesso, mas ele é grande o bastante para sobreviver à redução por máximo até as distâncias relevantes, e o contador de oclusão imediatamente deixou de mostrar zero. A conclusão pode ser generalizada como uma regra para o motor de produção: qualquer objeto considerado confiável como oclusor Hi-Z precisa ser dimensionado de acordo com sua silhueta na tela, porque a eficácia do Hi-Z em cenas abertas com folhagem é determinada pela cobertura do oclusor no mip relevante, e não pela elegância da matemática do teste de profundidade. A oclusão de grama por grama não pode ocorrer de qualquer maneira, pois uma lâmina fica na mesma profundidade que o solo sob ela. Assim, os ganhos reais vêm de árvores ocultando folhagem distante e de árvores ocultando outras árvores.
Tecnologias mencionadas neste capítulo
LUTs de atmosfera Hillaire 2020. Uma tabela de transmitância — a luz solar através do perfil de Rayleigh, Mie e ozônio — recalculada apenas quando o sol se move, combinada a uma tabela de visualização do céu calculada a cada quadro com uma parametrização não linear do horizonte, produz cores fisicamente baseadas para o céu e o sol que acompanham o horário do dia sem um gradiente ajustado manualmente. Uma aproximação analítica do espalhamento múltiplo substitui a tabela completa até que algum artefato exija o cálculo correto. O pôr do sol surge naturalmente da física, sem ajustes específicos para cada horário do dia.
Nuvens volumétricas Schneider Nubis. Uma marcha de raios através de uma camada horizontal, cuja forma é definida por uma textura Perlin-Worley de 128³ gerada na inicialização e erodida por uma textura Worley de 32³, com iluminação baseada na extinção pela lei de Beer, uma fase de lóbulo duplo e um termo de powder. Amostrar a cor solar das nuvens a partir da mesma tabela de transmitância em cada passo faz a iluminação acompanhar gratuitamente o nascer e o pôr do sol. A marcha de raios em meia resolução custa aproximadamente quatro vezes menos do que em resolução completa, sem perda visível de qualidade nas distâncias típicas, o equilíbrio padrão em produção.
Composição de WebGPU bruto com three.js na r184. Desenhar o céu na swap chain e renderizar a geometria do three.js por cima com autoClear = false não funciona porque a flag não controla a operação de carregamento de cor no backend WebGPU. Renderize o three.js em um alvo RGBA16F fora da tela e faça o mix final, seguido pelo mapeamento de tons e pela conversão para sRGB, em uma passagem que controle a swap chain.
Descarte por oclusão Hi-Z e dimensionamento de oclusores. Uma pirâmide de profundidade construída por reduções por máximo permite que uma passagem de descarte na GPU rejeite clusters cujo ponto mais próximo esteja atrás do oclusor mais distante em seu retângulo na tela. O teste silenciosamente não faz nada se os oclusores forem pequenos demais para dominar um texel no mip selecionado, porque a primeira redução por máximo substitui a profundidade do oclusor pela do fundo mais distante atrás dele. Os oclusores devem ser dimensionados de acordo com sua silhueta na tela, não com seu núcleo físico. Consulte LOD controlado pela GPU.
Parte 22 de 29. Anterior: Parte 21 — Um renderizador mais rápido que não era mais rápido Próxima: Parte 23 — Cinquenta avatares e uma voz na sala Guia da série: /pt-BR/blog/2026-02-25-open-world-browser-series-guide