Partículas macias no WebGL e OpenGL ES

Os sistemas de partículas são algumas das maneiras mais fáceis de tornar uma cena 3D visualmente mais rica. Em um de nossos aplicativos Android, o 3D Buddha Live Wallpaper é uma cena bastante simples, o que seria bom para adicionar um pouco mais de detalhes. E quando pensamos em como adicionar variedade à imagem, a decisão mais óbvia de preencher o espaço vazio ao redor da estátua de Buda era adicionar nuvens de fumaça ou névoa. Graças ao uso de partículas macias, conseguimos um bom resultado. Neste artigo, descreveremos em detalhes a implementação de partículas macias no WebGL / OpenGL ES puro, sem o uso de bibliotecas de terceiros e mecanismos 3D prontos.

A diferença entre o aplicativo antigo e o atualizado superou nossas expectativas. Partículas simples de fumaça melhoraram significativamente a cena, tornando-a mais rica e completa. Os sopros de fumaça são detalhes adicionais que chamam a atenção, além de uma maneira de tornar a transição entre os objetos principais e o fundo mais suave:



Partículas macias


Então, quais são essas partículas macias? Você deve se lembrar que em muitos jogos antigos (Quake 3 e CS 1.6) os efeitos da fumaça e das explosões tinham limites planos muito claros na interseção de partículas com uma geometria diferente. Todos os jogos modernos não têm mais esses artefatos devido ao uso de partículas macias - isto é, partículas com bordas embaçadas e “macias” ao redor de objetos adjacentes.

Renderização


O que é necessário para tornar as partículas macias? Primeiro, precisamos de informações sobre a profundidade da cena para determinar a interseção de partículas com outros objetos e amolecê-las. Então, precisamos determinar a interseção da cena e das geometrias de partículas comparando a profundidade da cena e das partículas no sombreador do fragmento - interseções em que as profundidades são iguais. A seguir, veremos o processo de renderização passo a passo. Ambas as implementações de cena para o Android OpenGL ES e WebGL são as mesmas, a principal diferença está apenas no carregamento de recursos. A implementação no WebGL é de código aberto e você pode obtê-lo aqui - https://github.com/keaukraine/webgl-buddha .

Renderização de mapa de profundidade


Para renderizar um mapa de profundidade da cena, primeiro precisamos criar texturas para o mapa de profundidade e cor e atribuí-las a um FBO específico. Isso é feito no método initOffscreen () no arquivo BuddhaRenderer.js .
A renderização real dos objetos de cena em um mapa de profundidade é realizada no método drawDepthObjects () , que desenha uma estátua de Buda e uma planta baixa . No entanto, há um truque para melhorar o desempenho. Como nesse estágio de renderização, não precisamos de informações de cores, mas apenas a profundidade, a renderização no buffer de cores é desativada chamando gl.colorMask (false, false, false, false) e ativada novamente chamando gl.colorMask (true, true, true, verdadeiro) . GlcolorMask () funçãopode ativar e desativar a gravação dos componentes vermelho, verde azul e alfa separadamente, portanto, para desativar completamente a gravação no buffer de cores, definimos todos os componentes como false e os ativamos para renderização na tela, expondo todos a true. O resultado da renderização na textura de profundidade pode ser visto descomentando a chamada para drawTestDepth () no método drawScene () . Como a textura do mapa de profundidade possui apenas um canal, ela é percebida assim que os canais vermelho, azul e verde são zero. Uma visualização do mapa de profundidade de nossa cena é assim:


Renderização de partículas


O código do sombreador usado para renderizar as partículas está localizado no arquivo SoftDiffuseColoredShader.js . Vamos ver como isso funciona.

A idéia principal de encontrar a interseção das geometrias de partículas e cenas é comparar o valor da profundidade do fragmento atual com o valor armazenado no mapa de profundidade.

O primeiro passo na comparação de profundidades é linearizar as profundidades, já que os valores originais são exponenciais. Isso é feito usando a função calc_depth () . Essa técnica é bem descrita aqui - https://community.khronos.org/t/soft-blending-do-it-yourself-solved/58190 . Para os valores de linearização, precisamos da variável Uniform vec2 uCameraRangecujos componentes xey contêm os valores dos planos de recorte próximo e distante da câmera. Em seguida, o sombreador calcula a diferença linear entre a profundidade da partícula e a cena - esse valor é armazenado na variável a. No entanto, se aplicarmos esse valor à cor do fragmento, obteremos partículas muito transparentes - a cor desaparecerá linearmente de qualquer geometria atrás da partícula e desaparecerá rapidamente. É assim que a visualização da diferença linear de profundidade é exibida (é possível descomentar a linha de código correspondente no shader e vê-la):


Para tornar as partículas mais transparentes apenas perto do limite da interseção (na região de a = 0), aplicamos a função GLSL smoothstep () ao valor da variável a com o valor de transição de 0 ao coeficiente especificado no uniforme uTransitionSize , que determina a largura da transição transparente. Se você quiser aprender mais sobre a função smoothstep () e ver alguns exemplos interessantes de seu uso, recomendamos a leitura deste artigo - http://www.fundza.com/rman_shaders/smoothstep/ . O coeficiente final é armazenado na variável b. Para o modo de mistura de cores usado em nossa cena, basta multiplicar a cor da partícula retirada da textura por esse coeficiente; em outras implementações de partículas, pode ser necessário alterar, por exemplo, apenas o canal alfa. Se você descomentar a linha de código no shader para visualizar esse coeficiente, o resultado será semelhante a este:


Comparação de vários valores do coeficiente de "suavidade" das partículas:

Otimização de renderização Sprite


Nesta cena, pequenas partículas de poeira são desenhadas como sprites pontuais (primitivas como GL_POINTS ). Esse modo é conveniente, pois cria automaticamente a geometria quadrada final da partícula com coordenadas de textura. No entanto, eles também têm desvantagens que tornam seu uso inadequado para grandes partículas de clubes de nevoeiro. Primeiro, eles são cortados pelos planos de recorte da matriz da câmera de acordo com as coordenadas do centro do sprite. Isso leva ao fato de que desaparecem abruptamente da vista nas bordas da tela. Além disso, a forma quadrada do sprite não é muito ideal para o shader de fragmento, pois é chamada nos locais em que a textura das partículas está vazia, o que causa um redesenho perceptível. Usamos uma forma de partícula otimizada - com bordas cortadas nos locais em que a textura é completamente transparente:


Tais modelos de partículas são comumente chamados de outdoor. Obviamente, eles não podem ser renderizados como primitivos de GL_POINTS ; portanto, cada partícula é desenhada separadamente. Isso não cria muitas chamadas drawElements , existem apenas 18 partículas de neblina em toda a cena. Eles devem ser colocados em coordenadas arbitrárias, redimensionadas, mas rotacionadas de modo a serem sempre perpendiculares à câmera, independentemente da sua posição. Isso é conseguido modificando a matriz descrita nesta resposta no StackOverflow . Há um método calculMVPMatrixForSprite () no arquivo BuddhaRenderer.js que cria matrizes MVP para modelos de outdoor. Ele realiza todas as transformações usuais de deslocamento e escala e depois usaresetMatrixRotations () para redefinir o componente de rotação da matriz de visualização de modelo antes de ser multiplicado pela matriz de projeção. A matriz resultante realiza uma transformação, como resultado do qual o modelo é sempre direcionado exatamente para a câmera.

Resultado


O resultado final pode ser visto ao vivo aqui .

Você pode aprender e reutilizar o código fonte do Github para seus projetos .

All Articles