Neste post, consideraremos a etapa do trabalho com vértices. Ou seja, teremos novamente que obter os livros didáticos de matemática e lembrar álgebra linear, matrizes e trigonometria. Viva!
Vamos descobrir como os modelos 3D são transformados e as fontes de luz são levadas em consideração. Também explicaremos em detalhes a diferença entre vértices e sombreamentos geométricos, e você descobrirá em que estágio é o local do mosaico. Para facilitar o entendimento, usamos diagramas e exemplos de código que demonstram como o jogo executa cálculos e processa valores.
A captura de tela no início do post mostra o jogo GTA V no modo de exibição de estrutura de arame. Compare-o com a estrutura de arame half-Life muito menos complexa 2. As imagens foram criadas por thalixte com
ReShade .
Qual é o objetivo?
No mundo da matemática, um ponto é simplesmente um lugar no espaço geométrico. Como não há nada menor que um ponto, ele não tem tamanho; portanto, os pontos podem ser usados para especificar a localização exata do início e do fim dos objetos, como segmentos de linha, planos e volumes.
Para gráficos 3D, essas informações são extremamente importantes, a aparência de tudo depende disso, porque todos os objetos são exibidos como conjuntos de segmentos de linhas, planos etc. A imagem abaixo mostra uma captura de tela do Bethesda 2015
Fallout 4 :
Pode não ser fácil para você ver que isso é apenas um monte de pontos e linhas, por isso mostraremos como a mesma cena fica no modo de estrutura de arame. Nesse modo, o mecanismo de renderização 3D ignora as texturas e os efeitos executados no estágio do pixel e desenha apenas linhas multicoloridas conectando os pontos.
Agora tudo parece completamente diferente, mas vemos como todas as linhas são combinadas para formar vários objetos, ambientes e planos de fundo. Alguns consistem em apenas dezenas de linhas, por exemplo, pedras em primeiro plano, enquanto outras contêm tantas linhas que parecem sólidas.
Cada ponto no início e no final de cada linha é processado executando vários cálculos. Alguns cálculos são muito simples e rápidos, outros são muito mais complicados. Ao processar pontos em grupos, especialmente na forma de triângulos, você pode obter um aumento significativo na produtividade, então vamos dar uma olhada neles.
O que é necessário para um triângulo?
O nome
triângulo deixa claro que a figura tem três cantos internos; Para fazer isso, ela precisa de três pontos de canto e três segmentos conectando-os. É correto chamar um ponto de
vértice de vértice (no plural - vértices); cada vértice é definido por um ponto. Como estamos em um mundo geométrico tridimensional, o
sistema de coordenadas cartesianas é usado para obter pontos. Normalmente, as coordenadas são escritas na forma de três valores, por exemplo, (1, 8, -3) ou genericamente (
x, y, z ).
Em seguida, podemos adicionar mais dois vértices para formar um triângulo:
Observe que as linhas mostradas são opcionais - podemos definir os pontos e informar ao sistema que esses três vértices formam um triângulo. Todos os dados de vértice são armazenados em um bloco de memória contíguo chamado
buffer de vértice ; as informações sobre a figura que eles formam são codificadas diretamente no programa de renderização ou armazenadas em outro bloco de memória chamado
buffer de índice .
Se as informações são codificadas em um programa de renderização, as várias formas que podem ser formadas por vértices são chamadas
primitivas . O Direct3D sugere o uso de uma lista para eles, tiras e ventiladores na forma de pontos, linhas e triângulos. Quando usadas corretamente, as faixas de triângulos usam vértices para mais de um triângulo, o que melhora a produtividade. No exemplo abaixo, vemos que, para criar dois triângulos conectados, são necessários apenas quatro vértices - se eles estiverem separados, precisaremos de seis vértices.
Da esquerda para a direita: lista de pontos, lista de linhas e tira de triângulosSe precisarmos processar um conjunto maior de vértices, por exemplo, em um modelo de NPC de jogo, é melhor usar um objeto chamado
malha , outro bloco de memória, mas que consiste em vários buffers (vértices, índices etc.) e recursos de textura do modelo . A
documentação online da Microsoft tem uma breve explicação de como usar esses buffers.
Por enquanto, vamos nos concentrar no que acontece com esses vértices em um jogo em 3D ao renderizar cada novo quadro. Em resumo, eles executam uma das duas operações:
- O vértice se move para uma nova posição.
- Mudanças na cor do vértice
Pronto para matemática? Excelente, porque precisamos disso.
Vetor aparece no palco.
Imagine que você tem um triângulo na tela e pressiona uma tecla para movê-lo para a esquerda. Naturalmente, esperamos que os números (
x, y, z ) de cada vértice sejam alterados de acordo; é isso que acontece, mas uma
maneira inesperada
de implementar mudanças. Em vez de simplesmente alterar as coordenadas, a grande maioria dos sistemas de renderização de gráficos 3D usa uma ferramenta matemática especial: queremos dizer
vetores .
Um vetor pode ser representado como uma seta apontando para um ponto específico no espaço e tendo o comprimento desejado. Os vértices são geralmente definidos usando vetores baseados em coordenadas cartesianas:
Observe que a seta azul começa em um lugar (neste caso, o
ponto de origem ) e se estende até o topo. Para definir o vetor, usamos um
registro em uma coluna , mas é bem possível usar um
registro em uma linha . Você deve ter notado que existe outro quarto valor, geralmente chamado
de componente w . É usado para mostrar o que o vetor significa: posição do ponto (
vetor da posição ) ou direção geral (vetor da
direção ). No caso de um vetor de direção, ele terá a seguinte aparência:
Esse vetor aponta na mesma direção e tem o mesmo comprimento que o vetor de posição anterior, ou seja, os valores (
x, y, z ) serão os mesmos; no entanto, o componente
w não é 1, mas zero. Explicaremos o uso de vetores de direção posteriormente, mas, por enquanto, lembre-se do fato de que todos os vértices na cena 3D serão descritos dessa maneira. Porque Porque neste formato é muito mais fácil movê-los.
Matemática, matemática e novamente matemática
Lembre-se de que temos um triângulo simples e queremos movê-lo para a esquerda. Cada vértice é descrito por um vetor de posição; portanto, a “matemática do movimento” (chamada
transformações ) deve trabalhar com esses vetores. Uma nova ferramenta aparece:
matrizes (
matriz no singular). Essa é uma matriz de valores gravados em um formato semelhante a uma planilha do Excel, com linhas e colunas.
Para cada tipo de transformação, há uma matriz correspondente e, para uma transformação, basta multiplicar a matriz de transformação e o vetor de posição. Não entraremos em detalhes de como e por que isso acontece, mas apenas veremos como fica.
Mover um vértice no espaço 3D é chamado de
tradução e requer o seguinte cálculo:
Valores
x 0 , etc. representa as coordenadas originais do vetor; valores
delta -
x representam a quantidade pela qual o vértice precisa ser movido. A multiplicação da matriz e do vetor leva ao fato de que eles simplesmente resumem (observe que o componente
w permanece inalterado, de modo que a resposta final permanece o vetor de posição como antes).
Além de mover, também podemos precisar rotacionar o triângulo ou alterar sua escala - para essas operações, também há transformações.
Essa transformação gira o vértice em torno do eixo z no plano XYE isso é usado se você precisar alterar a escala da figuraPodemos usar a ferramenta gráfica baseada em WebGL da
Real-Time Rendering para visualizar esses cálculos para a figura inteira. Vamos começar com a caixa na posição padrão:
Nesta ferramenta online, o ponto do modelo é o vetor de posição, a matriz mundial é a matriz de transformação e o ponto do espaço mundial é o vetor de posição do vértice transformado.
Vamos aplicar várias transformações à caixa:
Na imagem acima, a figura foi
movida 5 unidades ao longo de cada eixo. Esses valores podem ser vistos na última coluna da matriz grande do meio. O vetor de posição original (4, 5, 3, 1) permanece o mesmo que deveria, mas o vértice transformado agora é movido para (9, 10, 8, 1).
Nesta transformação, tudo foi dimensionado por um fator de 2: agora os lados da caixa se tornaram o dobro do tempo. Por fim, veja o exemplo de rotação:
O paralelepípedo foi girado através de um ângulo de 45 °, mas o
seno e o
cosseno desse ângulo são usados na matriz. Depois de verificar em uma calculadora científica, podemos ver que
sin (45 °) = 0,7071 ..., que é arredondado para o valor mostrado de 0,71. Nós obtemos a mesma resposta para o valor do
cosseno .
Matrizes e vetores são opcionais; Uma alternativa popular para eles, especialmente ao lidar com curvas complexas, é o uso de números complexos e
quaterniões . Esses cálculos são muito diferentes dos vetores, portanto, não os consideraremos, continuando a trabalhar com transformações.
Poder do shader do vértice
Nesse estágio, precisamos entender que tudo isso é feito por pessoas que programam o código de renderização. Se um desenvolvedor de jogos usa um mecanismo de terceiros (por exemplo, Unity ou Unreal), tudo isso já foi feito por ele; mas se alguém fizer seu motor do zero, precisará executar todos esses cálculos com vértices.
Mas como tudo isso parece em termos de código?
Para entender isso, usaremos exemplos do incrível site
Braynzar Soft . Se você deseja começar a trabalhar com programação 3D, então este é o lugar certo para aprender o básico, bem como as coisas mais complexas ...
Este é um exemplo de uma transformação all-in-one. Ele cria as matrizes de transformação apropriadas com base na entrada do teclado e as aplica ao vetor de posição original em uma operação. Observe que isso sempre é feito na ordem especificada (redimensionamento - rotação - transferência), porque qualquer outra maneira arruinará completamente o resultado.
Esses blocos de código são chamados de
sombreadores de vértices ; sua complexidade e tamanho podem variar enormemente. O exemplo acima é simples, é
apenas um shader de vértice que não usa toda a natureza programável dos shaders. Uma sequência mais complexa de shaders pode transformar objetos no espaço 3D, processar sua aparência do ponto de vista da câmera de cena e depois transferir dados para o próximo estágio do processo de renderização. Considerando a ordem do processamento do vértice, estudaremos outros exemplos.
Obviamente, eles podem ser usados para muito mais; portanto, ao jogar um jogo em 3D, não esqueça que todo o movimento que você vê é feito por uma GPU executando comandos de vertex shader.
No entanto, esse nem sempre foi o caso. Se você voltar a meados dos anos 90, as placas gráficas daquela época não tinham a capacidade de processar independentemente vértices e primitivas, apenas o processador central fazia tudo isso.
Um dos primeiros processadores com sua própria aceleração de hardware desse processo foi a
Nvidia GeForce, lançada em 2000 , e essa funcionalidade foi chamada de
Hardware Transform and Lighting (abreviação de Hardware TnL). Os processos com os quais esse equipamento podia lidar eram muito limitados em termos de equipes, mas com o lançamento de novos chips, a situação mudou rapidamente. Hoje, não há equipamentos separados para o processamento de vértices, e um dispositivo faz tudo de uma vez: pontos, primitivas, pixels, texturas etc.
A propósito, sobre
iluminação : vale a pena notar que vemos tudo graças à luz, então vamos ver como ele pode ser processado no estágio do vértice. Para fazer isso, precisamos aproveitar o que falamos anteriormente.
Luz, câmera, motor!
Imagine esta foto: o jogador está em um quarto escuro, iluminado por uma fonte de luz à direita. No meio da sala há uma chaleira enorme. Você pode precisar de ajuda com isso, então vamos usar o site de
renderização em tempo real e ver como fica:
Não esqueça que este objeto é um conjunto de triângulos planos conectados juntos; isto é, o plano de cada triângulo será direcionado em uma determinada direção. Alguns deles são direcionados para a câmera, alguns - para outros, alguns serão distorcidos. A luz da fonte cai em cada plano e é refletida a partir de um certo ângulo.
Dependendo de onde a luz é refletida, a cor e o brilho do avião podem mudar e, para que a cor do objeto pareça correta, tudo isso precisa ser calculado e levado em consideração.
Para começar, precisamos descobrir para onde cada plano é direcionado e, para isso, precisamos
do vetor normal do plano. Essa é outra seta, mas, diferentemente do vetor de posição, seu tamanho não é importante (de fato, depois de calcular a escala dos vetores normais sempre diminui para que eles tenham um comprimento de 1), e é sempre direcionado
perpendicularmente (em ângulo reto) ao plano.
O normal para o plano de cada triângulo é calculado determinando o produto vetorial de dois vetores de direção (mostrados acima
p e
q ) que formam os lados do triângulo. De fato, é melhor calculá-los para cada vértice, e não para um triângulo, mas como sempre há mais do que o último, será mais rápido calcular as normais dos triângulos.
Depois de receber o normal na superfície, você pode começar a considerar a fonte de luz e a câmera. Na renderização 3D, as fontes de luz podem ser de tipos diferentes, mas neste artigo consideraremos apenas fontes
direcionais , por exemplo, holofotes. Como o plano do triângulo, o foco e a câmera apontam em uma determinada direção, algo como isto:
O vetor da fonte de luz e o vetor normal podem ser usados para calcular o ângulo em que a luz cai na superfície (usando a relação entre o produto escalar dos vetores e o produto de seu tamanho). Os vértices do triângulo conterão informações adicionais sobre sua cor e material. O material descreve o que acontece com a luz quando atinge a superfície.
Uma superfície lisa de metal refletirá quase toda a luz incidente no ângulo em que caiu e mal mudará a cor do objeto. O material fosco áspero dispersa a luz de maneira menos previsível e muda ligeiramente de cor. Para levar isso em consideração, você precisa adicionar valores adicionais aos vértices:
- Cor base original
- Atributo de material ambiente - um valor que determina quanto a iluminação de "fundo" pode absorver e refletir um vértice
- O atributo do material Difuso é outro valor, mas desta vez a determinação da “rugosidade” do vértice, que, por sua vez, afeta a quantidade de absorção e reflexão da luz dispersa.
- Atributos de material especular - dois valores que especificam o brilho do vértice
Diferentes modelos de iluminação usam diferentes fórmulas matemáticas para agrupar todos esses atributos, e o resultado do cálculo é o vetor de iluminação de saída. Em combinação com o vetor da câmera, permite determinar a aparência geral do triângulo.
Uma fonte de luz direcional ilumina muitos demos Nvidia diferentesOmitimos muitos detalhes detalhados, e por boas razões: abra qualquer tutorial de renderização em 3D e você verá que capítulos inteiros são dedicados a esse processo. No entanto, nos jogos modernos, a maior parte de todos os cálculos de efeitos de iluminação e materiais são realizados no estágio de processamento de pixels, portanto, retornaremos a eles no próximo artigo.
Código de amostra B. Anguelov mostrando como o modelo de reflexão da luz Phong pode ser processado no shader de vértice.Tudo o que examinamos acima é feito por sombreadores de vértices e parece que nada é impossível para eles; infelizmente isso não é verdade. Os sombreadores de vértices não podem criar novos vértices e cada sombreador deve processar cada vértice individual. Seria conveniente se você pudesse usar o código para criar novos triângulos entre os que já possuímos (para melhorar a qualidade visual) e um shader que processe toda uma primitiva (para acelerar o processamento). Bem, nas GPUs modernas,
podemos fazê-lo!
Por favor, senhor, eu quero mais (triângulos)
Os chips gráficos modernos são extremamente poderosos e capazes de executar milhões de cálculos de vetores matriciais a cada segundo; eles lidam facilmente com uma enorme pilha de picos por vez. Por outro lado, a criação de modelos altamente detalhados para renderização é um processo muito longo e, se o modelo estiver a alguma distância da cena, todos esses detalhes serão desperdiçados.
Ou seja, precisamos de alguma forma ordenar que o processador divida uma primitiva grande, por exemplo, um triângulo plano em um conjunto de triângulos menores localizados dentro do original. Esse processo é chamado de
mosaico, e os chips gráficos já aprenderam a executá-lo muito bem; ao longo dos anos de desenvolvimento, o grau de controle que os programadores têm sobre esse processo aumentou.
Para analisar isso em ação, usaremos a
ferramenta de benchmark Heaven do mecanismo Unigine , porque ela nos permite aplicar diferentes valores de mosaico aos modelos usados no teste.
Para começar, vamos tomar um lugar no benchmark e estudá-lo sem usar mosaico. Observe que os paralelepípedos no chão parecem muito antinaturais - a textura usada é eficaz, mas parece errada. Vamos aplicar o mosaico à cena: o mecanismo Unigine o aplica apenas a peças individuais, mas a diferença será significativa.A terra, as bordas dos edifícios e a porta parecem muito mais realistas. Podemos ver como isso foi alcançado iniciando o processo novamente, mas desta vez com a seleção de todas as primitivas (ou seja, no modo de estrutura de arame):É claramente visto por que a Terra parece tão estranha - é completamente plana! A porta se funde com as paredes e as bordas do edifício são simples paralelepípedos.No Direct3D, as primitivas podem ser divididas em um grupo de partes menores (esse processo é chamado de subdivisão) executando um processo de três etapas. Primeiro, os programadores escrevem um shader de superfície (hull shader) - na verdade, esse código cria uma estrutura chamada de patch de geometria . Você pode pensar nisso como um mapa informando ao processador onde novos pontos e linhas aparecerão dentro do primitivo inicial.Em seguida, o bloco do mosaico dentro da GPU aplica esse patch ao primitivo. No final, um sombreador de domínio é executadocalcular as posições de todos os novos vértices. Se necessário, esses dados podem ser transferidos de volta para o buffer de vértice, para que os cálculos de iluminação possam ser executados novamente, mas desta vez com melhores resultados.Como é isso? Vamos lançar a versão em wireframe da cena mosaica:Honestamente, estabelecemos um alto grau de mosaico para tornar a explicação do processo mais visível. Não importa o quão bons sejam os chips gráficos modernos, isso não deve ser feito em todas as cenas - observe, por exemplo, a lâmpada ao lado da porta.Nas imagens com a estrutura de arame desativada, dificilmente você encontrará diferenças a essa distância, e vemos que esse nível de mosaico adicionou tantos triângulos que é difícil separá-los. No entanto, quando usada corretamente, essa função de processamento de vértices pode criar efeitos visuais fantásticos, especialmente ao simular colisões de corpos moles.Vamos ver como isso pode parecer em termos de código Direct3D; para isso, usamos o exemplo de outro ótimo site, o RasterTek .Aqui está um triângulo verde simples, dividido em vários pequenos triângulos ...O processamento de vértices é realizado por três sombreadores separados (consulte o exemplo de código ): um sombreador de vértice preparando um triângulo para mosaico, um sombreamento de superfície gerando um patch e um sombreador de domínio processando novos vértices. O resultado é compreensível o suficiente, mas o exemplo da Unigine demonstra os benefícios em potencial e os perigos do uso generalizado do mosaico."Iron" não aguenta mais!
Lembre-se, dissemos que os sombreadores de vértices sempre processam todos os vértices de uma cena? É fácil entender que o mosaico pode ser um problema sério aqui. E existem muitos efeitos visuais nos quais você precisa processar versões diferentes de uma primitiva, mas sem criá-las desde o início, por exemplo, cabelos, pêlos, grama e partículas de explosões.Felizmente, especialmente para essas coisas, existe outro shader - um shader geométrico . Esta é uma versão mais limitada do shader de vértice, mas pode ser aplicada a toda a primitiva. Em combinação com o mosaico, oferece aos programadores maior controle sobre grandes grupos de vértices.O 3DMark Vantage da UL Benchmark - sombreadores geométricos processam partículas e sinalizadoresDirect3D, como todas as APIs gráficas modernas, permite realizar muitos cálculos com vértices. Os dados finalizados podem ser transferidos para o próximo estágio do processo de renderização ( rasterização ) ou retornados ao pool de memória para reprocessamento ou leitura pelo processador central para outros fins. Como a documentação da Microsoft no Direct3D diz , isso pode ser implementado como um fluxo de dados:A etapa de saída do fluxo é opcional, especialmente porque só pode transferir primitivas inteiras (em vez de vértices individuais) de volta ao ciclo de renderização, mas é útil para efeitos que exigem um grande número de partículas. O mesmo truque pode ser feito usando um buffer de vértice variável ou dinâmico , mas é melhor manter os buffers de entrada inalterados, porque quando você os abre para edição, o desempenho é reduzido.O processamento de vértices é uma parte crítica da renderização, porque determina como será a cena da perspectiva da câmera. Nos jogos modernos, milhões de triângulos podem ser usados para construir mundos, e cada um desses vértices é de alguma forma transformado e iluminado.Triângulos. Existem milhões deles.O processamento de todos esses cálculos e matemática pode parecer um pesadelo logístico, mas os processadores gráficos (GPUs) e as APIs são projetados com tudo isso em mente - imagine uma fábrica funcionando perfeitamente passando um produto de cada vez pelo pipeline de produção.Programadores experientes em renderização de jogos em 3D possuem conhecimentos fundamentais em matemática e física; eles usam todos os truques e ferramentas possíveis para otimizar as operações, comprimindo o estágio de processamento de vértices em apenas alguns milissegundos. Mas este é apenas o começo da construção de um quadro 3D - o próximo estágio é a rasterização, o processamento extremamente complexo de pixels e texturas e somente então a imagem entra no monitor.