GLSL: Center ou Centroid? Ou quando os shaders atacam

Modificando o shader para o próximo jogo, encontrei um artefato desagradável que só se manifesta quando o hardware MSAA está ativado. Na captura de tela da paisagem, você pode ver alguns pixels brilhantes demais. Os valores de cores em vários deles eram tão grandes que, após a floração, eles se transformaram em "fantasmas" multicoloridos.

imagem

Trago à sua atenção a tradução de um artigo que explica em detalhes a razão desse fenômeno e a maneira de lidar com ele.

imagem

Figura 1 - Imagens corretas (esquerda) e incorretas (direita). Preste atenção na barra amarela na borda esquerda da imagem "incorreta". Embora a variável myMixer varie de 0 a 1, de alguma forma vai além desse intervalo na imagem "incorreta".

Considere um shader de fragmento simples com uma transformação não linear simples:

smooth in float myMixer; //      . //  sqrt    . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); //    myMixer < 0.0 vec3 color = mix( blue, yellow, a ); //   gl_FragColor = vec4( color, 1.0 ); } 

De onde veio a faixa amarela à esquerda na imagem incorreta? Para entender melhor o que deu errado, vamos primeiro examinar um caso em que tudo funciona corretamente (quase) sempre.

imagem

Esta é uma rasterização clássica com uma amostra. Os quadrados cinzentos são pixels e os pontos amarelos são os centros dos pixels localizados nas coordenadas da janela meio inteira (por padrão, as coordenadas do pixel inferior esquerdo em gl_FragCoord são (0,5, 0,5) - trans. ).

imagem

Na figura acima, a linha secante separa o meio espaço do primitivo. Acima e à esquerda desta linha, a variável myMixer é positiva, e abaixo e à direita é negativa.

A rasterização clássica de uma amostra classifica os pixels de acordo com a associação primitiva e cria fragmentos apenas para pixels cujos centros estão dentro da primitiva. Neste exemplo, seis fragmentos serão produzidos, mostrados no canto superior esquerdo. Os pixels marcados em cores suaves não se enquadram no primitivo. Fragmentos não serão gerados para eles.

imagem

Verde indica os pontos nos quais o sombreador do fragmento será calculado. O valor do myMixer será calculado para o centro de cada pixel. Observe que os pontos verdes estão acima e à esquerda da linha; portanto, os valores do myMixer neles serão positivos. Todos os dados de entrada associados aos vértices (variáveis ​​de entrada ou saída) também serão interpolados nesses pontos.

Nosso sombreador simples não usa derivadas (explícitas ou implícitas, por exemplo, ao amostrar de uma textura com níveis mip), mas as derivadas dFdx (horizontal) e dFdy (vertical) são marcadas com setas. Dentro do primitivo, eles são razoavelmente bem definidos e regulares.

Para resumir: em uma única seleção, os fragmentos são gerados apenas se o centro do pixel cair "dentro" da primitiva, os dados do fragmento são calculados para o centro do pixel, a interpolação de dados de vértices e o cálculo de sombreamento são realizados apenas dentro da primitiva. Tudo está bom e "correto". (Quase sempre. Por enquanto, vamos omitir as imprecisões de algumas derivadas nos pixels ao longo da borda do primitivo).

Então, tudo é (quase) excelente na rasterização com uma seleção. Mas o que pode dar errado quando a multisampling está ativada?

imagem

Essa é uma rasterização clássica de várias amostras. Quadrados cinza indicam pixels. Pontos amarelos são centros de pixel em coordenadas meio inteiras. Nos pontos azuis, a amostragem ocorre. Este exemplo mostra um diagrama simples de duas amostras rotacionadas. Todos os argumentos podem ser generalizados para um número arbitrário de amostras.

imagem

A linha ainda separa o meio espaço do primitivo. Acima e à esquerda, o valor do myMixer é positivo. Mais baixo e à direita - negativo.

Ao rasterizar com multisampling, o classificador de pixels gerará um fragmento se pelo menos uma amostra de pixel cair dentro do primitivo.

Neste exemplo, 10 fragmentos serão gerados, mostrados no semiplano superior esquerdo. Observe os quatro fragmentos adicionados ao longo da face, nos quais uma amostra cai dentro do primitivo, embora o centro esteja fora. Os pixels fora do primitivo ainda estão marcados como escuros.

imagem

O que acontecerá ao calcular no centro do pixel?

O sombreador será calculado em pontos verdes e vermelhos para cada um dos fragmentos. Os dados associados ao myMixer são calculados no centro de cada pixel. Em pontos verdes, esses valores serão positivos, pois estão acima e à esquerda da borda. Os pontos vermelhos estão fora do primitivo, porque os valores do myMixer neles são negativos. Nos pontos vermelhos, os dados associados são extrapolados em vez de interpolação.

No nosso sombreador, os valores sqrt (myMixer) não são definidos com um myMixer negativo. Mesmo quando os valores do myMixer registrados pelo shader de vértice estão no intervalo de zero a um, no shader de fragmento, o myMixer pode ir além desse intervalo devido à extrapolação. Assim, com um myMixer negativo, o resultado do shader do fragmento não é definido.

imagem

Ainda estamos considerando calcular o sombreador no centro dos pixels, as setas na figura mostram dFdx e dFdy. Nos fragmentos internos do polígono, eles são bem definidos, pois todos os cálculos são feitos no centro dos pixels, localizados em intervalos iguais.

imagem

O que acontecerá ao calcular em outros pontos que não os centros dos pixels?

Os pontos verdes são os pontos nos quais o sombreador será calculado. O valor associado do myMixer é calculado no centróide de cada pixel.

O centróide de um pixel é o centro de gravidade da interseção do quadrado do pixel e o interior do primitivo. Para um pixel totalmente coberto, o centróide é o centro. Para um pixel parcialmente coberto, o centróide geralmente é diferente do centro.

O padrão OpenGL permite que uma implementação selecione um ponto arbitrário na interseção de um primitivo e um pixel em vez de um centróide ideal. Por exemplo, poderia ser um ponto de amostragem.

Neste exemplo, se o centro estiver dentro da primitiva, os dados do vértice serão calculados para o centro. Caso contrário, eles são computados em qualquer um dos pontos de amostra localizados dentro do primitivo. Isso acontece por quatro pixels ao longo da borda. Todos os pontos verdes ficam acima e à esquerda da borda, portanto os valores neles são sempre interpolados e nunca extrapolados.

Por que nem sempre calcular o sombreador centróide? Em geral, é mais caro que a computação no centro. No entanto, este não é o fator principal.

É tudo sobre derivativos de computação. Preste atenção nas setas entre os pontos verdes. A distância entre eles não é a mesma para diferentes pares de pontos. Além disso, y não é constante para dFdx e x não é constante para dFdy. Os derivados são menos precisos quando calculados em centróides .

Esse é um compromisso e, portanto, o OpenGL, começando com GLSL 1.20, oferece ao desenvolvedor do shader uma escolha entre o centro e o centróide usando o qualificador do centróide:

 centroid in float myMixer; //  centroid  smooth //      . //  sqrt    . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); //    myMixer < 0.0 vec3 color = mix( blue, yellow, a ); //   gl_FragColor = vec4( color, 1.0 ); } 

Quando você deve usar o centróide?

  1. Quando um valor extrapolado pode levar a resultados vagos. Preste atenção especial às funções internas, cuja descrição diz "o resultado não é definido se ..."
  2. Quando um valor extrapolado é usado com uma função muito não linear ou descontínua. Isso inclui a função de etapa ou cálculo de alargamento, especialmente quando o expoente é grande o suficiente.

Quando você não deve usar um centróide?

  1. Se você precisar de derivativos exatos. As derivadas podem ser explícitas (chamada dFdx) ou implícitas, por exemplo, amostras de texturas com níveis mip ou com filtragem anisotrópica. Na especificação GLSL, os derivados nos centróides são considerados tão inutilizáveis ​​que são declarados indefinidos. Nesses casos, tente escrever:

     centroid in float myMixer; //  ! smooth in float myCenterMixer; //     . 

  2. Se uma grade for renderizada, na qual a maioria dos limites das primitivas é interna e sempre bem definida. O exemplo mais simples é uma faixa de 100 triângulos (TRIANGLE_STRIP), na qual apenas o primeiro e o último triângulos estão sujeitos a extrapolação. O qualificador do centróide resultará em interpolação nesses dois triângulos, com o custo de perda de precisão e continuidade nos 98 triângulos restantes.
  3. Se você souber que os artefatos podem aparecer de uma função indefinida, não linear ou descontínua, mas na prática esses artefatos acabam sendo quase invisíveis. Se o sombreador não atacar - não conserte!

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


All Articles