Derivados tela de espaço pixel fazer drasticamente o desempenho de impacto, mas eles afetar o desempenho se você usá-los ou não, então a partir de um certo ponto de vista eles estão livres!
Todas as GPUs da história recente agrupam um quadrilátero de quatro pixels e as colocam na mesma frente de onda / distorção, o que significa essencialmente que elas estão rodando próximas uma da outra na GPU, portanto, acessar valores a partir deles é muito barato. Como os warps / frentes de onda são executados em passo de bloqueio, os outros pixels também estarão exatamente no mesmo lugar no shader que você, portanto, o valor p
desses pixels estará apenas em um registro esperando por você. Esses outros três pixels sempre serão executados, mesmo que seus resultados sejam descartados. Portanto, um triângulo que cubra um único pixel sempre sombreia quatro pixels e joga fora os resultados de três deles, apenas para que esses recursos derivados funcionem!
Isso é considerado um custo aceitável (para o hardware atual), porque não são apenas funções como fwidth
essas que usam esses derivados: todas as amostras de textura também o fazem, para escolher o mapa mip da sua textura. Considere: se você estiver muito próximo de uma superfície, a coordenada UV usada para amostrar a textura terá uma derivada muito pequena no espaço da tela, o que significa que você precisará usar um mapa mip maior e, se estiver mais longe, a coordenada UV terá uma derivada maior no espaço da tela, o que significa que você precisa usar um mapa mip menor.
Na medida em que isso significa em termos menos matemáticos: fwidth
é equivalente a abs(dFdx(p)) + abs(dFdy(p))
. dFdx(p)
é simplesmente a diferença entre o valor de p
no pixel x + 1 e o valor de p
no pixel x, e da mesma forma para dFdy(p)
.
dFdx(p) = p(x1) - p(x)
, entãox1
pode ser um(x+1)
ou(x-1)
, dependendo da posição do pixelx
no quad. De qualquer maneira,x1
deve estar no mesmo warp / wavefront quex
. Estou correcto?dFdx
é calculado para cada um dos 2 pixels vizinhos na grade 2x2. E esse valor é calculado usando a diferença entre os dois valores vizinhos, se isso ép(x+1)-p(x)
oup(x)-p(x-1)
depende apenas da sua noção do quex
é exatamente aqui. O resultado é o mesmo, no entanto. Então sim, você está correto.Em termos inteiramente técnicos,
fwidth(p)
é definido comoE
dFdx(p)
/dFdy(p)
são as derivadas parciais do valorp
em relação às dimensõesx
e day
tela. Portanto, eles denotam como o valor dep
se comporta ao passar um pixel para a direita (x
) ou um pixel para cima (y
).Agora, como eles podem ser praticamente computados? Bem, se você conhece os valores dos pixels vizinhos
p
, pode apenas computar essas derivadas como diferenças finitas diretas como uma aproximação para suas derivadas matemáticas reais (que podem não ter uma solução analítica exata):Mas é claro que agora você pode perguntar: como sabemos os valores de
p
(que depois poderiam ser qualquer valor arbitrariamente calculado dentro do programa shader) para os pixels vizinhos? Como calculamos esses valores sem incorrer em grandes despesas gerais, fazendo todo o cálculo do shader duas (ou três) vezes?Bem, você sabe, esses valores vizinhos são calculados de qualquer maneira, já que para o pixel vizinho você também executa um shader de fragmento. Portanto, tudo o que você precisa é de acesso a essa chamada de shader de fragmento vizinho quando executado para o pixel vizinho. Mas é ainda mais fácil, porque esses valores vizinhos também são calculados ao mesmo tempo.
Os rasterizadores modernos chamam shaders de fragmentos em blocos maiores de mais de um pixel vizinho. No menor, esses seriam uma grade de pixels 2x2. E para cada um desses blocos de pixels, o sombreador de fragmento é chamado para cada pixel e essas invocações são executadas em uma etapa de bloqueio perfeitamente paralela, de modo que todos os cálculos sejam feitos exatamente na mesma ordem e no mesmo momento para cada um desses pixels no bloco (é também por isso que a ramificação no shader de fragmento, embora não seja mortal, deve ser evitada, se possível, pois cada invocação de um bloco deve explorar todos os ramos que são capturados por pelo menos uma das invocações, mesmo que apenas jogue fora os resultados posteriormente, como também foi abordado nas respostas a esta pergunta relacionada) Portanto, a qualquer momento, um shader de fragmento teoricamente tem acesso aos valores do shader de fragmento dos pixels vizinhos. E enquanto você não tem acesso direto a esses valores, você tem acesso a valores computados a partir deles, como as funções de derivativos
dFdx
,dFdy
,fwidth
, ...fonte