Pular para o conteúdo
Renderização · Etapas do pipelineIntermediário

Operações por amostra

Os testes e combinações fixos entre a saída do shader de fragmento e o pixel que de fato muda.

Demo ao vivo em breve

Como funciona

Quando o shader de fragmento devolve uma cor, nada mudou na tela ainda. O fragmento ainda passa por uma fila de etapas de função fixa que decidem se ele vai parar no pixel e como se combina com o que o pixel já guarda. A especificação do OpenGL ES as chama de operações por fragmento; elas trabalham sobre cada amostra do pixel, o que faz diferença quando o multisampling está ligado.

Nenhuma delas é escrita em GLSL. O código host as liga e configura (gl.enable, gl.depthFunc, gl.blendFunc e companhia) antes da chamada de desenho, e elas valem para tudo o que essa chamada produzir.

A ordem

A especificação do OpenGL ES 3.0 as lista nesta ordem (seção 4.1):

  1. Posse do pixel: o pixel pertence a este contexto (uma janela coberta, por exemplo, pode não pertencer).
  2. Teste de tesoura (scissor): descarta fragmentos fora de um retângulo, definido com gl.scissor(). Um recorte barato para painéis de interface e tela dividida.
  3. Operações de multisample: com MSAA, decidem quais amostras do pixel o fragmento cobre. Com gl.SAMPLE_ALPHA_TO_COVERAGE ligado, o alfa da saída também remove amostras, transformando alfa em cobertura com borda suave.
  4. Teste de stencil: compara o valor do pixel no buffer de stencil, um inteiro pequeno por amostra, com uma referência, e o atualiza. É uma máscara que a própria cena desenha: portais, espelhos, contornos desenhados só do lado de fora de um objeto.
  5. Teste de profundidade: compara a profundidade do fragmento com a guardada e fica só com a mais próxima (veja o buffer de profundidade).
  6. Blending: combina a cor do fragmento com a cor já guardada, em vez de substituí-la (veja blending).
  7. Conversão para sRGB e dithering: codificam a cor para um alvo sRGB, se houver, e opcionalmente aplicam dithering para a profundidade de bits do alvo.

Aí o resultado é escrito, passando pelas máscaras de escrita: gl.colorMask() pode bloquear canais isolados, e gl.depthMask(false) mantém o teste de profundidade funcionando sem mexer na profundidade guardada. Um fragmento que falha em qualquer teste, ou que o shader descartou, não escreve nada.

Configurando

Um quadro típico liga e desliga essas etapas entre chamadas de desenho. Primeiro a geometria opaca, com profundidade testada e escrita; depois a transparente, com profundidade testada mas não escrita, misturada por cima:

// passada opaca
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.depthMask(true);
gl.disable(gl.BLEND);
drawOpaque();

// passada transparente: escondida por paredes na frente, mas não esconde o que está atrás dela
gl.depthMask(false);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
drawTransparent();

Por amostra, e cedo

Com MSAA, cada pixel guarda várias amostras, e o shader de fragmento continua rodando uma vez por pixel para cada triângulo. Os testes e a escrita acontecem por amostra, usando a cobertura que o rasterizador encontrou, então a aresta de um triângulo corta um pixel em parte; no fim as amostras viram uma média (o resolve) numa cor só. É assim que o MSAA suaviza as bordas sem sombrear cada amostra.

A especificação descreve os testes de profundidade e de stencil rodando depois do shader, mas rodar um shader cujo resultado será jogado fora é perda de tempo, então as GPUs testam antes sempre que o resultado não pode mudar. Duas coisas no shader impedem isso: escrever gl_FragDepth, porque a profundidade só é conhecida quando o shader termina, e discard em muitas GPUs, porque o fragmento pode sumir depois do teste. Deixe os dois fora de shaders que cobrem boa parte da tela, a não ser que precise deles.

No Godot e neste site

O Godot configura essas etapas pelo material e pelo render_mode do shader, em vez de código host: blend_mix, blend_add, blend_sub, blend_mul e blend_premul_alpha para o blending; depth_draw_opaque, depth_draw_always e depth_draw_never para a escrita de profundidade; depth_test_disabled para pular o teste; alpha_to_coverage para a cobertura no MSAA. Escrever ALPHA leva o material para o pipeline transparente, desenhado depois do opaco.

Neste site, o blend e o alphaClip de uma passada no render.json escolhem uma combinação: "opaque" é testado e escrito, "alpha" e "additive" são testados e misturados mas não mexem na profundidade, e alphaClip descarta os fragmentos abaixo de um limiar.

Leitura complementar