Partilhar via


Mapas de sombra em cascata

Os mapas de sombra em cascata (CSMs) são a melhor maneira de combater um dos erros mais prevalentes com sombreamento: aliasing de perspetiva. Este artigo técnico, que pressupõe que o leitor esteja familiarizado com o mapeamento de sombras, aborda o tema dos CSMs. Mais especificamente, ele:

  • explica a complexidade dos MCS;
  • fornece pormenores sobre as possíveis variações dos algoritmos de CSM;
  • descreve as duas técnicas de filtragem mais comuns — filtragem por aproximação percentual (PCF) e filtragem com mapas de sombra de variância (VSMs);
  • identifica e aborda algumas das armadilhas comuns associadas à adição de filtragem aos MCS; e ainda
  • mostra como mapear CSMs para Direct3D 10 através de hardware Direct3D 11.

O código usado neste artigo pode ser encontrado no DirectX Software Development Kit (SDK) nos exemplos CascadedShadowMaps11 e VarianceShadows11. Este artigo será mais útil depois de implementar as técnicas abordadas no artigo técnico, Common Techniques to Improve Shadow Depth Maps, são implementadas.

Mapas de sombra em cascata e aliasing de perspetiva

O aliasing de perspetiva em um mapa de sombra é um dos problemas mais difíceis de superar. No artigo técnico, Common Techniques to Improve Shadow Depth Maps, o aliasing de perspetiva é descrito e algumas abordagens para mitigar o problema são identificadas. Na prática, os CSMs tendem a ser a melhor solução, e são comumente empregados em jogos modernos.

O conceito básico de MCS é fácil de compreender. Diferentes áreas do frustum da câmera exigem mapas de sombra com diferentes resoluções. Os objetos mais próximos do olho requerem uma resolução maior do que os objetos mais distantes. Na verdade, quando o olho se move muito perto da geometria, os pixels mais próximos do olho podem exigir tanta resolução que mesmo um mapa de sombra 4096 × 4096 é insuficiente.

A ideia básica dos CSMs é dividir o frustum em vários frusta. Um mapa de sombra é renderizado para cada subfrustum; em seguida, o sombreador de pixel obtém amostras do mapa que mais se aproxima da resolução necessária (Figura 2).

Figura 1. Cobertura de mapa de sombra

cobertura de mapa de sombra

Na Figura 1, a qualidade é mostrada (da esquerda para a direita) do mais alto para o mais baixo. A série de grades que representam mapas de sombra com um frustum de visualização (cone invertido em vermelho) mostra como a cobertura de pixels é afetada com mapas de sombra de resolução diferente. As sombras são da mais alta qualidade (pixels brancos) quando há uma proporção de 1:1 mapeando pixels no espaço de luz para texels no mapa de sombras. O aliasing de perspetiva ocorre na forma de grandes mapas de textura em blocos (imagem à esquerda) quando muitos pixels são mapeados para a mesma sombra texel. Quando o mapa de sombra é muito grande, ele é amostrado. Neste caso, texels são ignorados, artefatos cintilantes são introduzidos e o desempenho é afetado.

Figura 2. Qualidade de sombra CSM

qualidade de sombra CSM

A Figura 2 mostra recortes da seção de mais alta qualidade em cada mapa de sombra na Figura 1. O mapa de sombra com os pixels mais próximos (no ápice) está mais próximo do olho. Tecnicamente, estes são mapas do mesmo tamanho, com branco e cinza usados para exemplificar o sucesso do mapa de sombra em cascata. O branco é ideal porque mostra uma boa cobertura — uma proporção de 1:1 para pixels de espaço ocular e texels de mapa de sombra.

Os CSMs exigem as seguintes etapas por quadro.

  1. Dicione o frustum em subfrusta.

  2. Calcular uma projeção ortográfica para cada subfrustum.

  3. Renderize um mapa de sombra para cada subfrustum.

  4. Renderize a cena.

    1. Vincule os mapas de sombra e renderize.

    2. O sombreador de vértice faz o seguinte:

      • Calcula as coordenadas de textura para cada subfrustum de luz (a menos que a coordenada de textura necessária seja calculada no sombreador de pixel).
      • Transforma e ilumina o vértice, e assim por diante.
    3. O sombreador de pixel faz o seguinte:

      • Determina o mapa de sombra adequado.
      • Transforma as coordenadas de textura, se necessário.
      • Amostras da cascata.
      • Acende o pixel.

Particionando o Frustum

Particionar o frustum é o ato de criar subfrusta. Uma técnica para dividir o frustum é calcular intervalos de zero por cento a cem por cento na direção Z. Cada intervalo representa então um plano próximo e um plano distante como uma percentagem do eixo Z.

Figura 3. Ver frustums particionados arbitrariamente

ver frustums particionados arbitrariamente

Na prática, recalcular as divisões de frustum por quadro faz com que as bordas de sombra brilhem. A prática geralmente aceita é usar um conjunto estático de intervalos em cascata por cenário. Nesse cenário, o intervalo ao longo do eixo Z é usado para descrever um subfrustum que ocorre ao particionar o frustum. Determinar os intervalos de tamanho corretos para uma determinada cena depende de vários fatores.

Orientação da Geometria da Cena

Com relação à geometria da cena, a orientação da câmera afeta a seleção do intervalo em cascata. Por exemplo, uma câmera muito perto do chão, como uma câmera terrestre em um jogo de futebol, tem um conjunto estático diferente de intervalos em cascata do que uma câmera no céu.

Figura 4 mostra algumas câmeras diferentes e suas respetivas partições. Quando o alcance Z da cena é muito grande, mais planos divididos são necessários. Por exemplo, quando o olho está muito perto do plano de solo, mas objetos distantes ainda são visíveis, várias cascatas podem ser necessárias. Dividir o frustum para que mais divisões estejam perto do olho (onde o aliasing de perspetiva está mudando mais rápido) também é valioso. Quando a maior parte da geometria é agrupada em uma pequena seção (como uma visão aérea ou um simulador de voo) do frustum de visão, menos cascatas são necessárias.

Figura 4. Diferentes configurações requerem diferentes divisões de frustum

configurações diferentes requerem diferentes divisões de frustum

(à esquerda) Quando a geometria tem uma alta faixa dinâmica em Z, muitas cascatas são necessárias. (Centro) Quando a geometria tem baixa faixa dinâmica em Z, há pouco benefício de múltiplos frustums. (Direita) Apenas três partições são necessárias quando o intervalo dinâmico é médio.

Orientação da luz e da câmara

A matriz de projeção de cada cascata é encaixada firmemente em torno de seu subfrustum correspondente. Em configurações onde a câmera de visão e as direções da luz são ortogonais, as cascatas podem ser encaixadas firmemente com pouca sobreposição. A sobreposição torna-se maior à medida que a luz e a câmara de visualização se movem para um alinhamento paralelo (Figura 5). Quando a luz e a câmera de visão são quase paralelas, é chamado de "frusta de duelo", e é um cenário muito difícil para a maioria dos algoritmos de sombreamento. Não é incomum restringir a luz e a câmera para que esse cenário não ocorra. Os CSMs, no entanto, têm um desempenho muito melhor do que muitos outros algoritmos neste cenário.

Figura 5. A sobreposição em cascata aumenta à medida que a direção da luz se torna paralela à direção da câmara

sobreposição em cascata aumenta à medida que a direção da luz se torna paralela à direção da câmera

Muitas implementações de CSM usam frusta de tamanho fixo. O sombreador de pixel pode usar a profundidade Z para indexar na matriz de cascatas quando o frustum é dividido em intervalos de tamanho fixo.

Calculando um limite de View-Frustum

Uma vez selecionados os intervalos de frustum, os subfrusta são criados usando um de dois: ajuste à cena e ajuste à cascata.

Ajuste à cena

Todos os frusta podem ser criados com o mesmo plano próximo. Isto força as cascatas a sobreporem-se. O exemplo CascadedShadowMaps11 chama essa técnica de ajuste à cena.

Ajuste à cascata

Alternativamente, frusta pode ser criado com o intervalo de partição real sendo usado como planos próximos e distantes. Isso causa um ajuste mais apertado, mas degenera para se encaixar em cena no caso de duelo de frusta. As amostras de CascadedShadowMaps11 chamam essa técnica de ajuste à cascata.

Estes dois métodos são apresentados na Figura 6. Adequado para cascata desperdiça menos resolução. O problema com o ajuste à cascata é que a projeção ortográfica cresce e encolhe com base na orientação do frustum da visão. A técnica de ajuste à cena preenche a projeção ortográfica pelo tamanho máximo do frustum de visão, removendo os artefatos que aparecem quando a câmera de visão se move. Técnicas comuns para melhorar mapas de profundidade de sombra aborda os artefatos que aparecem quando a luz se move na seção "Movendo a luz em incrementos de tamanho texel".

Figura 6. Ajuste à cena vs. ajuste à cascata

adequado à cena versus apto à cascata

Renderizar o mapa de sombra

O exemplo CascadedShadowMaps11 renderiza os mapas de sombra em um buffer grande. Isso ocorre porque o PCF em matrizes de textura é um recurso do Direct3D 10.1. Para cada cascata, é criada uma janela de visualização que cobre a seção do buffer de profundidade correspondente a essa cascata. Um sombreador de pixel nulo é vinculado porque apenas a profundidade é necessária. Finalmente, a janela de visualização e a matriz de sombra corretas são definidas para cada cascata à medida que os mapas de profundidade são renderizados um de cada vez no buffer de sombra principal.

Renderizar a cena

O buffer que contém as sombras agora está vinculado ao sombreador de pixel. Há dois métodos para selecionar a cascata implementada no exemplo CascadedShadowMaps11. Esses dois métodos são explicados com código de sombreador.

Interval-Based Seleção em cascata

Figura 7. Seleção em cascata baseada em intervalos

seleção em cascata baseada em intervalos

Na seleção baseada em intervalos (Figura 7), o sombreador de vértice calcula a posição no espaço-mundo do vértice.

Output.vDepth = mul( Input.vPosition, m_mWorldView ).z;

O sombreador de pixel recebe a profundidade interpolada.

fCurrentPixelDepth = Input.vDepth;

A seleção em cascata baseada em intervalos usa uma comparação vetorial e um produto de pontos para determinar a academia correta. O CASCADE_COUNT_FLAG especifica o número de cascatas. O m_fCascadeFrustumsEyeSpaceDepths_data restringe as partições frustum de visualização. Após a comparação, o fComparison contém um valor de 1 quando o pixel atual é maior do que a barreira, e um valor de 0 quando a cascata atual é menor. Um produto de ponto soma esses valores em um índice de matriz.

        float4 vCurrentPixelDepth = Input.vDepth;
        float4 fComparison = ( vCurrentPixelDepth > m_fCascadeFrustumsEyeSpaceDepths_data[0]);
        float fIndex = dot(
        float4( CASCADE_COUNT_FLAG > 0,
        CASCADE_COUNT_FLAG > 1,
        CASCADE_COUNT_FLAG > 2,
        CASCADE_COUNT_FLAG > 3)
        , fComparison );

        fIndex = min( fIndex, CASCADE_COUNT_FLAG );
        iCurrentCascadeIndex = (int)fIndex;

Uma vez selecionada a cascata, a coordenada de textura deve ser transformada para a cascata correta.

vShadowTexCoord = mul( InterpolatedPosition, m_mShadow[iCascadeIndex] );

Esta coordenada de textura é então usada para amostrar a textura com a coordenada X e a coordenada Y. A coordenada Z é usada para fazer a comparação final de profundidade.

Map-Based Seleção em cascata

A seleção baseada em mapa (Figura 8) testa os quatro lados das cascatas para encontrar o mapa mais apertado que cobre o pixel específico. Em vez de calcular a posição no espaço do mundo, o sombreador de vértice calcula a posição do espaço de visão para cada cascata. O sombreador de pixel itera sobre as cascatas para dimensionar e deslocar as coordenadas de textura para que elas indexem a cascata atual. A coordenada de textura é então testada em relação aos limites de textura. Quando os valores X e Y da coordenada de textura caem dentro de uma cascata, eles são usados para amostragem da textura. A coordenada Z é usada para fazer a comparação final de profundidade.

Figura 8. Seleção em cascata baseada em mapa

seleção em cascata baseada em mapa

Interval-Based Seleção vs. Seleção Map-Based

A seleção baseada em intervalos é um pouco mais rápida do que a seleção baseada em mapas, porque a seleção em cascata pode ser feita diretamente. A seleção baseada em mapa deve cruzar a coordenada de textura com os limites em cascata.

A seleção baseada em mapas usa a cascata de forma mais eficiente quando os mapas de sombra não se alinham perfeitamente (veja a Figura 8).

Mistura entre Cascatas

VSMs (discutidos mais adiante neste artigo) e técnicas de filtragem, como PCF, podem ser usados com CSMs de baixa resolução para produzir sombras suaves. Infelizmente, isso resulta em uma costura visível (Figura 9) entre camadas em cascata porque a resolução não corresponde. A solução é criar uma banda entre mapas de sombra onde o teste de sombra é realizado para ambas as cascatas. Em seguida, o sombreador interpola linearmente entre os dois valores com base na localização do pixel na banda de mistura. Os exemplos CascadedShadowMaps11 e VarianceShadows11 fornecem um controle deslizante GUI que pode ser usado para aumentar e diminuir essa banda de desfoque. O sombreador executa uma ramificação dinâmica para que a grande maioria dos pixels só leia a partir da cascata atual.

Figura 9. Costuras em cascata

costuras em cascata

(à esquerda) Uma costura visível pode ser vista onde as cascatas se sobrepõem. (Direita) Quando as cascatas são misturadas, não ocorre costura.

Filtrando mapas de sombra

PCF

A filtragem de mapas de sombra comuns não produz sombras suaves e desfocadas. O hardware de filtragem desfoca os valores de profundidade e, em seguida, compara esses valores desfocados com o espaço de luz texel. A aresta rígida resultante do teste de aprovação/reprovação ainda existe. Desfocar mapas de sombra serve apenas para mover erroneamente a borda dura. PCF permite a filtragem em mapas de sombra. A ideia geral do PCF é calcular uma porcentagem do pixel na sombra com base no número de subamostras que passam no teste de profundidade sobre o número total de subamostras.

O hardware Direct3D 10 e Direct3D 11 pode executar PCF. A entrada para um amostrador PCF consiste na coordenada de textura e um valor de profundidade de comparação. Para simplificar, o PCF é explicado com um filtro de quatro toques. O amostrador de textura lê a textura quatro vezes, semelhante a um filtro padrão. No entanto, o resultado retornado é uma porcentagem dos pixels que passaram no teste de profundidade. A Figura 10 mostra como um pixel que passa em um dos quatro testes de profundidade está 25% na sombra. O valor real retornado é uma interpolação linear baseada nas coordenadas subtexel da textura lida para produzir um gradiente suave. Sem essa interpolação linear, o PCF de quatro toques só seria capaz de retornar cinco valores: { 0,0, 0,25, 0,5, 0,75, 1,0 }.

Figura 10. Imagem filtrada por PCF, com 25% do pixel selecionado coberto

imagem filtrada por PCF, com 25% do pixel selecionado coberto

Também é possível fazer PCF sem suporte de hardware ou estender PCF para kernels maiores. Algumas técnicas até amostram com um kernel ponderado. Para fazer isso, crie um kernel (como um Gaussian) para uma grade N × N. Os pesos devem somar 1. A textura é então amostrada N2 vezes. Cada amostra é dimensionada pelos pesos correspondentes no kernel. O exemplo CascadedShadowMaps11 usa essa abordagem.

Viés de profundidade

O viés de profundidade torna-se ainda mais importante quando grandes kernels PCF são usados. Só é válido comparar a profundidade do espaço de luz de um pixel com o pixel para o qual ele mapeia no mapa de profundidade. Os vizinhos do mapa de profundidade referem-se a uma posição diferente. É provável que esta profundidade seja semelhante, mas pode ser muito diferente dependendo da cena. A Figura 11 destaca os artefatos que ocorrem. Uma única profundidade é comparada a três texels vizinhos no mapa de sombras. Um dos testes de profundidade falha erroneamente porque sua profundidade não se correlaciona com a profundidade do espaço de luz calculada da geometria atual. A solução recomendada para esse problema é usar um deslocamento maior. Uma compensação muito grande, no entanto, pode resultar em Peter Panning. Calcular um plano próximo apertado e um plano distante ajuda a reduzir os efeitos do uso de um offset.

Figura 11. Auto-sombra errônea

auto-sombreamento errôneo

O auto-sombreamento errôneo resulta da comparação de pixels na profundidade do espaço de luz com os texels no mapa de sombra que não se correlacionam. A profundidade no espaço de luz está correlacionada com a sombra texel 2 no mapa de profundidade. Texel 1 é maior do que a profundidade do espaço de luz, enquanto 2 é igual e 3 é menor. Texels 2 e 3 passam no teste de profundidade, enquanto Texel 1 falha.

Calculando um viés de profundidade de Per-Texel com DDX e DDY para PCFs grandes

Calcular um viés de profundidade per texel com ddx e ddy para PCFs grandes é uma técnica que calcula o viés de profundidade correto — supondo que a superfície seja plana — para o texel do mapa de sombra adjacente.

Esta técnica ajusta a profundidade de comparação a um plano usando a informação derivada. Como essa técnica é computacionalmente complexa, ela deve ser usada apenas quando uma GPU tem ciclos de computação de sobra. Quando kernels muito grandes são usados, esta pode ser a única técnica que funciona para remover artefatos de auto-sombreamento sem causar Peter Panning.

A Figura 12 destaca o problema. A profundidade no espaço de luz é conhecida pelo texel que está sendo comparado. As profundidades do espaço-luz que correspondem aos texels vizinhos no mapa de profundidade são desconhecidas.

Figura 12. Mapa de cena e profundidade

mapa de cena e profundidade

A cena renderizada é mostrada à esquerda, e o mapa de profundidade com um bloco texel de amostra é mostrado à direita. O texel olho-espaço mapeia para o pixel rotulado D no centro do bloco. Esta comparação é precisa. A profundidade correta no espaço ocular correlacionada com os pixels que o vizinho D é desconhecida. Mapear os texels vizinhos de volta ao espaço dos olhos só é possível se assumirmos que o pixel pertence ao mesmo triângulo que D.

A profundidade é conhecida pelo texel que se correlaciona com a posição do espaço de luz. A profundidade é desconhecida para os texels vizinhos no mapa de profundidade.

Em um alto nível, esta técnica usa o ddx e ddy operações HLSL para encontrar a derivada da posição do espaço de luz. Isso não é trivial porque as operações derivadas retornam o gradiente da profundidade do espaço de luz em relação ao espaço da tela. Para convertê-lo em um gradiente da profundidade do espaço de luz em relação ao espaço de luz, uma matriz de conversão deve ser calculada.

Explicação com Shader Code

Os detalhes do resto do algoritmo são fornecidos como uma explicação do código de sombreador que executa esta operação. Esse código pode ser encontrado no exemplo CascadedShadowMaps11. A Figura 13 mostra como as coordenadas de textura do espaço de luz são mapeadas para o mapa de profundidade e como as derivadas em X e Y podem ser usadas para criar uma matriz de transformação.

Figura 13. Matriz espaço-tela para espaço-luz

espaço de tela para matriz de espaço de luz

As derivadas da posição do espaço de luz em X e Y são usadas para criar esta matriz.

O primeiro passo é calcular a derivada da posição luz-vista-espaço.

          float3 vShadowTexDDX = ddx (vShadowMapTextureCoordViewSpace);
          float3 vShadowTexDDY = ddy (vShadowMapTextureCoordViewSpace);

As GPUs da classe Direct3D 11 calculam essas derivadas executando 2 × 2 quad de pixels em paralelo e subtraindo as coordenadas de textura do vizinho em X para ddx e do vizinho em Y para ddy. Estas duas derivadas compõem as linhas de uma matriz 2 × 2. Em sua forma atual, essa matriz poderia ser usada para converter pixels vizinhos de espaço de tela em inclinações de espaço de luz. No entanto, o inverso desta matriz é necessário. É necessária uma matriz que transforme pixels vizinhos de espaço de luz em inclinações de espaço de tela.

          float2x2 matScreentoShadow = float2x2( vShadowTexDDX.xy, vShadowTexDDY.xy );
          float fInvDeterminant = 1.0f / fDeterminant;

          float2x2 matShadowToScreen = float2x2 (
          matScreentoShadow._22 * fInvDeterminant,
          matScreentoShadow._12 * -fInvDeterminant,
          matScreentoShadow._21 * -fInvDeterminant,
          matScreentoShadow._11 * fInvDeterminant );

Figura 14. Espaço de luz para espaço de tela

espaço de luz para espaço de tela

Esta matriz é então usada para transformar os dois texels acima e à direita do texel atual. Estes vizinhos são representados como um deslocamento do texel atual.

          float2 vRightShadowTexelLocation = float2( m_fTexelSize, 0.0f );
          float2 vUpShadowTexelLocation = float2( 0.0f, m_fTexelSize );
          float2 vRightTexelDepthRatio = mul( vRightShadowTexelLocation,
          matShadowToScreen );
          float2 vUpTexelDepthRatio = mul( vUpShadowTexelLocation,
          matShadowToScreen );

A proporção que a matriz cria é finalmente multiplicada pelas derivadas de profundidade para calcular os deslocamentos de profundidade para os pixels vizinhos.

            float fUpTexelDepthDelta =
            vUpTexelDepthRatio.x * vShadowTexDDX.z
            + vUpTexelDepthRatio.y * vShadowTexDDY.z;
            float fRightTexelDepthDelta =
            vRightTexelDepthRatio.x * vShadowTexDDX.z
            + vRightTexelDepthRatio.y * vShadowTexDDY.z;

Esses pesos agora podem ser usados em um loop PCF para adicionar um deslocamento à posição.

    for( int x = m_iPCFBlurForLoopStart; x < m_iPCFBlurForLoopEnd; ++x ) 
    {
        for( int y = m_iPCFBlurForLoopStart; y < m_iPCFBlurForLoopEnd; ++y )
            {
            if ( USE_DERIVATIVES_FOR_DEPTH_OFFSET_FLAG )
            {
            depthcompare += fRightTexelDepthDelta * ( (float) x ) +
            fUpTexelDepthDelta * ( (float) y );
            }
            // Compare the transformed pixel depth to the depth read
            // from the map.
            fPercentLit += g_txShadow.SampleCmpLevelZero( g_samShadow,
            float2(
            vShadowTexCoord.x + ( ( (float) x ) * m_fNativeTexelSizeInX ) ,
            vShadowTexCoord.y + ( ( (float) y ) * m_fTexelSize )
            ),
            depthcompare
            );
            }
     }

PCF e CSMs

PCF não funciona em matrizes de textura no Direct3D 10. Para usar PCF, todas as cascatas são armazenadas em um grande atlas de textura.

Deslocamento Derivative-Based

A adição de compensações baseadas em derivativos para CSMs apresenta alguns desafios. Isto deve-se a um cálculo de derivados dentro do controlo de fluxos divergentes. O problema ocorre devido a uma maneira fundamental que as GPUs operam. As GPUs Direct3D11 operam em 2 × 2 quads de pixels. Para executar uma derivada, as GPUs geralmente subtraem a cópia do pixel atual de uma variável da cópia do pixel vizinho dessa mesma variável. A forma como isso acontece varia de GPU para GPU. As coordenadas de textura são determinadas pela seleção em cascata baseada em mapa ou intervalo. Alguns pixels em um pixel quad escolhem uma cascata diferente do resto dos pixels. Isso resulta em costuras visíveis entre mapas de sombra porque os deslocamentos baseados em derivativos agora estão completamente errados. A solução é executar a derivada em coordenadas de textura do espaço de visualização de luz. Estas coordenadas são as mesmas para todas as cascatas.

Preenchimento para kernels PCF

Os kernels PCF indexam fora de uma partição em cascata se o buffer de sombra não for acolchoado. A solução é cobrir a borda externa da cascata com metade do tamanho do kernel PCF. Isso deve ser implementado no sombreador que seleciona a cascata e na matriz de projeção que deve tornar a cascata grande o suficiente para que a borda seja preservada.

Mapas de sombra de variância

Os VSMs (consulte Variance shadow maps por Donnelly e Lauritzen para obter mais informações) permitem a filtragem direta de mapas de sombra. Ao usar VSMs, todo o poder do hardware de filtragem de textura pode ser usado. Pode ser utilizada filtragem trilinear e anisotrópica (Figura 15). Além disso, os VSMs podem ser borrados diretamente através da convolução. Os VSMs têm algumas desvantagens; Dois canais de dados de profundidade devem ser armazenados (profundidade e profundidade ao quadrado). Quando as sombras se sobrepõem, o sangramento de luz é comum. Eles funcionam bem, no entanto, com resoluções mais baixas e podem ser combinados com CSMs.

Figura 15. Filtragem anisotrópica

de filtragem anisotrópica

Detalhes do algoritmo

Os VSMs funcionam renderizando a profundidade e a profundidade ao quadrado em um mapa de sombra de dois canais. Este mapa de sombra de dois canais pode então ser desfocado e filtrado como uma textura normal. O algoritmo então usa a Desigualdade de Chebychev no sombreador de pixel para estimar a fração de área de pixel que passaria no teste de profundidade.

O sombreador de pixel obtém os valores de profundidade e profundidade quadrada.

        float  fAvgZ  = mapDepth.x; // Filtered z
        float  fAvgZ2 = mapDepth.y; // Filtered z-squared

A comparação de profundidade é realizada.

        if ( fDepth <= fAvgZ )
        {
        fPercentLit = 1;
        }

Se a comparação de profundidade falhar, a porcentagem do pixel que está aceso é estimada. A variância é calculada como média-de-quadrados menos quadrado-de-média.

        float variance = ( fAvgZ2 ) − ( fAvgZ * fAvgZ );
        variance = min( 1.0f, max( 0.0f, variance + 0.00001f ) );

O valor fPercentLit é estimado com a Desigualdade de Chebychev.

        float mean           = fAvgZ;
        float d              = fDepth - mean;
        float fPercentLit    = variance / ( variance + d*d );

Sangramento leve

A maior desvantagem dos VSMs é o sangramento leve (Figura 16). O sangramento leve ocorre quando vários rodízios de sombra se ocluem ao longo das bordas. Os VSMs sombreiam as bordas das sombras com base em disparidades de profundidade. Quando as sombras se sobrepõem, existe uma disparidade de profundidade no centro de uma região que deve ser sombreada. Este é um problema com o uso do algoritmo VSM.

Figura 16. VSM sangramento leve

de sangramento leve vsm

Uma solução parcial para o problema é elevar o fPercentLit a uma potência. Isso tem o efeito de atenuar o desfoque, o que pode causar artefatos onde a disparidade de profundidade é pequena. Às vezes existe um valor mágico que alivia o problema.

fPercentLit = pow( p_max, MAGIC_NUMBER );

Uma alternativa para aumentar a percentagem de luz para uma potência é evitar configurações em que as sombras se sobrepõem. Mesmo configurações de sombra altamente ajustadas têm várias restrições de luz, câmera e geometria. O sangramento leve também é diminuído pelo uso de texturas de maior resolução.

Os mapas de sombra de variância em camadas (LVSMs) resolvem o problema à custa de quebrar o frustum em camadas perpendiculares à luz. O número de mapas necessários seria bastante elevado quando os MCS também estão a ser utilizados.

Além disso, Andrew Lauritzen, coautor do artigo sobre VSMs, e autor de um artigo sobre LVSMs, discutiu a combinação de mapas de sombra exponenciais (ESMs) com VSMs para neutralizar a mistura de luz em um Beyond3D Forum.

VSMs com CSMs

O exemplo VarianceShadow11 combina VSMs e CSMs. A combinação é bastante simples. O exemplo segue as mesmas etapas que o exemplo CascadedShadowMaps11. Como o PCF não é usado, as sombras são borradas em uma convolução separável de duas passagens. Não usar PCF também permite que a amostra use matrizes de textura em vez de um atlas de textura. PCF em matrizes de textura é um recurso do Direct3D 10.1.

Gradientes com CSMs

O uso de gradientes com CSMs pode produzir uma costura ao longo da borda entre duas cascatas, como visto na Figura 17. A instrução de exemplo usa derivadas entre pixels para calcular informações, como o nível de mipmap, necessárias para o filtro. Isso causa um problema em particular para a seleção de mipmap ou filtragem anisotrópica. Quando os pixels em um quad tomam ramificações diferentes no sombreador, os derivados calculados pelo hardware da GPU são inválidos. Isso resulta em uma costura irregular ao longo do mapa de sombras.

Figura 17. Costuras em bordas em cascata devido à filtragem anisotrópica com controle de fluxo divergente

costuras em bordas em cascata devido à filtragem anisotrópica com controle de fluxo divergente

Este problema é resolvido calculando as derivadas na posição no espaço de visão de luz; A coordenada de espaço de visualização de luz não é específica para a cascata selecionada. As derivadas calculadas podem ser dimensionadas pela porção da escala da matriz de textura de projeção para o nível mipmap correto.

        float3 vShadowTexCoordDDX = ddx( vShadowMapTextureCoordViewSpace );
        vShadowTexCoordDDX *= m_vCascadeScale[iCascade].xyz;
        float3 vShadowTexCoordDDY = ddy( vShadowMapTextureCoordViewSpace );
        vShadowTexCoordDDY *= m_vCascadeScale[iCascade].xyz;

        mapDepth += g_txShadow.SampleGrad( g_samShadow, vShadowTexCoord.xyz,
        vShadowTexCoordDDX, vShadowTexCoordDDY );

VSMs em comparação com sombras padrão com PCF

Tanto os VSMs quanto o PCF tentam aproximar a fração de área de pixel que passaria no teste de profundidade. Os VSMs funcionam com hardware de filtragem e podem ser desfocados com kernels separáveis. Kernels de convolução separáveis são consideravelmente mais baratos de implementar do que um kernel completo. Além disso, os VSMs comparam uma profundidade de espaço de luz com um valor no mapa de profundidade de espaço de luz. Isso significa que os VSMs não têm os mesmos problemas de deslocamento que o PCF. Tecnicamente, os VSMs são a amostragem de profundidade em uma área maior, bem como a realização de uma análise estatística. Isto é menos preciso do que o PCF. Na prática, os VSMs fazem um bom trabalho de mistura, o que resulta em menos compensação sendo necessária. Como descrito acima, a desvantagem número um para VSMs é sangramento leve.

VSMs e PCF representam uma compensação entre o poder de computação da GPU e a largura de banda da textura da GPU. Os VSMs exigem mais matemática a ser executada para calcular a variância. PCF requer mais largura de banda de memória de textura. Grandes kernels PCF podem rapidamente se tornar congestionados pela largura de banda de textura. Com o poder de computação da GPU crescendo mais rapidamente do que a largura de banda da GPU, os VSMs estão se tornando os mais práticos dos dois algoritmos. Os VSMs também ficam melhores com mapas de sombra de baixa resolução devido à mistura e filtragem.

Resumo

Os MCS oferecem uma solução para o problema do aliasing de perspetiva. Existem várias configurações possíveis para obter a fidelidade visual necessária para um título. PCF e VSMs são amplamente utilizados e devem ser combinados com CSMs para reduzir o aliasing.

Referências

Donnelly, W. e Lauritzen, A. Mapas de sombra de variância. In SI3D '06: Atas do simpósio de 2006 sobre gráficos e jogos 3D interativos. 2006. pp. 161-165. Nova Iorque, NY, EUA: ACM Press.

Lauritzen, Andrew e McCool, Michael. Mapas de sombra de variância em camadas. Anais da interface gráfica 2008, 28–30 de maio de 2008, Windsor, Ontário, Canadá.

Engel, Woflgang F. Secção 4. Mapas de sombra em cascata. ShaderX5 , Técnicas Avançadas de Renderização, Wolfgang F. Engel, Ed. Charles River Media, Boston, Massachusetts. 2006. pp. 197-206.