Programação de vento WebGL e GPU. Palestra no FrontTalks 2018

Para renderizar gráficos complexos em páginas da Web, existe uma Web Graphics Library, abreviada como WebGL. O designer de interface Dmitry Vasiliev falou sobre a programação da GPU do ponto de vista de um designer de layout, sobre o que é o WebGL e como resolvemos o problema de visualizar grandes dados climáticos usando essa tecnologia.


- Estou desenvolvendo interfaces no escritório de Yandaterinburg em Yandex. Comecei no grupo Sport. Estávamos desenvolvendo projetos especiais de esportes quando havia campeonatos mundiais de hóquei, futebol, Olimpíadas, Paraolimpíadas e outros eventos legais. Também trabalhei no desenvolvimento de resultados de pesquisa especiais, dedicados à nova faixa de Sochi.








Link do slide

Além disso, em um capacete e meio, reiniciámos o serviço Trabalho com Erros. E então o trabalho começou em Pogoda, onde eu estava envolvido no suporte à operacionalidade da API, seu desenvolvimento, escrevendo a infraestrutura em torno dessa API e escrevendo os binders de nó para as fórmulas de aprendizado de máquina treinadas.



Então o trabalho começou mais interessante. Participou do redesenho de nossos serviços meteorológicos. Desktops, carrinhos de mão.





Depois de colocar as previsões padrão em ordem, decidimos fazer a previsão que ninguém tem. Esta previsão foi a previsão para o movimento da precipitação nos territórios.



Existem radares meteorológicos especiais que detectam precipitação em um raio de 2000 km, eles sabem sua densidade e distância até eles.



Usando esses dados e prevendo com a ajuda do aprendizado de máquina de seus movimentos adicionais, fizemos uma visualização no mapa. Você pode ir e voltar.


Link do slide

Vimos as opiniões das pessoas. As pessoas gostaram. Todos os tipos de memes começaram a aparecer, e havia fotos legais quando Moscou estava inundando o inferno.

Como todos gostaram do formato, decidimos seguir em frente e dedicar a seguinte previsão ao vento.



Os serviços que mostram a previsão de vento já estão lá. Este é um par de classe que se destacam.



Olhando para eles, percebemos que queremos fazer o mesmo - ou pelo menos não pior.

Portanto, decidimos visualizar partículas que se movem suavemente no mapa, dependendo da velocidade do vento, e deixar para trás algum tipo de loop para que possam ser vistas, a trajetória do vento.

Como já somos ótimos e fizemos um mapa legal com precipitação usando tela 2D, decidimos fazer o mesmo com as partículas.



Após consultar o designer, percebemos que precisamos preencher cerca de 6% da tela com partículas para ter um efeito legal.

Para desenhar um número tão grande de partículas usando a abordagem padrão, tivemos um tempo mínimo de 5 ms.



Se você acha que ainda precisamos mover as partículas e trazer algum tipo de beleza, como desenhar a cauda das partículas, podemos assumir que cairemos por um tempo mínimo de 40 ms para mostrar uma animação suave, a fim de produzir pelo menos 25 quadros por segundo.

O problema é que aqui cada partícula seria processada sequencialmente. Mas e se você processá-los em paralelo?

Uma clara distinção entre a operação dos processadores central e gráfico foi mostrada por "Legend Destroyers" em uma das conferências. Eles lançaram uma máquina na qual estava instalado um marcador de paintball, cuja tarefa era desenhar um smiley de uma cor. Em cerca de 10 segundos, ele desenhou essa imagem. ( Link para o vídeo - aprox.)







Então os caras lançaram uma canoa, que é uma GPU, e alguns espetos pintaram Mona Lisa. É assim que a velocidade de computação da CPU e da GPU é diferente.











Para aproveitar esses recursos em um navegador, a tecnologia WebGL foi inventada.

O que é isso Com essa pergunta, entrei na Internet. Adicionando algumas palavras com animação de partículas e vento, encontrei alguns artigos.


Links do slide: primeiro , segundo

Uma delas é uma demonstração de Vladimir Agafonkin, um engenheiro da Mapbox, que criou o vento no WebGL e se referiu ao blog de Chris Wellons, que falou sobre como mover e armazenar o estado das partículas na GPU.

Nós pegamos e copiamos. Esperamos esse resultado. Aqui as partículas se movem sem problemas.



Nós conseguimos não entender o que.



Tentando descobrir o código. Melhorando, obtendo novamente um resultado insatisfatório. Subimos ainda mais fundo - temos chuva em vez de vento.



Ok, nós decidimos fazer isso sozinhos.



Para trabalhar com o WebGL, existem estruturas. Quase todos eles visam trabalhar com objetos 3D. Não precisamos desses recursos 3D. Nós só precisamos desenhar uma partícula e movê-la. Portanto, decidimos fazer tudo com as mãos.



Atualmente, existem duas versões da tecnologia WebGL. A segunda versão, que é legal, possui uma versão moderna da linguagem de programação na qual o programa é executado no adaptador gráfico, pode executar cálculos diretamente, e não apenas desenhar. Mas tem pouca compatibilidade.



Bem, decidimos usar o antigo e comprovado WebGL 1, que tem bom suporte além do Opera Mini, do qual ninguém precisa.



WebGL é uma coisa de dois componentes. Este é o JS que executa o estado dos programas que são executados na placa gráfica. E existem componentes que são executados diretamente na placa gráfica.

Vamos começar com JS. WebGL é apenas o contexto apropriado para o elemento canvas. Além disso, ao receber esse contexto, não é apenas um objeto específico que é alocado, os recursos de ferro são alocados. E se rodarmos algo bonito no WebGL em um navegador e decidirmos executar o Quake, é bem possível que esses recursos sejam perdidos, o contexto seja perdido e todo o programa seja interrompido.



Portanto, ao trabalhar com o WebGL, você também deve ouvir a perda de contexto e poder recuperá-la. Portanto, enfatizei que o init é.



Além disso, todo o trabalho de JS se resume a coletar programas executados na GPU, enviando-lhes uma placa gráfica, definindo alguns parâmetros e dizendo "desenhar".



No WebGL, se você olhar para o próprio elemento de contexto, verá várias constantes. Essas constantes se referem a endereços na memória. Eles não são realmente constantes no processo do programa. Como se o contexto for perdido e restaurado novamente, outro pool de endereços poderá ser alocado e essas constantes serão diferentes para o contexto atual. Portanto, quase todas as operações no WebGL no lado JS são executadas por meio de utilitários. Ninguém quer fazer o trabalho rotineiro de encontrar endereços e outros tipos de lixo.



Voltamos ao que é executado na própria placa de vídeo - um programa que consiste em dois conjuntos de instruções escritas em uma linguagem semelhante a C GLSL. Essas instruções são chamadas shader de vértice e shader de fragmento. Um programa é criado a partir desse par.



Qual é a diferença entre esses shaders? O sombreador de vértice define a superfície na qual algo deve ser desenhado. Depois de definir o primitivo, que deve ser pintado, o shader de fragmento que cai nesse intervalo é chamado.





No código, fica assim. O sombreador possui uma seção para declarar variáveis ​​definidas externamente a partir de JS, seu tipo e nome são determinados. Bem como a seção principal, que executa o código necessário para esta iteração.

Espera-se que um shader de vértice na maioria dos casos defina a variável gl_Position para alguma coordenada no espaço quadridimensional. Isso é x, y, z e a largura do espaço, o que não é muito necessário saber no momento.

O sombreador de fragmento espera definir a cor de um pixel específico.

Neste exemplo, temos a cor do pixel selecionada na textura conectada.



Para transferir isso para JS, basta envolver o código fonte dos shaders nas variáveis.



Além disso, essas variáveis ​​são transformadas em shaders. Este é um contexto do WebGL, criamos shaders a partir dos códigos-fonte, criamos um programa em paralelo e anexamos alguns shaders ao programa. Temos um programa viável.

No caminho, verificamos que a compilação dos shaders foi bem-sucedida, que o programa foi construído com sucesso. Dizemos que você precisa usar este programa, porque pode haver vários programas para diferentes valores de renderização.

Configure e diga desenhar. Acontece algum tipo de imagem.



Subiu mais fundo. No shader de vértice, todos os cálculos são executados no espaço de -1 a 1, independentemente do tamanho do seu ponto de saída. Por exemplo, o espaço de -1 a 1 pode ocupar a tela inteira 1920 x 1080. Para desenhar um triângulo no centro da tela, é necessário desenhar uma superfície que cubra a coordenada 0, 0.



O sombreador de fragmento trabalha no espaço de 0 a 1 e as cores são exibidas por quatro componentes: R, G, B, Alfa.

Usando CSS como exemplo, você pode encontrar uma notação de cor semelhante ao usar porcentagens.



Para desenhar algo, você precisa dizer quais dados precisam ser desenhados. Especificamente para um triângulo, definimos uma matriz digitada de três vértices, cada um consistindo em três componentes, x, y e suficiente.

Nesse caso, o sombreador de vértice parece obter o par atual de pontos, coordenadas e definir essa coordenada na tela. Aqui, como é, sem transformações, colocamos um ponto na tela.



O shader de fragmento pode colorir as constantes passadas de JS com cores, também sem cálculos adicionais. Além disso, se algumas variáveis ​​no shader de fragmento forem transferidas do exterior ou do shader anterior, a precisão deverá ser especificada. Nesse caso, a precisão média é suficiente e quase sempre é suficiente.



Passamos para JS. Atribuímos os mesmos shaders a variáveis ​​e declaramos uma função que criará esses shaders. Ou seja, um sombreador é criado, a fonte é despejada nele e, em seguida, compilada.



Fazemos dois shaders, vértice e fragmento.



Depois disso, crie um programa, faça o upload de shaders já compilados para ele. Ligamos o programa porque os shaders podem trocar variáveis ​​entre si. E, neste estágio, a correspondência dos tipos de variáveis ​​que esses shaders trocam é verificada.

Dizemos que usamos este programa.



Em seguida, criamos uma lista de vértices que queremos visualizar. O WebGL possui um recurso interessante para algumas variáveis. Para alterar um tipo de dados específico, você precisa definir o contexto global para editar o array_buffer e fazer o upload de algo para este endereço. Não há atribuição explícita de nenhum dado para uma variável. Tudo é feito através da inclusão de algum contexto.

Também é necessário estabelecer as regras para leitura deste buffer. Pode-se observar que especificamos uma matriz de seis elementos, mas o programa precisa explicar que cada vértice consiste em dois componentes, cujo tipo é float, isso é feito na última linha.



Para definir a cor, o programa procura o endereço para a variável u_color e define o valor para essa variável. Definimos a cor, vermelho 255, 0,8 do verde, 0 azul e um pixel completamente opaco - ele fica amarelo. E dizemos que para executar este programa usando as primitivas triangulares, no WebGL você pode desenhar pontos, linhas, triângulos, triângulos de forma complexa e assim por diante. E faça três picos.



Você também pode especificar que a matriz sobre a qual estamos renderizando deve ser contada desde o início.


Link do slide

Se você complicar um pouco o exemplo, poderá adicionar uma dependência de cores na posição do cursor. Ao mesmo tempo, o fps passa pelo telhado.



Para desenhar partículas em todo o mundo, você precisa conhecer a velocidade do vento em todos os pontos deste mundo.

Para ampliar e de alguma forma mover o mapa, você precisa criar contêineres que correspondam à posição atual do mapa.

Para mover as próprias partículas, você precisa criar um formato de dados que possa ser atualizado usando uma GPU. Faça o desenho em si e desenhe o laço.



Fazemos todos os dados através da textura. Utilizamos 22 canais para determinar as velocidades horizontal e vertical, onde a velocidade zero do vento corresponde ao meio da faixa de cores. É 128 aproximadamente. Como a velocidade pode ser negativa e positiva, definimos a cor em relação ao meio do intervalo.

Acontece que essa imagem.



Para carregá-lo em um cartão, precisamos cortá-lo. Para conectar a imagem ao mapa, usaremos a ferramenta Yandex.Map Layer padrão, na qual determinamos o endereço a partir do qual cortar os ladrilhos e adicionamos essa camada ao mapa.


Link do slide

Temos uma imagem em que a desagradável cor verde é codificada como velocidade do vento.



Em seguida, você precisa obter um local onde desenharemos a animação em si, enquanto esse local deve corresponder às coordenadas do mapa, seus movimentos e outras ações.

Por padrão, podemos assumir que usaríamos a Camada, mas a Camada do cartão cria uma tela a partir da qual captura imediatamente o contexto 2D que pode capturar. Mas se tentarmos extrair da tela, que já possui um contexto de um tipo diferente, e extrairmos um contexto GL, obtemos nulos como resultado. Se você acessá-lo, o programa trava.


Link do slide

Portanto, usamos o painel, esses são contêineres para layouts e adicionamos nossa tela a partir da qual já pegamos o contexto que precisávamos.



Para organizar de alguma forma as partículas na tela e poder movê-las, foi utilizado o formato da posição das partículas na textura.

Como isso funciona? Uma textura quadrada é criada para otimização, e aqui o tamanho do seu lado é conhecido.



Ao desenhar as partículas em ordem e saber o número de série da partícula e o tamanho da textura em que estão armazenadas, você pode calcular um pixel específico no qual a posição na tela real é codificada.





No próprio sombreador, parece ler o índice renderizado, a textura com a posição atual das partículas e o tamanho do lado. Em seguida, determinamos as coordenadas x, y para esta partícula, lemos esse valor e decodificamos. Que tipo de mágica é essa: rg / 255 + ba?

Para a posição das partículas, usamos 20 canais duplos. O canal de cores tem um valor de 0 a 255 e, para a tela 1080, não podemos colocar as partículas em nenhuma posição da tela nem um pouco, porque podemos colocar a partícula com no máximo 255 pixels. Portanto, em um canal armazenamos o conhecimento de quantas vezes uma partícula passou 255 pixels e, no segundo canal, armazenamos o valor exato de quanto passou depois.

Em seguida, o sombreador de vértice deve converter esses valores em seu espaço de trabalho, ou seja, de -1 em 1 e definir esse ponto na exibição.



Para olhar apenas nossas partículas, basta pintá-las de branco. O GLSL possui um nível de açúcar que, se definirmos o tipo de uma variável e a passarmos para uma constante, essa constante será distribuída pelos quatro componentes, por exemplo.



Tendo desenhado esse programa, vemos um conjunto de quadrados idênticos. Vamos tentar adicionar beleza a eles.



Primeiro, adicione a dependência desses quadrados à velocidade atual do vento. Simplesmente lemos a velocidade atual e as texturas correspondentes para cada partícula. Obtemos o comprimento do vetor, que corresponde à velocidade absoluta no ponto, e adicionamos essa velocidade ao tamanho da partícula.



Além disso, para não desenhar os quadrados, no shader de fragmento, cortamos todos os pixels que ficam fora do raio, que não estão incluídos no raio do círculo inscrito. Ou seja, nosso shader se torna uma coisa dessas.



Calculamos a distância do pixel renderizado do centro. Se exceder metade do espaço, não o mostraremos.



Temos uma imagem mais diversificada.

Em seguida, você precisa mover de alguma forma essas coisas. Como o WebGL 1 não sabe calcular algo, trabalha diretamente com dados, aproveitaremos a capacidade de renderizar programas em componentes especiais, buffers de quadro.

Os buffers de quadro podem ser mapeados, por exemplo, para texturas que podem ser atualizadas. Se o buffer do quadro não for declarado, o desenho por padrão será executado na tela.

Mudando a saída de uma textura de posição para outra, podemos atualizá-los um por um e usá-los para renderização.





O procedimento para atualizar a posição em si é assim: leia a posição atual, adicione-a ao vetor de velocidade atual e adicione-a, codifique-a em uma nova cor.



No código, parece ler a posição atual, decodificar, ler a velocidade atual, trazer a velocidade ao normal, dobrar os dois componentes, codificar em cores.



Acontece que essa imagem. O estado das partículas está mudando constantemente e aparece algum tipo de animação.

Se você executar essa animação por 5 a 10 minutos, ficará claro que todas as partículas chegarão ao seu destino final. Todos eles deslizam para dentro do funil. Você fica com essa foto.



Para evitar isso, introduzimos um fator de permutação de partículas em um local aleatório.

Depende da velocidade atual do vento, da posição atual das partículas e do número aleatório que transmitimos do JS - porque a primeira versão do WebGL não possui uma função de randomização ou algum tipo de função de ruído.



Neste exemplo, calculamos a posição prevista da partícula, a posição aleatória e selecionamos uma ou outra, dependendo do fator de redefinição.


Links do slide: primeiro , segundo , terceiro , quarto

Para entender o que estava no último slide, você pode ler estes artigos. O primeiro fornece um enorme impulso para entender o que o WebGL fornece, em que consiste e como não cometer erros. Na Khronos, este é um grupo que se dedica ao desenvolvimento do padrão, há uma descrição de todas as funções.



O último ponto da nossa tarefa é desenhar um traço de partículas. , , , , , , , .



.





WebGL 2D canvas, . 64 . 2D canvas, 25 , WebGL 0,3 . .

, WebGL , , .

, , , - break points, - , . WebGL — .



, . , Firefox «», WebGL-, , , . , .



A segunda ferramenta que facilita muito a vida é a extensão do navegador Spector.js. Ele também captura a tela do contexto WebGL e permite ver todas as operações executadas nessa tela, tempos e variáveis ​​transmitidas.



No total, durante uma semana de trabalho, do zero, conseguimos uma solução pronta para o uso ao vento. Espero ter conseguido dizer em que tipo de tecnologia é, WebGL, em que consiste e dar um exemplo real de seu uso no produto. Isso é tudo.

Source: https://habr.com/ru/post/pt433278/


All Articles