Middle Earth: Shadow of Mordor foi lançado em 2014. O jogo em si foi uma grande surpresa, e o fato de ser um spin-off da história do universo do Senhor do Anel foi bastante inesperado. O jogo obteve grande sucesso e, no momento em que escrevia,
Monolith já havia lançado uma sequência - Shadow of War. Os gráficos do jogo são muito bonitos, especialmente considerando que ele foi lançado para diferentes gerações de consoles, incluindo o Xbox 360 e PS3. A versão para PC é muito bem polida, contém opções gráficas adicionais e pacotes de textura de alta resolução que revelam completamente o potencial do jogo.
O jogo usa o relativamente novo renderizador adiado DX11. Eu usei o
Renderdoc para aprender profundamente as técnicas de renderização do jogo. Durante o trabalho, foram utilizados os parâmetros gráficos máximos possíveis (ultra) e todas as possíveis "loções", como transparência independente da ordem, mosaico, oclusão no espaço da tela e vários desfoque de movimento.
Moldura
Aqui está um quadro que iremos analisar. O jogador está localizado em uma plataforma de madeira na região de Udun. Shadow of Mordor usa mecânica semelhante à mecânica de jogos como Assassin's Creed, onde você pode escalar edifícios e torres e depois apreciar a paisagem digital ao redor dos telhados.
Passagem de profundidade
As aproximadamente 140 primeiras chamadas de renderização fazem um rápido passo preliminar para renderizar os maiores elementos e edifícios de elevação para o buffer de profundidade. A maioria dos objetos não se enquadra nesse passe preliminar, mas ajuda quando o jogo tem um número muito grande de chamadas de empate e você pode olhar para longe. Curiosamente, um personagem que está sempre no centro da tela e ocupa uma fração decente do espaço da tela não se enquadra nessa passagem. Como em muitos outros jogos de mundo aberto, o mecanismo usa os valores z inversos. Essa técnica liga o plano próximo a 1,0 e o plano distante a 0,0 para aumentar a precisão em grandes distâncias e evitar conflitos-z. Leia mais sobre a precisão do buffer z
aqui .
Buffer G
Imediatamente após isso, a passagem do buffer G é iniciada, realizada em aproximadamente 2700 chamadas de empate. Se você leu minha
análise anterior de Castlevania: Lords of Shadow 2 ou estudou outros artigos semelhantes, essa passagem deve ser familiar para você. A propriedade das superfícies é registrada em um conjunto de buffers, que são lidos nas passagens do cálculo da iluminação para calcular as reações das superfícies à luz. Shadow of Mordor usa um renderizador diferido clássico, mas um número relativamente pequeno de destinos de renderização do buffer G é usado para atingir esse objetivo (3). Para comparação: o Unreal Engine nesta passagem usa 5-6 buffers. O buffer G possui o seguinte esquema:
Buffer normal
R | G | B | Um |
Normal.x | Normal.y | Normal.z | ID |
O buffer de normais armazena os normais no espaço mundial no formato de "8 bits por canal". Isso é apenas o suficiente, e às vezes nem um pouco, para descrever superfícies planas que variam suavemente. Se você olhar de perto, isso pode ser visto em algumas poças do jogo. O canal alfa é usado como um ID que marca vários tipos de objetos. Por exemplo, descobri que 255 se refere ao personagem, 128 à parte animada da bandeira, e o céu é marcado com o ID 1, porque posteriormente esses identificadores são usados para filtrá-lo no estágio de adição (o céu recebe sua própria flor radial).
No artigo original, essas e muitas outras imagens são animadas para maior clareza, por isso recomendo procurar lá.Buffer Albedo
R | G | B | Um |
Albedo.r | Albedo.g | Albedo.b | Oclusão da cavidade |
O buffer albedo armazena todos os três componentes albedo e uma oclusão em pequena escala (às vezes chamada de oclusão de cavidade), usada para sombrear pequenos detalhes que não podem ser alcançados por um mapa de sombras ou pós-processamento no espaço da tela. É usado principalmente para fins decorativos, por exemplo, buracos e vincos nas roupas, pequenas rachaduras na árvore, pequenos padrões nas roupas da Talion, etc.


Ao processar inimigos no shader, albedo leva em consideração a textura do sangue (curiosamente, Talion nunca fica ferido visível). A textura do sangue é a entrada para a fase de renderização das roupas e do corpo dos inimigos, mas não especifica a cor do sangue, que é a entrada no buffer constante, mas determina os fatores / níveis sanguíneos para controlar a quantidade exibida de sangue. Além disso, a orientação normal é usada para dimensionar o efeito, permitindo controlar a direção dos respingos de sangue. Em seguida, albedo sombreia essencialmente o brilho das feridas recebidas pelo inimigo, tiradas do local apropriado no mapa de sangue, e também modifica outras propriedades, como a especular, para obter um efeito sanguíneo convincente. Não consegui encontrar a parte do quadro em que o mapa é renderizado, mas presumo que eles sejam gravados logo no início do quadro, quando a espada é exposta, e depois usados aqui.
Buffer especular
R | G | B | Um |
Rugosidade | Intensidade especular | Fresnel | Fator de espalhamento subterrâneo |
O buffer especular contém outras propriedades de superfície que podem ser vistas em jogos, como rugosidade (não é exatamente rugosidade, mas um grau especular em escala, mas pode ser interpretado dessa maneira), intensidade especular (o brilho da reflexão especular) que escala albedo para obter o fator de refletividade especular de cor correto (comumente referido na literatura como F0, porque é a entrada para a resposta do espelho de Fresnel) e o componente de espalhamento de subsuperfície (espalhamento de subsuperfície). O último componente é usado para iluminar materiais translúcidos, como tecidos finos, plantas e pele. Se mais tarde mergulharmos no estudo do shader de iluminação, descobrimos que aqui usamos a variação do
modelo especular normalizado de acordo com Blinn-Fong .
Decalques diferidos
Como vimos acima, Shadow of Mordor exibe traços bastante detalhados de sangue nos personagens feridos. Quando Talion agita sua espada, o ambiente também recebe sua parcela de sangue orc escuro. No entanto, outra técnica é usada para ambientes -
adesivos adiados . Essa técnica consiste em projetar um conjunto de texturas planas na superfície do que foi renderizado anteriormente. Dessa forma, o conteúdo do buffer G é substituído por esse novo conteúdo antes de executar o passo de iluminação. No caso do sangue, basta apenas um spray sangrento e, ao renderizar muitos decalques, uma paisagem sombria é criada rapidamente.
A última coisa que é renderizada na passagem do buffer G é o céu, a textura do céu é de alta resolução (8192 × 2048) no formato HDR BC6H. Eu tive que fazer uma pequena correção de tom, porque no HDR todas as cores são muito escuras.
Mosaico
Um "recurso" muito interessante do jogo (se estiver ativado) é o mosaico. É usado para muitas coisas diferentes, desde o terreno até a renderização de personagens (assim como adereços e objetos de personagens). Aqui, o mosaico não subdivide a malha poli baixa, mas cria polígonos a partir de uma nuvem de pontos, usando o grau necessário de subdivisão, dependendo dos critérios para o nível de detalhe, por exemplo, a distância da câmera. Um exemplo interessante é a capa Talion, que é transmitida à GPU como uma nuvem de pontos (após simulação física), e o shader de mosaico recria os polígonos.
Transparência independente do pedido

Uma das primeiras coisas que me impressionou com sua estranheza é o passe de tratamento capilar, porque ele executa um shader especial muito complexo. Nas opções gráficas, é mencionada a opção OIT (Transparência independente do pedido) para cabelos, ou seja, deve ser. Primeiro, ele executa a saída em um buffer separado e calcula o número total de pixels transparentes sobrepostos, salvando propriedades em uma estrutura "profunda" semelhante a um buffer G. Mais tarde, outro shader classifica fragmentos individuais individuais de acordo com sua profundidade. Parece que as flechas também são renderizadas dessa maneira (provavelmente, sua plumagem requer classificação adequada). Esse é um efeito muito sutil que não adiciona diferenças gráficas especiais, mas ainda é um detalhe interessante. Aqui está um exemplo simples: a imagem acima mostra o número de fragmentos sobrepostos (quanto mais vermelhos, mais fragmentos). A transparência regular ainda é classificada pela CPU e renderizada como alfa tradicional. Somente entidades individuais se enquadram no passe OIT.
Sombras de Mordor
SoM tem muitas fontes de sombra. Além dos tradicionais mapas de sombras de fontes de luz dinâmicas, o SoM usa uma oclusão ambiental de dois canais no espaço da tela, uma oclusão em microescala criada para quase todos os objetos no jogo e uma textura de oclusão semelhante a um mapa de altura com uma vista superior.
Oclusão no espaço da tela
A primeira passagem é processada usando o buffer G ambiente e a oclusão especular no espaço da tela. O sombreador em si é um enorme ciclo desdobrado que mostra o mapa de profundidade em tamanho real e o mapa de profundidade médio anteriormente reduzido, procurando amostras vizinhas em um determinado padrão. Ele usa uma textura quadrada 4x4 para selecionar vetores pseudoaleatórios em busca de fontes de oclusão. Ele produz um buffer de oclusão barulhento, que é suavizado por um simples borrão em duas passagens. A característica mais interessante aqui é que existem dois canais de oclusão diferentes: um deles é usado como oclusão especular e o outro como oclusão difusa. Nas implementações padrão do SSAO, é calculado um canal que se aplica a toda iluminação cozida. Aqui, o cartão SSAO também é lido para transmissão à passagem de iluminação direcional, onde é usado.
Cartões de sombra
O próximo evento é a renderização do mapa de sombras. Como a ação do jogo ocorre principalmente em espaços abertos, a maior parte da luz e das sombras é retirada da luz direcional principal. Aqui, usamos a técnica de
mapas de sombras em cascata (uma variação dos quais são
mapas de sombras divididos em paralelo ), uma técnica bastante padrão para aplicar sombras a longas distâncias, que consiste em renderizar a mesma cena de um ponto de vista da fonte de luz para diferentes áreas do espaço. Geralmente, os mapas de sombras distantes da área de cobertura da câmera estão a grandes distâncias ou têm resolução menor que os anteriores, reduzindo essencialmente a resolução em áreas nas quais os detalhes ainda não são necessários devido ao fato de a geometria estar muito distante. Nesta cena, o jogo renderiza três cascatas de sombras 4096 × 4096 (na verdade, o jogo tem lugar para quatro). A cascata superior está muito próxima de Talion, e a inferior inclui montanhas e objetos muito distantes da câmera. Ao trabalhar com sombras, o jogo usa o mesmo truque com a coordenada z inversa que no mapa de profundidade. \
Buffer de sombra
O próximo passo é criar um buffer de sombra. Essa é uma textura de canal único, baseada nas informações de oclusão dos mapas de sombras anteriores, que codifica um fator de sombra no intervalo [0, 1]. Para criar suavidade nas bordas, o mapa de sombras é amostrado 4 vezes usando o estado de um amostrador bilinear especial, que recebe 4 amostras e as compara com um determinado valor (isso é chamado de
Filtragem por fechamento por porcentagem ). Obter várias amostras e calcular a média de seus resultados costuma ser chamado de
Porcentagens mais próximas de sombras suaves . Além de ler o mapa de sombras, também é amostrado o último componente do buffer especular (ou seja, o coeficiente de dispersão do subsolo), multiplicado pelo “fator de sangramento leve”. Parece que isso é necessário para eliminar a sombra desses objetos, para que um pouco mais de luz passe por eles.
Textura de projeção direcional
Outra fonte de luz e sombra é uma textura da vista superior amostrada por uma fonte de luz direcional. Essa é a conversão de cores adicionada à cor da fonte de luz direcional, mais o efeito do sombreamento global que se aplica à iluminação direcional. Parece que alguns deles foram criados manualmente em cima de um mapa de iluminação de nível gerado automaticamente com uma vista superior. Parece que as arestas das sombras para geometria estática são ajustadas manualmente (talvez para evitar conflitos com o mapa real de sombras), e algumas partes também parecem ser levemente pintadas à mão. Provavelmente, a tarefa dessa textura é a adição de baixo custo da oclusão ambiental em larga escala e simulações leves de iluminação global, além da iluminação direcional. As imagens abaixo mostram o matiz, a oclusão e o produto de ambos os fatores, o que nos dá uma ideia de como será a máscara de cor final.



O resultado de todas as passagens de iluminação é salvo no destino de renderização do formato R11G11B10F. Aqui está a aparência do resultado. Realizei uma correção tonal dos resultados para tornar o efeito da iluminação direcional no nível mais visível.
Todas as montanhas distantes (não mostradas na imagem acima) também são iluminadas pela luz direcional, mas são destacadas como um caso separado, para que a iluminação possa ser melhor controlada. Alguns são redimensionados, mas os mais distantes são na verdade impostores com mapas normais e albedo criados de maneira inteligente. Eles têm fontes especiais de iluminação direcional que afetam apenas as montanhas.
Iluminação estática
O Shadow of Mordor usa uma implementação muito intensiva de memória da iluminação estática, que utiliza texturas de volume muito grandes. A imagem abaixo mostra três texturas estáticas para a quantidade de iluminação usada para iluminar difusamente parte dessa área. Cada um deles é uma textura compactada enorme de 512x512x128 BC6H, ou seja, eles ocupam 32 MB por textura ou 96 MB em geral (tocamos com configurações de qualidade máxima). A textura da cor indica a intensidade que entra no voxel. Os outros dois indicam a força ou magnitude dessa intensidade ao longo de todas as seis direções xyz e -xyz, e o normal é usado para selecionar três componentes (xyz positivo ou negativo, aqueles que são mais consistentes com o normal). Tendo construído esse vetor, tomamos seu produto vetorial pelo quadrado do normal, e isso se torna o fator de escala para a intensidade. A fórmula é a seguinte:


Os volumes de luz estática também renderizam um mapa cúbico para iluminação especular, que provavelmente é capturada no centro do SLV. Curiosamente, as texturas de volume armazenam valores HDR compactados em BC6H e os mapas cúbicos são armazenados no formato BC3 (DXT5), que não pode armazenar valores de ponto flutuante. Para compensar essa limitação, o canal alfa retém o brilho, que então varia de 1 a 10. Essa é uma decisão um pouco estranha e, para mim, parece mais uma implementação do Legacy. Não esqueça que o jogo foi lançado para os consoles da geração anterior, que não suportam os novos formatos de textura HDR.
Os quadros abaixo mostram os resultados “antes e depois”, levando em consideração o impacto da imagem média. Para visualização, realizei a correção tonal.
Nevoeiro atmosférico
Shadow of Mordor tem um sistema de tempo e tempo, graças ao qual o sol brilha ou a chuva flui através do jogo em Mordor. Este sistema é controlado pela soma dos componentes, e um dos mais importantes é o nevoeiro. Shadow of Mordor usa um modelo bastante simples, mas fisicamente sólido, de neblina atmosférica, incluindo espalhamento único de
Rayleigh , bem como espalhamento por uma partícula esférica (espalhamento de Mie).
Começa calculando a posição da câmera em relação ao
centro da terra . Várias fórmulas trigonométricas permitem determinar onde a câmera está localizada na atmosfera, onde está o pixel e qual a distância que o feixe viaja na atmosfera a uma determinada altura máxima da atmosfera. No nosso caso, a atmosfera é definida a uma altura de 65.000 metros acima da superfície do planeta. Com essas informações em mente, Rayleigh e os coeficientes de partículas esféricos são usados para calcular os tipos de densidades das partículas de nevoeiro e suas cores. Essas densidades obscurecem os pixels já sombreados, espalhando a luz que entra na câmera a partir da superfície sombreada e contribuindo para o nevoeiro. Ao simular essa dispersão, o brilho e a direção do sol são levados em consideração.
Velocidade do obturador e correção de tom
Ao calcular a velocidade do obturador, é utilizada uma abordagem bastante padrão: reduzir sequencialmente a resolução do buffer de brilho, calculado a partir do buffer de cores HDR principal, em uma cadeia de texturas, cada uma com metade do tamanho da textura anterior, começando com uma textura 1/3 do buffer do quadro principal. Com essa diminuição na resolução, são coletadas 4 amostras, com média dos valores dos pixels vizinhos, ou seja, após a conversão de todos os valores médios em um único texel, o resultado final se torna brilho médio. Depois que a textura atinge o tamanho de 16 × 9 texels, um sombreador de computação é iniciado, resumindo todos os texels restantes. Este valor é lido imediatamente no passo de correção de tom para alterar os valores de brilho.
Para correção de tom, é utilizado o operador Reinhardt, cuja fórmula otimizada pode ser encontrada
aqui e
aqui . No código hlsl, isso será parecido com isto:
float3 hdrColor = tex2D(HDRTexture, uv.xy); hdrColor *= exposureValue;
Se traçarmos essa curva, veremos que esse operador descarta 10% dos valores de branco, mesmo com um valor de entrada de 2,0, ao mesmo tempo que deixa forçosamente uma pequena parte do intervalo inferior completamente preta. Isso cria uma imagem escura e desaturada.
Alpha Stage
A etapa alfa é um pouco incomum porque renderiza objetos diretamente no buffer LDR. Outros jogos também os renderizam no buffer HDR para que eles possam participar do passe do obturador. Seja como for, a textura de brilho calculada anteriormente é limitada a todos os objetos iluminados por alfa (em alguns casos, por exemplo, para objetos emissores de luz, a velocidade do obturador é calculada usando as constantes do shader, não a pesquisa de textura) e, portanto, a velocidade do obturador é aplicada automaticamente ao renderizar, e não realizado no pós-processamento. Um caso muito específico de usar alfa em um jogo é uma transição para o modo fantasma (nele o fantasma de Celebrimbor é renderizado em cima do personagem do jogador, forjado nos anéis soberanos do universo LOTR; assim, o jogo mostra que ele está sempre por perto, embora invisível). O jogo passa vários parâmetros para as malhas dos dois personagens, que controlam a opacidade e permitem que o jogo oculte parcialmente Talion e gradualmente mostre o Celebrimore. Outros objetos no jogo no modo fantasma também renderizam versões fantasma em cima de objetos opacos, como inimigos e torres. Aqui está outra cena com a transição para o mundo fantasmagórico.




Chuva
No quadro principal que examinamos, não há chuva, mas o clima é uma parte tão importante do jogo que eu gostaria de mencionar aqui. Ele é gerado e simulado na GPU e renderizado no final da fase alfa. É executado um sombreador de computação que executa a simulação e grava as posições no buffer. Essas posições são ocupadas por outro sombreador, que, com a ajuda de chamadas indiretas instanciadas, processa tantas cópias de quads quanto as posições calculadas no passe anterior. O sombreador de vértices possui um quad simples, que, se necessário, é deformado e gira em direção à câmera. Para impedir que a chuva penetre nas superfícies, o sombreador de vértice também lê mapas de altura de uma vista superior, o que permite rejeitar todas as gotas abaixo da superfície sobreposta. Esse mapa de altura é renderizado logo no início do quadro. O mesmo shader de vértice informa ao shader de pixel onde obter a amostra da textura da gota; se a gota estiver próxima da superfície, ela seleciona uma área de textura contendo uma animação de abertura. Além disso, as gotas de chuva realizam cálculos de neblina no sombreador de pixels para combinar perfeitamente com o resto da cena. Aqui está uma captura de tela da mesma perspectiva em um dia chuvoso.


Quando o efeito da chuva é ativado, o buffer especular é globalmente modificado para criar superfícies molhadas, e as ondas da chuva são renderizadas no buffer normal. A animação é cronometrada, portanto, apenas um quadro da animação em loop é usado. O buffer normal mostrado abaixo é modificado para mostrar as ondas renderizadas no buffer.
Alargamentos da lente e flor
Após a conclusão da renderização alfa, os reflexos da lente são renderizados sobre ela. Uma série de quadríceps deslocados é renderizada a partir do ponto em que a luz direcional vem (no nosso caso, o sol). Imediatamente após isso, um passe de bloom é realizado. Essa é uma técnica bastante padrão, que consiste em uma série de tamanho reduzido e texturas borradas contendo pixels cujo brilho excede um determinado limite. São utilizados dois passes de floração, comuns com o desfoque gaussiano para toda a cena e um desfoque radial especial que se aplica apenas ao céu. O desfoque radial é uma das operações que usam um ID especial do buffer G de mapas normais, porque apenas os pixels no céu são levados em consideração. Como um bônus, esse desfoque irá amostrar o mapa de profundidade e pode criar
raios crepusculares de baixo custo. Como estamos trabalhando com um buffer LDR nesse estágio, o valor do limite de bloom é diferente do valor do tapete HDR (valores acima do limite, geralmente 1,0, levam ao cálculo), e isso significa que o valor do bloom obtido dele é um pouco limitado. De qualquer forma, isso é bom para o jogo e aqui estão os resultados. Nas imagens abaixo, as cores da textura bloom mip parecem um pouco estranhas, pois cada pixel é dimensionado pelo brilho contido no canal alfa. Esse brilho foi calculado anteriormente, no estágio de correção de tons. Na composição final, bloom é calculado como
bloom.rgb · bloom.a · bloomScale .
Antialiasing + profundidade de campo
Não há muito o que dizer sobre essas duas operações; abordagens padrão do setor são usadas. Uma simples passagem pelo antialiasing FXAA é realizada imediatamente após a composição do bloom com a imagem LDR, e a profundidade de campo é realizada imediatamente após. Para profundidade de campo, o jogo renderiza duas versões borradas diminuídas do buffer final. Em seguida, a profundidade do pixel é usada para misturar imagens borradas e normais, o que dá o efeito de desfocar. Por uma questão de clareza, nessa captura, exagerei o efeito da profundidade de campo. O jogo possui um modo de captura de tela embutido, permitindo que você configure facilmente essas condições.
Desfoque de movimento
O desfoque de movimento consiste em duas passagens. Primeiro, os dados das orientações anteriores e atuais da câmera são transferidos para o buffer de velocidade de tela cheia. Nesse caso, dois canais da textura são preenchidos com velocidade no espaço da tela. Agora, o canal r contém a magnitude da mudança de pixel na direção horizontal da tela e o canal g contém a magnitude na direção vertical. É assim que as faixas radiais são obtidas ao mover a câmera. O personagem é renderizado novamente, desta vez preenchendo o canal azul com base em suas poses atuais e anteriores, como é o caso da câmera. O canal azul é usado para marcar se a renderização deve ou não ser renderizada. O canal alfa também é preenchido com um valor constante (0,0598), mas eu não investiguei seu valor ou seus objetivos. A resolução do buffer de velocidade é reduzida para uma textura muito pequena, calculando a média de uma faixa relativamente ampla de velocidades na textura original. Na última passagem, isso dá a cada pixel uma idéia aproximada do raio do desfoque em uma passagem de desfoque real.
Em seguida, o desfoque lê as texturas de velocidade, um mapa de profundidade, o buffer de cores original e a textura do ruído. O último é usado para ocultar o efeito de uma imagem espelhada, que pode ocorrer com esse tipo de desfoque com um raio grande. Em seguida, o buffer de imagem é amostrado várias vezes na direção indicada pelo buffer de velocidade, as cores são calculadas como médias, o que leva ao desfoque da imagem na direção dos vetores de movimento. Além disso, esse efeito é dimensionado de acordo com a taxa de quadros com a qual o jogo funciona. Para esta captura, tive que limitar o jogo a 30fps, porque a 60fps e acima disso quase não se nota.
Correção de cores
A passagem final da correção de cores é realizada usando os "cubos de cores". Um cubo de cores é uma textura 3D cujos componentes rgb se ajustam às coordenadas xyz da textura. Essas coordenadas xyz contêm a cor com a qual precisamos substituir a cor original. No nosso caso, a tabela de pesquisa (LUT) é neutra (ou seja, as coordenadas e a cor contêm o mesmo valor), então modifiquei a mesma cena usando as predefinições que o jogo fornece no editor de câmera.
Quadro final
Após a criação do quadro principal em um buffer separado, a interface do usuário é renderizada. Isso garante que, independentemente do tamanho selecionado para o buffer traseiro, a interface do usuário sempre fique clara e bonita no tamanho da janela nativa, enquanto o jogo poderá alterar a resolução, se necessário, para garantir a velocidade. No final, as duas texturas são misturadas com base nos dados do canal alfa da interface do usuário e renderizadas no buffer de quadro final, que está pronto para exibição na tela.
Espero que tenham gostado da minha análise. Quero agradecer a
Adrian Correge pelo incrível trabalho que me inspirou a estudar os gráficos, bem como a equipe do estúdio
Monolith por este jogo verdadeiramente inesquecível.