Mistura alfa atrasada

Neste artigo, quero falar sobre métodos para misturar geometria rasterizada. Os modelos clássicos de mistura de objetos translúcidos - Alfa, Aditivo e Multiplicativo - são unidos pelo mesmo princípio de desenho: desenha sequencialmente uma primitiva após a outra, misturando os pixels recebidos na saída do shader de fragmento com o que está no buffer atual. Cada nova primitiva atualiza a área do buffer na qual ele é desenhado; no caso da mistura alfa, os objetos que são mais altos do que os que foram desenhados anteriormente. Mas e se você quiser fazer algo com um grupo de objetos desenhados no topo da cena - por exemplo, recorte-os por máscara ou realce-os? Aqui duas decisões vêm à mente imediatamente: ou faça alterações em seu material (ou seja, altere o sombreador, expanda o conjunto de texturas), por exemplo, adicione uma projeção de outra textura,que será responsável pela máscara de transparência. No entanto, se tivermos muitos objetos manchados, alterar cada material exclusivo é inconveniente e cheio de erros. A segunda opção é atrair todos os objetos de interesse para um destino de tela cheia separado e desenhá-lo já na cena final. Aqui podemos fazer o que quisermos com seu conteúdo, mas isso requer a alocação de memória extra e, o mais desagradável, alternar a renderização do destino. Esta não é a operação “mais barata” em dispositivos móveis, que precisará ser executada duas vezes. E se você quiser trabalhar com várias camadas como essa?Aqui, podemos fazer o que quisermos com seu conteúdo, mas isso requer a alocação de memória extra e, o mais desagradável, alternar a renderização do destino. Esta não é a operação “mais barata” em dispositivos móveis, que precisará ser executada duas vezes. E se você quiser trabalhar com várias camadas como essa?Aqui, podemos fazer o que quisermos com seu conteúdo, mas isso requer a alocação de memória extra e, o mais desagradável, alternar a renderização do destino. Esta não é a operação “mais barata” em dispositivos móveis, que precisará ser executada duas vezes. E se você quiser trabalhar com várias camadas como essa?



Existe outra maneira, mais simples e elegante de resolver esses problemas. Podemos pintar a cena ao contrário!

Uma pequena digressão para lembrá-lo como o método de renderização clássico funciona
- Alpha Blending , , , . 4 RGBA, RGB — A(Alpha) — ( ). . :


ColorSrc — RGB , ( , ), ColorDst — , , Color_Result — , , , . Variable1 Variable2, ? , ( , ). , : , ... 

:


AlphaSrc — - (), OneMinusAlphaSrc, , 1.0 — AlphaSrc. : 1 * + 2 * (1 — ). alpha (). = 1, , = 0, . OpenGL .

OpenGL ES 2.0 — .

Como a imagem é formada em etapas: primeiro desenhamos um plano de fundo, depois desenhamos todos os objetos em camadas, um após o outro. O que é renderizado pela última vez substitui os pixels anteriores: 





Qual é o truque? 


A essência da tecnologia de renderização reversa ou, como também pode ser chamada, mistura atrasada é a seguinte. Desenhamos a cena para trás usando uma fórmula de mistura diferente. Além disso, a imagem final permanecerá exatamente a mesma da abordagem clássica.

Como funciona?


O método de mistura através do canal de transparência da imagem que desenhamos foi descrito acima. Agora, inverteremos o caminho: usaremos a transparência dos pixels já desenhados (ou melhor, misturaremos a transparência desenhada com a já desenhada). Ou seja, em vez de AlphaSrc, usaremos AlphaSaturate e, em vez de OneMinusAlphaSrc - One. Acontece que, se já houver algo com transparência = 1 no buffer, a contribuição será zero e a cor desse pixel não será alterada. Se houver transparência zero, vamos adicionar as duas cores (para isso, precisaremos limpar o buffer do quadro com zeros ou preto com transparência zero). Com essa adição, a cor resultante será igual à desenhada. A fórmula final é assim:
 

(aprox. AlphaSaturate = min (AlphaSrc, 1 - AlphaDst))

Os valores de transparência precisam ser adicionados: ele deve acumular camada por camada, ou seja, teremos Um e Um nas variáveis ​​de mixagem do canal alfa. Por que não modificamos o ColorDst e limpamos o buffer com zeros? Isso é necessário para a mistura de aditivos, o AdditiveBlending será diferente apenas porque terá zero na variável AlphaSrc. Não deve modificar a transparência, apenas a cor.

Para maior clareza, o esquema de renderização reversa é assim: 

Primeiro, limpamos o buffer de quadros. Em seguida, definimos a função de mistura dada acima e começamos a desenhar a partir dos objetos mais altos (na abordagem clássica, eles seriam desenhados por último), descendo para os inferiores. A imagem de fundo será desenhada por último.


Como isso pode ser usado?


Descreverei várias tarefas resolvidas por esse método, usando nosso projeto como exemplo:

  1. Cortar objetos por máscara com transparência. Recorte suave da sala de jogos:


    Depois de desenhar o campo de jogo, basta limpar a transparência nos locais da imagem que queremos ocultar. Isso é feito usando a fórmula de mistura, na qual o objeto de máscara desenhado substitui a cor e a transparência inversamente por sua própria transparência, e o grau de limpeza pode ser ajustado continuamente. Nesse caso, a seguinte geometria é usada para recorte:


    Ele muda de forma à medida que a câmera se move entre as salas. A fórmula de mistura para limpeza é a seguinte:

    ColorSrc = GL_ZERO,
    ColorDst = GL_ONE_MINUS_SRC_ALPHA,
    AlphaSrc = GL_ZERO,
    AlphaDst = GL_ONE_MINUS_SRC_ALPHA

    Você pode usar qualquer geometria com qualquer textura, iniciando a limpeza da camada a partir da qual você precisará:

  2. O desaparecimento suave do campo é feito da mesma forma. O preço de emissão é de um DrawCall.
  3. :


    , UI, . , «» , , « », . , : , . :

    ColorSrc = GL_SRC_ALPHA,
    ColorDst = GL_ONE_MINUS_SRC_ALPHA,
    AlphaSrc = GL_ZERO,
    AlphaDst = GL_ONE
  4. , :


  5. «»:



    . , . — 2 DrawCalls.



Ambient occlusion





Há uma desvantagem, ou melhor, restrições: nem todas as misturas podem ser repetidas para essa técnica. A mistura alfa e o aditivo são definitivamente possíveis, mas é necessário adaptar ou não usar suas próprias misturas especiais. Mas há uma saída: você pode separar os estágios de renderização da cena. Parte disso deve ser feito pelo método reverso, parte do método usual, e foi o que fizemos para efeitos especiais no topo do campo e no pós-processo.

Um ponto importante nas técnicas de renderização aditiva e mista: se ele for desenhado ANTES da passagem de renderização reversa e se não houver informações de transparência na textura (textura como “mancha branca em fundo preto”), esse objeto substituirá a transparência. Na passagem de "retorno", as informações sobre essa área serão perdidas e, visualmente, parecerão um "quadrado escuro" ou uma borda preta em torno de um ponto aditivo claro:


Isso pode ser superado modificando a mistura aditiva em termos de mistura do canal alfa:

AlphaSrc = GL_ONE_MINUS_DST_ALPHA
AlphaDst = GL_ONE

Mas isso não é adequado para todos os tipos de mistura e será mais confiável modificar a própria textura. O que se entende:

Se houver texturas no formulário: 


Então você precisa fazer um deles:


Ou seja, o brilho dos canais de cores deve ser convertido em transparência e esticar as cores inversamente com transparência. A textura resultante e antiga deve ter a mesma aparência em um fundo preto. Manualmente, é improvável que isso tenha êxito, faz sentido criar um conversor automático. Nesse caso, o pseudo-código de conversão de canal terá a seguinte aparência:

RGB_old = Texel_in.rgb
A_old = Texel_in.a
A_middle = 1.0 / ((RGB_old) / 3.0) * A_old // linear color space
RGB_new = RGB_old * A_middle;
A_shift = minimum( 1.0 / RGB_new.r, 1.0)
A_shift = minimum( 1.0 / RGB_new.g, A_shift)
A_shift = minimum( 1.0 / RGB_new.b, A_shift)
RGB_result = RGB_new * A_shift; 
A_result = (RGB_result) / 3.0)
Texel_out = Vector4(RGB_result, A_result)



Aqui vou seguir as etapas de renderização da cena do nosso projeto
 
  1. . , -.

  2. , , , «» :


  3. UI:

  4. , , , :

  5. :

  6. :

  7. , .




Conclusão


O método torna possível trabalhar de maneira simples e barata com as camadas da cena que estão sendo desenhadas, usando o canal alfa como uma máscara. É relativamente simples implementá-lo em um projeto já em funcionamento: não requer modificações profundas no código do subsistema gráfico, basta alterar a ordem de renderização e a fórmula de mistura. Em alguns lugares, ele pode economizar significativamente o desempenho. Existem limitações, mas na maioria dos casos você pode chegar a um acordo com elas.

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


All Articles