Como o quadro de Rise of the Tomb Raider é renderizado


Rise of the Tomb Raider (2015) é a sequela do excelente reinício de Tomb Raider (2013). Pessoalmente, acho as duas partes interessantes porque elas se afastaram da série original estagnada e contaram a história de Lara novamente. Neste jogo, como no prequel, o enredo toma o centro do palco, fornece mecânicas fascinantes de criação, caça e escalada / pesquisa.

O Tomb Raider usou o Crystal Engine, desenvolvido pela Crystal Dynamics, também usado em Deus Ex: Human Revolution . A sequela usou um novo mecanismo chamado Foundation, desenvolvido anteriormente para Lara Croft e o Temple of Osiris (2014). Sua renderização geralmente pode ser descrita como um mecanismo de ladrilhos com passo de iluminação preliminar, e mais tarde descobriremos o que isso significa. O mecanismo permite escolher entre os renderizadores DX11 e DX12; Eu escolhi o último, pelas razões que discutimos abaixo. Para capturar o quadro, o Renderdoc 1.2 foi usado no Geforce 980 Ti, o jogo inclui todas as funções e decorações.

Quadro analisado



Para evitar spoilers, direi que nesse quadro os bandidos estão perseguindo Lara, porque ela está procurando um artefato que eles estão procurando. Este conflito de interesses não pode ser resolvido sem armas. Lara entrou furtivamente na base inimiga à noite. Eu escolhi um quadro com iluminação atmosférica e de contraste, na qual o motor pode se mostrar.

Avanço de profundidade


Aqui, a otimização usual para muitos jogos é realizada - um pequeno passo preliminar da profundidade (aproximadamente 100 chamadas de empate). O jogo renderiza os maiores objetos (e não aqueles que ocupam mais espaço na tela) para aproveitar os recursos do processador de vídeo Early-Z. Leia mais sobre isso em um artigo da Intel . Em resumo, as GPUs são capazes de evitar a execução de um sombreador de pixels se puderem determinar que ele se sobrepõe ao pixel anterior. Esta é uma passagem de baixo custo, preenchendo previamente o buffer Z com os valores de profundidade.

Nesse ponto, descobri uma técnica interessante de nível de detalhe (LOD) chamada "fracassar" ou "tabuleiro de damas". Essa é uma maneira comum de exibir ou ocultar gradualmente objetos à distância, para que mais tarde eles possam ser substituídos por uma malha de qualidade inferior ou ocultá-los completamente. Olhe para esse caminhão. Parece que está sendo renderizado duas vezes, mas na verdade está sendo renderizado com um LOD alto e um LOD baixo na mesma posição. Cada um dos níveis renderiza os pixels que o outro não processou. O primeiro LOD possui 182.226 vértices e o segundo LOD possui 47.250. A uma grande distância, eles são indistinguíveis, mas um deles é três vezes menos caro. Nesse quadro, o LOD 0 quase desaparece e o LOD 1 é quase completamente renderizado. Após o desaparecimento completo do LOD 0, apenas o LOD 1 será renderizado.


LOD 0


LOD 1


A textura pseudo-aleatória e o coeficiente de probabilidade nos permitem descartar pixels que não ultrapassaram o valor limite. Essa textura é usada no ROTR. Pode-se perguntar por que a mistura alfa não é usada. A mistura alfa tem muitas desvantagens em comparação com o desbotamento.

  1. Conveniência para a passagem preliminar das profundezas: graças à renderização de um objeto opaco com orifícios feitos, podemos renderizar na passagem preliminar e usar o início-z. Objetos com mistura alfa em um estágio tão inicial não serão renderizados no buffer de profundidade devido a problemas de classificação.
  2. A necessidade de sombreadores adicionais : se um renderizador diferido for usado, o sombreador de objetos opacos não conterá nenhuma iluminação. Se você precisar substituir um objeto opaco por um transparente, precisará de uma opção separada na qual exista iluminação. Além de aumentar a quantidade de memória necessária e a complexidade devido a pelo menos um sombreador adicional para todos os objetos opacos, eles devem ser precisos para evitar que os objetos avancem. Isso é complicado por vários motivos, mas tudo se resume ao fato de que a renderização agora é feita em um caminho de código diferente.
  3. Redesenhos maiores : a mistura alfa pode criar redesenhos grandes e, em um certo nível de complexidade de objetos, uma grande fração da largura de banda pode ser necessária para sombrear o LOD.
  4. Z-conflitos : z-conflitos são um efeito de cintilação quando dois polígonos são renderizados a uma profundidade muito próxima um do outro. Nesse caso, a imprecisão dos cálculos de ponto flutuante obriga a serem renderizados por sua vez. Se renderizarmos dois LODs consecutivos, ocultando gradualmente um e mostrando o segundo, eles podem causar um conflito z, porque estão muito próximos um do outro. Sempre há maneiras de contornar isso, por exemplo, preferindo um polígono a outro, mas esse sistema é complexo.
  5. Efeitos do buffer Z : muitos efeitos como o SSAO usam apenas o buffer de profundidade. Se renderizamos objetos transparentes no final do pipeline quando a oclusão do ambiente já estava concluída, não poderíamos levar em consideração.

A desvantagem dessa técnica é que ela parece pior do que a mistura alfa, mas um bom padrão de ruído, borrão após o fracasso ou anti-aliasing temporário podem quase ocultá-la completamente. Nesse sentido, o ROTR não faz nada particularmente incomum.

Passe Normal


O Crystal Dynamics usa um padrão de iluminação bastante incomum em seus jogos, que abordaremos no corredor de iluminação. Por enquanto, basta dizer que o mecanismo não possui um passe G-buffer; pelo menos na medida em que é familiar em outros jogos. Nesta passagem, os objetos transmitem apenas informações sobre a profundidade e os normais à saída. As normais são registradas no destino de renderização do formato RGBA16_SNORM no espaço do mundo. É curioso que esse mecanismo use o esquema Z-up, não o Y-up (o eixo Z é direcionado para cima, não o eixo Y), que é mais frequentemente usado em outros modelos / pacotes de modelagem. O canal alfa contém brilho, que é descompactado como exp2(glossiness * 12 + 1.0) . O valor de brilho também pode ser negativo, porque o sinal é usado como uma bandeira indicando se a superfície é metálica. Isso pode ser observado por si só, porque todas as cores escuras no canal alfa estão relacionadas a objetos de metal.

RGB
Normal.xNormal.yNormal.zBrilho + Metalicidade


Normal


Brilho / metalidade

Vantagens da partida


Lembre-se de que na seção "Profundidade preliminar", falamos sobre economia de custos de pixels? Voltarei um pouco para ilustrá-lo. Tire a seguinte imagem. Isso está tornando a parte detalhada da montanha no buffer normal. O Renderdoc destacou os pixels que passaram no teste de profundidade com verde e os que não passaram no vermelho (eles não serão renderizados). O número total de pixels que seriam renderizados sem essa passagem preliminar é aproximadamente igual a 104518 (calculado no Photoshop). O número total de pixels realmente renderizados é 23858 (calculado pelo Renderdoc). Economize cerca de 77%! Como vemos, com o uso inteligente, esse passe preliminar pode dar um grande ganho e requer apenas cerca de cem chamadas em sorteio.

Gravando comandos multithread


Vale a pena notar um aspecto interessante, pelo qual escolhi o renderizador DX12 - gravação de comandos multithread. Nas APIs anteriores, como o DX11, a renderização geralmente é executada em um único encadeamento. O driver gráfico recebeu comandos de renderização do jogo e transmitiu constantemente solicitações de GPU, mas o jogo não sabia quando isso aconteceria. Isso leva à ineficiência, porque o driver deve, de alguma forma, adivinhar o que o aplicativo está tentando fazer e não é escalável para vários segmentos. APIs mais recentes, como o DX12, entregam o controle a um desenvolvedor que pode decidir como escrever comandos e quando enviá-los. Embora o Renderdoc não possa mostrar como a gravação é feita, você verá que há sete passagens de cores marcadas como Passagem de cor N e cada uma delas é envolvida em um par de ExecuteCommandList: Reset / Close. Marca o início e o fim da lista de comandos. A lista é responsável por aproximadamente 100-200 chamadas em sorteio. Isso não significa que eles foram gravados usando vários fluxos, mas sugerem isso.

Pegadas na neve


Se você olhar para Lara, poderá ver que, ao se mover na frente da captura de tela, ela deixou rastros na neve. Em cada quadro, é executado um sombreador de computação, que registra deformações em determinadas áreas e as aplica com base no tipo e altura da superfície. Aqui, apenas o mapa normal é aplicado à neve (ou seja, a geometria não muda), mas em algumas áreas onde a neve é ​​mais espessa, a deformação é realmente realizada! Você também pode ver como a neve "cai" no lugar e preenche os trilhos deixados por Lara. Essa técnica é descrita com muito mais detalhes no GPU Pro 7 . A textura da teia de neve é ​​uma espécie de mapa de elevação que rastreia os movimentos de Lara e colado nas bordas para que o shader de amostragem possa tirar proveito dessa dobra.

Atlas das Sombras


Ao criar o mapeamento de Sombra, é usada uma abordagem bastante comum - agrupar o maior número possível de cartões de sombra em uma textura de sombra comum. Esse atlas de sombras é na verdade uma enorme textura de 16 bits com um tamanho de 16384 × 8196. Isso permite reutilizar e escalar com flexibilidade os mapas de sombra no atlas. No quadro que estamos analisando, 8 mapas de sombra são registrados no atlas. Quatro deles se relacionam com a principal fonte de iluminação direcional (a lua, porque acontece à noite), porque usam mapas de sombras em cascata - uma técnica bastante padrão de sombras de longa distância para iluminação direcional, que eu já expliquei um pouco antes . Mais interessante, várias fontes de projeção e de destaque também estão incluídas na captura desse quadro. O fato de 8 mapas de sombra serem gravados nesse quadro não significa que existem apenas 8 fontes de projeção de iluminação de sombra nele. O jogo pode armazenar em cache os cálculos de sombra, ou seja, a iluminação que não mudou a posição da fonte ou a geometria no escopo não deve atualizar seu mapa de sombra.


Parece que a renderização de mapas de sombra também se beneficia da gravação de comandos multithread na lista e, nesse caso, até 19 listas de comandos foram escritas para renderizar mapas de sombra.

Sombras da iluminação direcional

As sombras da iluminação direcional são calculadas antes da passagem da luz e depois amostradas. Não sei o que aconteceria se houvesse várias fontes de iluminação direcional na cena.


Oclusão ambiental


Para oclusão ambiental, o ROTR permite que você use o HBAO ou uma variante do HBAO + (essa técnica foi publicada originalmente pela NVIDIA). Existem várias variações desse algoritmo, por isso considerarei o que encontrei no ROTR. Primeiro, o buffer de profundidade é dividido em 16 texturas, cada uma das quais contém 1/16 de todos os valores de profundidade. A separação é realizada de forma que cada textura contenha um valor de um bloco 4 × 4 da textura original mostrada na figura abaixo. A primeira textura contém todos os valores marcados em vermelho (1), a segunda contém os valores marcados em azul (2) e assim por diante. Se você quiser saber mais sobre essa técnica, aqui está um artigo de Louis Bavoil , que também foi um dos autores do artigo sobre HBAO.


O próximo passo calcula a oclusão do ambiente para cada textura, o que fornece 16 texturas AO. Uma oclusão ambiental é gerada da seguinte forma: o buffer de profundidade é amostrado várias vezes, recriando a posição e acumulando o resultado do cálculo para cada uma das amostras. Cada textura de oclusão do ambiente é calculada usando coordenadas de amostragem diferentes, ou seja, em um bloco de pixels 4x4, cada pixel conta sua própria parte da história. Isso é feito por razões de desempenho. Cada pixel já obtém uma amostra do buffer de profundidade 32 vezes e o efeito total exigirá 16 × 32 = 512 amostras, o que é um fracasso mesmo para as GPUs mais poderosas. Em seguida, eles se recombinam em uma textura de tela cheia, o que resulta bastante barulhento; portanto, para suavizar os resultados logo em seguida, é realizada uma passagem de desfoque em tela cheia. Vimos uma solução muito semelhante em Shadow of Mordor .

imagem

Peças HBAO

imagem

HBAO completo com ruído

imagem

Desfoque horizontal completo do HBAO

imagem

Ready HBAO

Pré-passe para iluminação de azulejos


O Light Prepass é uma técnica bastante incomum. A maioria das equipes de desenvolvimento usa uma combinação de cálculo de iluminação diferida + direta (com variações, por exemplo, com bloco, cluster) ou completamente direta para alguns efeitos do espaço na tela. A técnica de pré-iluminação é tão incomum que merece uma explicação. Se o conceito de iluminação retardada tradicional é separar as propriedades dos materiais da iluminação, a idéia de separar a iluminação das propriedades dos materiais é a pedra angular da passagem preliminar da iluminação. Embora essa redação pareça um pouco tola, a diferença da iluminação diferida tradicional é que armazenamos todas as propriedades do material (como albedo, cor especular, rugosidade, metalidade, micro-oclusão, emissivo) em um enorme buffer G e usamos mais tarde dados de entrada para passes de iluminação subsequentes. A iluminação diferida tradicional pode apresentar uma grande carga na taxa de transferência; quanto mais complexos os materiais, mais informações e operações são necessárias no buffer G. No entanto, no passe de iluminação preliminar, primeiro acumulamos toda a iluminação separadamente, usando a quantidade mínima de dados e, em seguida, aplicamos-os nas passagens subsequentes aos materiais. Nesse caso, a iluminação é suficiente apenas para normais, rugosidade e metalidez. Os shaders (duas passagens são usadas aqui) emitem dados em três formatos de destino de renderização RGBA16F. Um contém iluminação difusa, o segundo contém iluminação especular e o terceiro contém iluminação ambiente. Neste ponto, todos os dados de sombra são levados em consideração. É curioso que na primeira passagem (difusa + iluminação espelhada) para uma passagem em tela cheia, um quadrilátero de dois triângulos seja usado e, em outros efeitos, um triângulo em tela cheia (por que isso é importante, você pode descobrir aqui ). Deste ponto de vista, todo o quadro não é integral.

imagem

Iluminação difusa

imagem

Iluminação de espelho

imagem

Iluminação ambiente

Otimização de blocos

A iluminação de azulejos é uma técnica de otimização projetada para renderizar um grande número de fontes de luz. O ROTR divide a tela em blocos 16 × 16 e salva as informações sobre quais fontes cruzam cada bloco, ou seja, os cálculos de iluminação serão executados apenas para as fontes relacionadas aos blocos. No início do quadro, é iniciada uma sequência de sombreadores computacionais que determina quais fontes se relacionam com os blocos. No estágio de iluminação, cada pixel determina em qual bloco ele está e passa por cada fonte de luz no bloco, realizando todos os cálculos de iluminação. Se as fontes estiverem vinculadas aos blocos de maneira eficiente, você poderá economizar bastante computação e grande parte da largura de banda, além de aumentar a produtividade.

Zoom de profundidade

A upsampling baseada em profundidade é uma técnica interessante, útil nesta e em passagens subsequentes. Às vezes, os algoritmos computacionalmente caros não podem ser renderizados em resolução máxima; portanto, eles são renderizados em uma resolução mais baixa e depois ampliados. No nosso caso, a iluminação ambiente é calculada em meia resolução, ou seja, após os cálculos, a iluminação deve ser recriada corretamente. Na forma mais simples, 4 pixels de baixa resolução são capturados e interpolados para obter algo parecido com a imagem original. Isso funciona para transições suaves, mas não parece bom em descontinuidades, porque ali misturamos valores não relacionados que podem ser adjacentes no espaço da tela, mas distantes um do outro no espaço do mundo. Em soluções para esse problema, várias amostras de buffer de profundidade geralmente são coletadas e comparadas com a amostra de profundidade que queremos recriar. Se a amostra estiver muito distante, não a levaremos em consideração na reconstrução. Esse esquema funciona bem, mas significa que o shader de recreação consome muita largura de banda.

O ROTR faz um movimento complicado com o descarte antecipado do estêncil. Depois de passar as normais, o buffer de profundidade fica completamente cheio, de modo que o mecanismo executa uma passagem em tela cheia, marcando todos os pixels interrompidos no buffer de estêncil. Quando chega a hora de recriar o buffer de iluminação ambiente, o mecanismo usa dois shaders: um é muito simples para áreas sem intervalos de profundidade, o outro é mais complexo para pixels com intervalos. O estêncil inicial descarta os pixels se eles não pertencerem à região correspondente, ou seja, há custos apenas nas regiões necessárias. As seguintes imagens são muito mais claras:

imagem

Iluminação ambiente de meia resolução

imagem

Escalando as profundezas do interior

imagem

Iluminação ambiente de resolução total, sem costelas

imagem

Aumentando a profundidade das costelas

imagem

Iluminação ambiente pronta

imagem

Visualização em meia resolução

imagem

Uma visão em close da imagem recriada

Após uma passagem preliminar da iluminação, a geometria é transferida para o transportador, somente dessa vez cada objeto mostra texturas de iluminação, textura de oclusão ambiental e outras propriedades dos materiais que não gravamos no buffer G desde o início. Isso é bom, porque a largura de banda é muito salva aqui devido ao fato de que você não precisa ler um monte de texturas para gravá-las em um grande buffer G e depois lê-las / decodificá-las novamente. A desvantagem óbvia dessa abordagem é que toda a geometria precisa ser retransmitida, e a textura da passagem preliminar da iluminação por si só representa uma grande carga no rendimento. Eu me perguntei por que não usar um formato mais leve, por exemplo, R11G11B10F, para texturas preliminares de iluminação de passagem, mas há informações adicionais no canal alfa, portanto isso seria impossível. Seja como for, esta é uma solução técnica interessante. Nesse ponto, toda a geometria opaca já está renderizada e acesa. Observe que ele inclui objetos emissores de luz, como o céu e a tela do laptop.


Reflexões


Esta cena não é um bom exemplo para demonstrar reflexões, então eu escolhi outra. O shader de reflexão é uma combinação bastante complicada de ciclos que pode ser reduzida para duas partes: uma mostra mapas cúbicos e a outra executa SSR (Reflexão do espaço da tela - cálculo das reflexões no espaço da tela); tudo isso é feito de uma só vez e no final é misturado, levando em consideração o coeficiente que determina se o SSR detectou a reflexão (provavelmente o coeficiente não é binário, mas é um valor no intervalo [0, 1]). O SSR funciona de maneira padrão em muitos jogos - rastreia repetidamente o buffer de profundidade, tentando encontrar a melhor interseção entre o raio refletido pela superfície sombreada e outra superfície em qualquer lugar da tela. O SSR trabalha com a cadeia mip da escala anteriormente reduzida do buffer HDR atual, e não com o buffer inteiro.

Também existem fatores de correção como o brilho da reflexão, bem como a textura peculiar de Fresnel, calculada antes dessa passagem, com base nas normais e na rugosidade. Não tenho muita certeza, mas depois de estudar o código de montagem, parece-me que o ROTR só pode calcular o SSR para superfícies lisas. O mecanismo não possui uma cadeia mip de desfoque após o estágio SSR, que existe em outros mecanismos , e não há nada como rastrear o buffer de profundidade usando raios, que varia com base na rugosidade . Em geral, superfícies mais ásperas recebem reflexões de mapas cúbicos ou nem sequer as recebem. No entanto, onde o SSR trabalha, sua qualidade é muito alta e estável, levando em consideração que ele não se acumula ao longo do tempo e que o desfoque espacial não é realizado. Os dados alfa também suportam SSR (em alguns templos você pode ver reflexos muito bonitos na água) e isso é uma boa adição que você não vê frequentemente.

imagem

Reflexões para

imagem

Buffer de reflexão

imagem

Reflexões depois

Nevoeiro iluminado



Em nossa cena, o nevoeiro é mal representado porque escurece o fundo e, portanto, é criado por partículas, para que possamos usar novamente o exemplo com reflexões. O nevoeiro é relativamente simples, mas bastante eficaz. Existem dois modos: global, a cor geral do nevoeiro e a cor da dispersão interna obtida no mapa cúbico. Talvez o mapa cúbico tenha sido retirado novamente dos mapas de reflexão cúbica, ou talvez criado novamente. Nos dois modos, a rarefação do nevoeiro é obtida da textura global de rarefação, na qual as curvas de rarefação são compactadas para vários efeitos. Nesse esquema, é notável que ele produza um nevoeiro iluminado de custo muito baixo, isto é, espalhando mudanças internas no espaço, criando a ilusão de interação do nevoeiro com iluminação distante. Essa abordagem também pode ser usada para dispersão atmosférica interna perto do céu.

imagem

Nevoeiro para

imagem

Nevoeiro depois

Iluminação volumétrica


Nos estágios iniciais do quadro, várias operações são executadas para preparar a iluminação volumétrica. Dois buffers são copiados da CPU para a GPU: índices da fonte de luz e dados da fonte de luz. Ambos são lidos por um sombreador computacional que gera uma textura 3D de 40x23x16 da visualização da câmera, contendo o número de fontes de luz que atravessam esta área. A textura é 40 × 23 porque cada bloco ocupa 32 × 32 pixels (1280/32 = 40, 720/32 = 22,5) e 16 é o número de pixels em profundidade. A textura não inclui todas as fontes de luz, mas apenas aquelas marcadas como volumosas (há três em nossa cena). Como veremos abaixo, existem outros efeitos volumétricos falsos criados por texturas planas. A textura exibida tem uma resolução mais alta - 160x90x64. Após determinar o número de fontes de luz por bloco e seu índice, três shaders computacionais são executados sequencialmente, executando as seguintes operações:

  1. A primeira passagem determina a quantidade de luz que entra na célula dentro do volume na forma de uma pirâmide de visibilidade. Cada célula acumula a influência de todas as fontes de luz, como se tivessem suspensas partículas que reagem à luz e devolvem parte dela à câmera.
  2. A segunda passagem borra a iluminação com um pequeno raio. Provavelmente, isso é necessário para evitar oscilações ao mover a câmera, porque a resolução é muito baixa.
  3. A terceira passagem ignora a textura do volume da frente para trás, adicionando gradualmente a influência de cada fonte e fornecendo a textura final. De fato, simula a quantidade total de iluminação recebida ao longo do feixe a uma determinada distância.Como cada célula contém uma parte da luz refletida pelas partículas na direção da câmera, em cada uma delas receberemos uma contribuição conjunta de todas as células passadas anteriormente. Esta passagem também se confunde.

Quando tudo isso é concluído, obtemos uma textura 3D que relata quanta luz uma determinada posição recebe em relação à câmera. Tudo o que resta a ser feito na passagem em tela cheia é determinar essa posição, encontrar o voxel correspondente da textura e adicioná-lo ao buffer HDR. O sombreador de iluminação em si é muito simples e contém apenas cerca de 16 instruções.

imagem

Iluminação volumétrica

imagem

Iluminação volumétrica após

Rendição do cabelo


Se a função PureHair não estiver ativada, as camadas de cabelo padrão serão renderizadas umas sobre as outras. Essa solução ainda parece ótima, mas eu gostaria de focar na tecnologia mais recente. Se a função estiver ativada, o quadro começa com uma simulação dos cabelos de Lara com uma sequência de shaders computacionais. A primeira parte do Tomb Raider usou uma tecnologia chamada TressFX e, na sequência, a Crystal Dynamics implementou uma tecnologia aprimorada. Após os cálculos iniciais, obtemos até 7 buffers. Todos eles são usados ​​para controlar os cabelos de Lara. O processo é o seguinte:

  1. Inicie um sombreador computacional para calcular valores de movimento com base nas posições anteriores e atuais (para desfoque de movimento)
  2. 1×1 ()
  3. 122 (Triangle Strip) ( — ). , . 7 , . , , . « ».
  4. / quad , , . , , .
  5. 4, ( « »)

Se você estiver interessado em aprender mais sobre isso, a AMD possui muitos recursos e apresentações , porque é uma biblioteca pública criada pela empresa . Fiquei confuso com o estágio anterior ao estágio 1, no qual a mesma chamada de empate é realizada como no estágio 3; diz-se que ele gera apenas valores de profundidade, mas, na verdade, o conteúdo não é renderizado, e isso é interessante; talvez o Renderdoc não esteja me dizendo nada. Eu suspeito que ele pode ter tentado executar uma solicitação de renderização condicional, mas não vejo nenhuma chamada de previsão.

imagem

Hair up

imagem

Pixels visíveis do cabelo

imagem

Cabelos sombreados

Renderização em mosaico de dados e partículas alfa


Objetos transparentes novamente usam a classificação de ladrilhos das fontes de luz calculadas para o passo de iluminação preliminar dos ladrilhos. Cada objeto transparente calcula sua própria iluminação em uma passagem, ou seja, o número de instruções e ciclos se torna bastante assustador (e é por isso que a passagem preliminar da iluminação foi usada para objetos opacos). Objetos transparentes podem até refletir reflexos no espaço da tela se estiverem ativados! Cada objeto é renderizado em ordem de classificação, de trás para a frente, diretamente no buffer HDR, incluindo vidro, chama, água de rotina, etc. A passagem alfa também mostra as bordas destacadas quando Lara foca em algum objeto (por exemplo, uma garrafa com uma mistura combustível em uma caixa à esquerda).


No entanto, as partículas são renderizadas em um buffer de meia resolução para suavizar a enorme carga na largura de banda criada pela repintura, especialmente quando muitas partículas grandes que cobrem a tela são usadas para criar neblina, neblina, chama, etc. Portanto, o buffer HDR e o buffer de profundidade diminuem pela metade de cada lado, após o qual a renderização de partículas começa. As partículas criam uma enorme quantidade de redesenho, alguns pixels são sombreados cerca de 40 vezes. O mapa de calor mostra o que quero dizer. Como as partículas foram renderizadas em meia resolução, o mesmo truque de zoom inteligente é usado aqui como na iluminação ambiente (as lacunas são marcadas no estêncil, a primeira passagem é renderizada em pixels internos, a segunda recria as bordas). Você pode notar que as partículas produzem outros efeitos alfa, como chama,brilhar, etc. Isso é necessário para que o alfa possa ser classificado corretamente em relação a, por exemplo, fumaça. Você também pode notar que os raios de luz "volumétricos" aparecem aqui, provenientes de focos de segurança. Eles são adicionados aqui e não criados no estágio da iluminação volumétrica. Essa é uma maneira barata, porém realista, de criá-los a longas distâncias.

imagem



imagem

-

imagem

1

imagem

2

imagem

3

imagem


imagem



imagem

-


O ROTR executa a velocidade do obturador e a correção de tom em uma única passagem. No entanto, embora geralmente acreditemos que a correção gama ocorre com a correção do tom, esse não é o caso aqui. Existem muitas maneiras de implementar a exposição, como vimos em outros jogos . O cálculo da luminância no ROTR é muito interessante e requer quase nenhum dado ou passagem intermediários, portanto explicaremos esse processo com mais detalhes. A tela inteira é dividida em blocos de 64 × 64, após o qual o cálculo dos grupos (20, 12, 1) de 256 fluxos em cada um começa a preencher a tela inteira. Cada encadeamento executa essencialmente a seguinte tarefa (o pseudocódigo é apresentado abaixo):

 for(int i = 0; i < 16; ++i) { uint2 iCoord = CalculateCoord(threadID, i, j); // Obtain coordinate float3 hdrValue = Load(hdrTexture, iCoord.xyz); // Read HDR float maxHDRValue = max3(hdrValue); // Find max component float minHDRValue = min3(hdrValue); // Find min component float clampedAverage = max(0.0, (maxHDRValue + minHDRValue) / 2.0); float logAverage = log(clampedAverage); // Natural logarithm sumLogAverage += logAverage; } 

Cada grupo calcula a soma logarítmica de todos os 64 pixels (256 threads, cada um dos quais processa 16 valores). Em vez de armazenar o valor médio, ele salva a soma e o número de pixels realmente processados ​​(nem todos os grupos processam exatamente 64 × 64 pixels, porque, por exemplo, eles podem ir além das bordas da tela). Shader sabiamente usa armazenamento de encadeamento local para dividir a soma; cada fluxo trabalha primeiro com 16 valores horizontais e, em seguida, os fluxos separados resumem todos esses valores verticalmente e, finalmente, o fluxo de controle desse grupo (fluxo 0) adiciona o resultado e salva todos no buffer. Esse buffer contém 240 elementos, essencialmente fornecendo o brilho médio de muitas áreas da tela. O comando a seguir inicia 64 threads que circundam todos esses valores e os adicionam,para obter o brilho final da tela. Ele também retorna do logaritmo para unidades lineares.

Não tenho muita experiência com técnicas de exposição, mas a leitura deste post de Krzysztof Narkovic esclareceu algumas coisas. É necessário salvar em uma matriz de 64 elementos para calcular a média móvel, na qual é possível visualizar os valores calculados anteriores e suavizar a curva para evitar mudanças muito nítidas no brilho, criando mudanças nítidas na velocidade do obturador. Esse é um shader muito complexo e ainda não descobri todos os detalhes, mas o resultado final é o valor da velocidade do obturador correspondente ao quadro atual.

Depois de encontrar velocidades adequadas do obturador, uma passagem executa a velocidade final do obturador mais a correção de tons. O ROTR parece usar o mapeamento de tom fotográfico, que explica o uso de meios logarítmicos em vez dos meios usuais. A fórmula de correção tonal no sombreador (após a exposição) pode ser expandida da seguinte maneira:



Uma breve explicação pode ser encontrada aqui . Não consegui descobrir por que é necessária uma divisão adicional por Lm, porque cancela a influência da multiplicação. De qualquer forma, o whitePoint é 1.0, portanto, o processo não faz muito nesse quadro; a imagem altera apenas a velocidade do obturador. Não há limite para os valores do intervalo LDR! Ocorre durante a classificação de cores, quando o cubo de cores limita indiretamente valores maiores que 1,0.

imagem

Exposição a

imagem

Exposição Após

Reflexo da lente


Os reflexos das lentes são renderizados de uma maneira interessante. Um pequeno passo preliminar calcula uma textura 1xN (onde N é o número total de elementos ofuscantes que serão renderizados como reflexos de lente, no nosso caso, existem 28). Essa textura contém o valor alfa da partícula e outras informações não utilizadas, mas, em vez de calculá-lo a partir de uma solicitação de visibilidade ou algo semelhante, o mecanismo calcula-o analisando o buffer de profundidade em torno da partícula no círculo. Para fazer isso, as informações sobre os vértices são armazenadas em um buffer disponível para o pixel shader.


Em seguida, cada elemento é renderizado como simples plano alinhado ao plano emitido a partir de fontes de luz. Se o valor alfa for menor que 0,01, o valor NaN será atribuído à posição para que essa partícula não rasterize. Eles são um pouco como o efeito bloom e adicionam brilho, mas esse efeito em si é criado mais tarde.

imagem

A lente se incendeia

imagem

Elementos de alargamento da lente

imagem

A lente flare após

Bloom


O Bloom usa uma abordagem padrão: diminuindo a amostragem do buffer HDR, os pixels brilhantes são isolados e sua escala é aumentada sequencialmente com desfoque para expandir sua área de influência. O resultado é ampliado para a resolução da tela e a composição é sobreposta. Vale a pena explorar alguns pontos interessantes. Todo o processo é realizado usando 7 shaders computacionais: 2 para downsampling, 1 para desfoque simples, 4 para ampliar.

  1. target (mip 1). . , mip- , 0.02.
  2. mip mip 2, 3, 4 5.
  3. mip 5. , . , .
  4. — . 3 , mip N mip N + 1, , . bloom , .
  5. mip 1 HDR-, bloom.

imagem

Bloom


MIP 1 Bloom


MIP 2 Bloom


MIP 3 Bloom


MIP 4 Bloom

imagem

MIP 5 Bloom


MIP 5 Bloom


MIP 4 Bloom


MIP 3 Bloom

imagem

MIP 2 Bloom


MIP 1 Bloom


Florescer depois O

aspecto curioso é que as texturas em escala reduzida alteram a proporção. Para fins de visualização, eu os corrigi e só posso adivinhar as razões para isso; talvez isso seja feito para que os tamanhos das texturas sejam múltiplos de 16. Outro ponto interessante: como esses shaders geralmente são muito limitados em largura de banda, os valores armazenados na memória compartilhada do grupo são convertidos de float32 para float16! Isso permite que o shader troque operações matemáticas pela duplicação de memória livre e largura de banda. Para que isso se torne um problema, o intervalo de valores deve se tornar bastante grande.

Fxaa


O ROTR suporta uma ampla variedade de técnicas diferentes de suavização de serrilhado, como FXAA (Fast Approximate AA) e SSAA (Super Sampling AA). Vale ressaltar que a opção de ativar o AA temporal está ausente, porque para a maioria dos jogos modernos do AAA, ela se torna padrão. Seja como for, o FXAA lida com sua tarefa notavelmente, o SSAA também funciona bem, essa é uma opção bastante "pesada" se o jogo não apresentar desempenho.

Desfoque de movimento


Parece que o Motion blur usa uma abordagem muito semelhante à solução em Shadows of Mordor. Após renderizar a iluminação volumétrica, uma passagem de renderização separada gera os vetores de movimento dos objetos animados para o buffer de movimento. Em seguida, esse buffer é combinado com o movimento causado pela câmera, e o buffer de movimento final passa a ser inserido na passagem de desfoque, que executa o desfoque na direção indicada pelos vetores de movimento do espaço na tela. Para estimar o raio de desfoque em algumas passagens, a textura dos vetores de movimento em uma escala reduzida é calculada para que cada pixel tenha uma idéia aproximada do tipo de movimento nas proximidades. O desfoque é realizado em várias passagens com meia resolução e, como vimos, posteriormente sua escala com a ajuda do estêncil aumenta em duas passagens. Vários passes são realizados por dois motivos: primeiro,para reduzir o número de leituras de textura necessárias para criar desfoque com um raio potencialmente muito grande e, em segundo lugar, porque são executados diferentes tipos de desfoque. Depende se o personagem animado estava nos pixels atuais.

imagem

Desfoque de movimento para


Velocidade de desfoque de movimento


Motion Blur Pass 1


Passe de desfoque de movimento 2


Motion Blur Pass 3


Motion Blur Pass 4


Passe de desfoque de movimento 5


Passe de desfoque de movimento 6


Desfoque de movimento, aproximando e afastando o zoom


Desfoque de movimento, bordas de zoom

Recursos e detalhes adicionais


Há mais algumas coisas que vale a pena mencionar sem muitos detalhes.

  1. Congelamento da câmera: em clima frio, adiciona flocos de neve e gelo à câmera
  2. Câmera suja: adiciona sujeira à câmera.
  3. Correção de cores: no final do quadro, é realizada uma pequena correção de cores, usando um cubo de cores razoavelmente padrão para executar a correção de cores, conforme descrito acima, e também adiciona ruído para tornar algumas cenas mais severas

UI


A interface do usuário é implementada um pouco incomum - renderiza todos os elementos no espaço linear. Normalmente, no momento da renderização, a interface do usuário já havia feito a correção de tom e a gama. No entanto, o ROTR usa espaço linear até o final do quadro. Isso faz sentido, porque o jogo usa uma reminiscência da interface do usuário 3D; no entanto, antes de gravar imagens sRGB no buffer HDR, elas devem ser convertidas em espaço linear para que a operação mais recente (correção gama) não distorça as cores.

Resumir


Espero que você tenha gostado de ler essa análise da mesma maneira que eu fiz. Pessoalmente, eu definitivamente aprendi muito com isso. Parabéns aos talentosos desenvolvedores do Crystal Dynamics pelo fantástico trabalho realizado para criar esse mecanismo. Também quero agradecer a Baldur Karlsson por seu fantástico trabalho no Renderdoc. Seu trabalho tornou a depuração de gráficos em um PC um processo muito mais conveniente. Eu acho que a única coisa um pouco complicada nessa análise foi rastrear os lançamentos de shader, porque no momento em que escrevemos esse recurso não estava disponível para o DX12. Espero que, com o tempo, apareça e todos ficaremos muito satisfeitos.

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


All Articles