Fator DirectX

A tela e a câmera

Charles Petzold

Baixar o código de exemplo

Charles PetzoldHá 15 anos, o artista britânico David Hockney começou a desenvolver uma teoria sobre a arte renascentista que acabou por ser bastante controverso. Hockney maravilhado com como os antigos mestres como van Eyck, Velázquez e Leonardo foram capazes de processar o mundo visual na lona com surpreendente precisão em perspectiva e sombreamento. Ele tornou-se convencido de que esta precisão foi possível somente com a ajuda de ferramentas ópticas de lentes e espelhos, como a câmera obscura e camera lucida. Estes dispositivos achatam a cena 3D e aproximá-la ao artista para a reprodução. Hockney publicou sua tese em um livro lindo e persuasivo, intitulado, "conhecimento secreto: Redescobrindo as técnicas perdidas dos grandes mestres"(Viking, 2001).

Mais recentemente, inventor Tim Jenison (fundador da NewTek, empresa que desenvolveu a torradeira e LightWave 3D), tornou-se obcecado com a utilização de instrumentos ópticos para recriar a pintura de 350 anos de Jan Vermeer "A lição de música". Ele construiu uma sala semelhante ao original, decorada com reproduções de mobiliário e adereços, recrutou modelos vivos (incluindo a filha dele) e uma ferramenta simples de ótica de sua própria invenção é usado para pintar a cena. A mandíbula -­cair o resultados são narrados no fascinante documentário, "Vermeer deTim."

Por que é necessário utilizar dispositivos ópticos — ou, nestes dias, uma câmera simples — para capturar cenas em 3D em superfícies 2D com precisão? Muito do que achamos que vemos no mundo real é construído no cérebro de relativamente escassa informação visual. Achamos que vemos uma cena de todo mundo real em um grande panorama, mas em qualquer momento, estamos realmente concentrados somente em um pequeno detalhe do mesmo. É praticamente impossível reunir esses fragmentos visuais separados em pintura composta que se assemelha ao mundo real. É muito mais fácil quando a lona é complementada com um mecanismo muito parecido com uma câmera — que por si só imita a óptica do olho humano, mas sem as deficiências do olho.

Um processo semelhante ocorre em computação gráfica: Quando o processamento de gráficos 2D, uma lona metafórica funciona muito bem. A tela é uma superfície de desenho, e na sua forma mais óbvia, corresponde diretamente para as linhas e colunas de pixels que compõem a exibição de vídeo.

Mas quando se deslocam de 2D para 3D, a lona metafórica precisa de ser complementada com uma câmera metafórica. Como uma câmera do mundo real, esta câmera metafórica capta objetos no espaço 3D e nivela-os sobre uma superfície que pode ser então transferida para a tela.

Características da câmera

Uma câmera do mundo real pode ser descrita com diversas características que são facilmente convertidas em descrições matemáticas exigidas pelos gráficos 3D. A câmera tem uma determinada posição no espaço, que pode ser representado como um ponto de 3D.

A câmera também é apontada em uma determinada direção: um vetor 3D. Você pode calcular esse vetor obtendo a posição de um objeto que a câmera está apontada diretamente para, e subtraindo-se a posição da câmera.

Se você manter uma câmera fixada em uma determinada posição e apontado em um sentido particular, você ainda tem a liberdade de inclinação da câmera para um lado e na verdade ele gira 360 graus. Isto significa que um outro vetor é necessário para indicar "up" em relação a câmera. Este vetor é perpendicular à direção de câmera.

Depois de estabelecer como a câmera é posicionada no espaço, você pode mexer com os botões da câmera.

Câmeras de hoje são frequentemente capazes de zoom: Você pode ajustar a lente da câmera de ângulo amplo que abrange uma grande cena para uma exibição de telefoto que reduz para um close up. A diferença baseia-se a distância entre o plano que capta a imagem e o ponto focal, que é um único ponto através do qual a luz passa, conforme mostrado no Figura 1. A distância entre o plano focal e o ponto focal é chamada o comprimento focal.

The Focal Plane, Focal Point and Focal Length
Figura 1: O plano Focal, o ponto Focal e o comprimento Focal

Os tamanhos do plano focal e a distância focal implicam um campo de visão angular emana o ponto focal. Vistas telefoto (mais corretamente chamadas de "foco de longo") são geralmente associadas um campo de visão inferior a 35 graus, enquanto uma grande angular é superior a 65 graus, com campos mais normal de vista no meio.

Computação gráfica adiciona uma característica da câmera não é possível na vida real: Em 3D gráficos de programação você frequentemente tem uma escolha entre as câmeras que alcançar projeção ortogonal ou perspectiva. A perspectiva é como na vida real: Objetos mais longe a câmera parecem ser menores, porque o campo de visão abrange uma gama maior mais longe o ponto focal.

Com projeção ortogonal, isso não é o caso. Tudo é processado em tamanho em relação ao tamanho real do objeto, independentemente de sua distância da câmera. Matematicamente, esta é a mais simples das duas projeções e é mais adequada para desenhos técnicos e arquitetônicos.

As transformações de câmera

Na programação de gráficos 3D, câmeras são construções matemáticas. A câmera é composto por duas transformações de matriz, bem como aqueles que manipulam objetos no espaço 3D. As duas câmera transformações são chamadas de exibição e projeção.

A matriz de vista efetivamente posiciona e orienta a câmera no espaço 3D; a matriz de projeção descreve o que a câmera "vê" e como ele vê isso. Estes câmera transformações são aplicadas depois de tudo matriz outra transformações são usadas para posição de objetos no espaço 3D, muitas vezes chamado de "espaço do mundo." Após todas as outras transformações, primeiro transformar a exibição é aplicada, e finalmente transformar a projeção.

Na programação do DirectX — se Direct3D ou a exploração de conceitos 3D em Direct2D — é mais fácil construir essas transformações de matriz usando a biblioteca de matemática do DirectX, a coleção de funções no namespace DirectX que começam com as letras XM e usar os tipos de dados XMVECTOR e XMMATRIX. Estes dois tipos de dados são proxies para registradores da CPU, para que essas funções são muitas vezes bastante rápidas.

Quatro funções estão disponíveis para calcular a matriz de exibição:

  • XMMatrixLookAtRH (EyePosition, FocusPosition, UpDirection)
  • XMMatrixLookAtLH (EyePosition, FocusPosition, UpDirection)
  • XMMatrixLookToRH (EyePosition, EyeDirection, UpDirection)
  • XMMatrixLookToLH (EyePosition, EyeDirection, UpDirection)

Os argumentos da função incluem a palavra "Olho", mas a documentação usa a palavra "câmara".

As abreviaturas de LH e RH defendem da esquerda e da direita. Vou assumir um sistema de coordenadas da esquerda para esses exemplos. Se você apontar o dedo indicador da mão esquerda na direção do positivo X eixo e o dedo do meio na direção de Y positivo, o polegar apontará para Z positivo. Se o positivo eixo X dá certo e Y positivo sobe (uma orientação comum em programação 3D), em seguida, o eixo-Z vem fora da tela.

Todas as quatro funções exigem três objetos do tipo XMVECTOR e retornam um objeto do tipo XMMATRIX. Em todas as quatro funções, dois destes argumentos indicam a posição da câmera (rotulado como EyePosition no modelo de função) e o UpDirection. As funções de olhar para incluem um argumento FocusPosition — uma posição da câmera é apontada para — enquanto as funções pretendia tem um EyeDirection, que é um vetor. É só um cálculo simples para converter um formulário para outro.

Por exemplo, suponha que você deseja posicionar a câmera no ponto (0, 0, – 100), apontando em direção a origem (e, portanto, na direção do eixo Z positivo), com a parte superior da câmera apontando para cima. Você pode chamar qualquer um

XMMATRIX view =
  XMMatrixLookAtLH(XMVectorSet(0, 0, -100, 0),
                   XMVectorSet(0, 0, 0, 0),
                   XMVectorSet(0, 1, 0, 0));

ou

XMMATRIX view =
  XMMatrixLookToLH(XMVectorSet(0, 0, -100, 0),
                   XMVectorSet(0, 0, 1, 0),
                   XMVectorSet(0, 1, 0, 0));

Em ambos os casos, a função cria essa matriz de exibição:

Esta matriz de exibição particular simplesmente desloca as unidades de toda a cena 3D 100 na direção do eixo Z positivo. Muitas matrizes de exibição também envolverá rotações de vários tipos, mas nenhuma escala. Depois de transformar a exibição é aplicada à cena 3D, a câmera pode ser assumida para ser posicionado na origem (com a parte superior da câmara apontada na direção de Y positivo) e apontou na direção Z positiva (para um sistema da esquerda) ou direção – Z (à direita). Essa orientação permite a transformação de projeção a ser muito mais simples do que seria caso contrário.

As convenções de projeção

Anterior incursões desta coluna programação 3D dentro Direct2D, eu me converti objetos do espaço 3D para a visualização de vídeo 2D simplesmente ignorando a coordenada Z.

É hora de converter de 3D para 2D de uma forma mais profissional, usando as convenções que são encapsuladas em matrizes de projeção da câmera padrão. Na verdade, a conversão padrão de 3D para 2D ocorre em duas fases: primeiro 3D coordenadas normalizadas coordenadas 3D e em seguida coordenadas 2D. A transformação de projeção especificada pelo programa controla a primeira conversão. Na programação Direct3D, a segunda conversão normalmente é realizada automaticamente pelo sistema de processamento. Um programa que usa Direct2D para exibir elementos gráficos 3D deve executar esta segunda conversão em si.

A finalidade de transformar a projeção é em parte normalizar todas as coordenadas 3D na cena. Esta normalização define quais objetos são visíveis na renderização final e que são excluídos. Na sequência desta normalização, cena renderizada final engloba X coordenadas Y coordenadas que variam de – 1 à esquerda e 1 à direita, que variam de – 1 na parte inferior a 1, na parte superior e as coordenadas de Z variando de 0 (mais próxima da câmera) a 1 (mais afastada da câmera). As coordenadas de Z também são usadas para determinar quais objetos obscurecem outros objetos relativo para o espectador.

Não em tudo neste espaço é Descartado e em seguida o normalizado as coordenadas X e Y são mapeadas para a largura e a altura da superfície de exposição, enquanto que as coordenadas de Z são ignoradas.

Para normalizar as coordenadas de Z, as funções que calculam uma matriz de projeção sempre exigem argumentos do tipo flutuam chamada NearZ e Fernanda que indicam uma distância da câmera ao longo do eixo Z. Estas duas distâncias são convertidas em coordenadas de Z normalizadas de 0 e 1, respectivamente.

Isto é um pouco contra-intuitivo porque isso implica que há uma área do espaço 3D que está muito perto da câmera para ser visível e uma outra área que é muito longe. Mas por razões práticas, é necessário limitar a profundidade desta forma. Tudo por trás da câmera deve ser eliminado, por exemplo e objetos muito perto da que câmera poderia obscurecer tudo o resto. Se Z coordenadas fora para infinito foram autorizados, a resolução de números de ponto flutuante iria ser tributada ao determinar quais objetos se sobrepõem a outros.

Porque a matriz de exibição câmera contas para possível tradução e rotação da câmera, a matriz de projeção é sempre baseado em uma câmera localizada na origem e apontando ao longo do eixo Z. Eu vou estar usando coordenadas esquerdas para estes exemplos, o que significa que a câmera está apontada na direção do eixo Z positivo. Coordenadas da esquerda são um pouco mais simples quando lidar com projeção transforma porque NearZ e Fernanda são iguais às coordenadas ao longo do eixo Z positivo ao invés do eixo-Z.

A biblioteca de matemática do DirectX define 10 funções para calcular a matriz de projeção — quatro para projeções ortográficas e seis para projeções de perspectiva, metade para as coordenadas da esquerda e metade para à direita.

Nas funções de XMMatrixOrthographicRH e LH, especifique ViewWidth e ViewHeight junto com NearZ e Fernanda. Figura 2 é uma visão olhando para baixo no sistema de coordenadas 3D de uma posição sobre o eixo Y positivo. Esta figura mostra como estes argumentos definem um cubóide em um sistema de coordenadas da esquerda visível para um globo ocular na origem.

A Top View of an Orthographic Transform
Figura 2 vista superior de um ortográficas transformar

Muitas vezes, a relação de ViewWidth para ViewHeight é o mesmo que a relação de aspecto da tela usada para processamento. A transformação de projeção dimensiona tudo de – ViewWidth / 2 para ViewWidth / 2 para o intervalo de – 1 a 1 e mais tarde essas coordenadas normalizadas são dimensionadas por metade da largura de pixel da superfície de exposição para renderização. O cálculo é semelhante para ViewHeight.

Aqui está que uma chamada para XMMatrixOrthographicLH com ViewWidth e ViewHeight definida como 40 e 20 e NearZ e Fernanda definido para 50 e 100, que coincide com o diagrama, assumindo que as marcas de escala cada 10 unidades:

XMMATRIX orthographic =
  XMMatrixOrthographicLH(40, 20, 50, 100);

Isso resulta na seguinte matriz:

As fórmulas de transformação são:

Vê-se que x valores de – 20 e 20 são transformados para – 1 e 1, respectivamente e que valores de y de – 10 e 10 são transformados para – 1 e 1, respectivamente. Um valor de Z de 50 é transformado para 0, e um valor de Z de 100 é transformado em 1.

Duas funções ortogonais adicionais contêm as palavras A530 e permitem que você especifique left, right, top e bottom coordenadas ao invés de larguras e alturas.

As funções XMMatrixPerspectiveRH e LH tem os mesmos argumentos que XMMatrixOrthograhicRH e LH, mas definir um tronco de quatro lados (como uma pirâmide com sua corte superior), conforme mostrado no Figura 3.

A Top View of a Perspective Transform
Figura 3 vista superior de uma transformação de perspectiva

Os argumentos ViewWidth e ViewHeight nas funções de transformação controlam a largura e a altura do tronco em NearZ, mas a largura e altura em Fernanda é proporcionalmente maior com base na relação de Fernanda para NearZ. Este diagrama demonstra também como uma gama maior de x e y coordenadas mais distante da câmera são mapeadas para o mesmo espaço (e, portanto, são feitos menores) como x e y coordenadas mais perto da câmera.

Aqui está uma chamada para XMMatrixPerspectiveLH com os mesmos argumentos que eu usei para XMMatrixOrthographicLH:

XMMATRIX perspective =
  XMMatrixPerspectiveLH(40, 20, 50, 100);

A matriz criada esta chamada é:

Note que a quarta coluna indica uma transformação afim! Em uma transformação afim, o m14, m24 e m34 valores são 0, e m44 é 1. Aqui, m34 é 1 e m44 é 0.

Isto é como perspectiva é alcançada em ambientes de programação 3D, então vamos olhar para a multiplicação de transformar em detalhe:

Os resultados da multiplicação de matriz nas seguintes fórmulas de transformação:

Observe o valor de w´. Conforme discutido na edição de abril nesta coluna, o uso de coordenadas w em transformações 3D ostensivamente acomoda a tradução, mas também traz na matemática de coordenadas homogêneas (ou projetivas). Transformações afim sempre ocorrem no subconjunto 3D do espaço de 4D, onde w é igual a 1, mas esta transformação não afim moveu coordenadas fora desse espaço 3D. As coordenadas devem ser voltou para aquele espaço 3D, dividindo-os por w´. As fórmulas de transformação que incorporam este ajustamento necessário são em vez disso:

Quando z é igual a NearZ ou 50, as fórmulas de transformação são as mesmas para a projeção ortogonal:

Valores de x de -20 a 20 são transformados em valores de x´ de ­-1 a 1, por exemplo.

Para outros valores de z, as fórmulas de transformação são diferentes, e quando z é igual a Fernanda ou 100, eles olham como este:

A esta distância da câmera, valores de x da ­-40 a 40 são transformados em valores de x´ de -1 a 1. Uma gama maior de x e y valores no FarF ocupam o mesmo campo visual, como um intervalo menor no NearZ.

Como com as funções ortogonais, duas funções adicionais têm as palavras A530, os nomes de função e deixá-lo conjunto esquerda, direita, superior, e inferior coordena ao invés de larguras e alturas.

As funções XMMatrixPerspectiveFovRH e LH permitem especificar um campo angular de visão (FOV) ao invés de uma largura e altura. Este campo de visão é provável diferentes ao longo dos eixos X e Y. Você precisa especificá-lo ao longo do eixo Y e também fornecer uma relação entre largura e altura.

Para criar uma matriz de perspectiva consistente com o exemplo anterior, o campo de visão pode ser calculado com a função atan2, com um argumento de y igual a metade da altura na NearZ e o argumento de x igual a NearZ e então duplicando o resultado:

float angleY = 2 * atan2(10.0f, 50.0f);

O segundo argumento é a relação entre largura e altura, ou 2 neste exemplo:

XMMATRIX perspective =
  XMMatrixPerspectiveFovLH(angleY, 2, 50, 100);

Esta chamada resulta em uma matriz de perspectiva idêntica à que acabou de criar com XMMatrixPerspectiveLH.

Um círculo de texto

Na edição de fevereiro desta coluna, demonstrei como criar um círculo 3D de texto e animar a sua rotação. No entanto, usei Direct2D geometrias para esse exercício e encontrou o desempenho muito pobre. Na coluna de março eu demonstrei como texto de suavização de serrilhado em uma coleção de triângulos, que parecia ter desempenho consideravelmente melhor do que de geometrias. Na coluna de maio, eu demonstrei como usar triângulos para criar e processar objetos 3D.

É hora de juntar estas diferentes técnicas. O código-fonte para download para esta coluna inclui um projeto de Windows 8 Direct2D chamado TessellatedText3D. Para este programa defini um triângulo 3D usando objetos de XMFLOAT3:

struct Triangle3D
{
  DirectX::XMFLOAT3 point1;
  DirectX::XMFLOAT3 point2;
  DirectX::XMFLOAT3 point3;
};

O construtor da classe TessellatedText3DRenderer carrega em um arquivo de fonte, cria uma face de fonte e gera um ID2D1Path­objeto de geometria do GetGlyphRunOutline método usando um tamanho de fonte arbitrário de 100. Esta geometria é então convertida em uma coleção de triângulos usando o método Tessellate com um dissipador de mosaico personalizado. Com a determinada fonte, fonte tamanho glifo índices e especifiquei, Tessellate gera 1.741 triângulos.

Os triângulos 2D são então convertidos em 3D triângulos envolvendo o texto em um círculo. Baseia-se o tamanho da fonte arbitrário de 100, este círculo acontece de ter um raio de aproximadamente 200 (armazenado em m_source­Radius), e o círculo é centralizado na origem 3D.

Em Update o método da classe TessellatedText3DRenderer, as funções XMMatrixRotationY e XMMatrixRotationX oferecem duas transformações para animar uma rotação do texto em torno dos eixos X e Y. Eles são armazenados em objetos XMMATRIX chamados rotateMatrix e tiltMatrix, respectivamente.

O método de atualização do que continua com o código mostrado na Figura 4. Este código calcula as matrizes de exibição e projeção. A matriz de vista define a posição da câmera no eixo Z – baseia o raio do círculo, então a câmera é um comprimento de raio fora do texto circular, mas apontou em direção ao centro.

Figura 4 o modo de exibição e matrizes de projeção em TessellatedText3D

void TessellatedText3DRenderer::Update(DX::StepTimer const& timer)
{
  ...
// Calculate camera view matrix
  XMVECTOR eyePosition = XMVectorSet(0, 0, -2 * m_sourceRadius, 0);
  XMVECTOR focusPosition = XMVectorSet(0, 0, 0, 0);
  XMVECTOR upDirection = XMVectorSet(0, 1, 0, 0);
  XMMATRIX viewMatrix = XMMatrixLookAtLH(eyePosition,
                                         focusPosition,
                                         upDirection);
  // Calculate camera projection matrix
  float width = 1.5f * m_sourceRadius;
  float nearZ = 1 * m_sourceRadius;
  float farZ = nearZ + 2 * m_sourceRadius;
  XMMATRIX projMatrix = XMMatrixPerspectiveLH(width,
                                              width,
                                              nearZ,
                                              farZ);
  // Calculate composite matrix
  XMMATRIX matrix = rotateMatrix * tiltMatrix *
                    viewMatrix * projMatrix;
  // Apply composite transform to all 3D triangles
  XMVector3TransformCoordStream(
    (XMFLOAT3 *) m_dstTriangles3D.data(),
    sizeof(XMFLOAT3),
    (XMFLOAT3 *) m_srcTriangles3D.data(),
    sizeof(XMFLOAT3),
    3 * m_srcTriangles3D.size(),
    matrix);
  ...
}

Com argumentos que também se baseia o raio do círculo, o código continua calculando-se uma matriz de projeção e em seguida, multiplica as matrizes de todos juntos. O paralelo de usos XMVector3TransformCoordStream processamento para aplicar essa transformação para uma matriz de objetos XMFLOAT3 (na verdade uma matriz de objetos Triangle3D), automaticamente, realizando a divisão por w*'*.

O método de atualização continua além do que é mostrado no Figura 4 , convertendo os transformaram coordenadas 3D para 2D, usando um fator de escala com base em metade da largura da tela do vídeo, e ignorar o z coordenadas. O método de atualização também usa os 3D vértices de cada triângulo para calcular um produto cruzado, que é a normal da superfície — um vetor perpendicular à superfície. Os triângulos 2D então são divididos em dois grupos com base na coordenada z da superfície normal. Se a coordenada z for negativa, o triângulo é virada para o espectador, e se for positivo, o triângulo é virado para fora. O método Update conclui através da criação de dois objetos de ID2D1Mesh com base em dois grupos de triângulos 2D.

O método Render, em seguida, exibe a malha de triângulos traseiras com um pincel cinzento e a malha da frente com um pincel azul. O resultado é mostrado na Figura 5.

The TessellatedText3D Display
Figura 5 a exibição de TessellatedText3D

Como você pode ver, os triângulos frontal mais perto ao telespectador são muito maiores do que os triângulos voltados para trás um pouco mais longe. Este programa não tem nenhum dos problemas de desempenho encontrados com geometrias de renderização.

Sombreamento com luz?

Na edição de maio desta coluna, eu levei leve em consideração ao exibir objetos 3D. O programa assume a luz vem de uma determinada direção, e ele calcula o sombreamento diferente para os lados das figuras sólidos baseados no ângulo que a luz bate em cada superfície.

É possível algo semelhante com este programa?

Na teoria, sim. Mas minhas experiências revelaram alguns problemas sérios de desempenho. Ao invés de criar dois objetos de ID2D1Mesh no método de atualização, precisa de um programa de implementação de sombreamento do texto para processar cada triângulo com uma cor diferente, e isso requer 1.741 diferentes ID2D1Mesh objetos recriados em cada chamada de atualização e 1.741 correspondente ID2D1SolidColorBrush. Isto diminuiu a animação até aproximadamente um frame por segundo.

O que é pior, o Visual não foram satisfatório. Cada triângulo Obtém uma cor sólida discreta diferente com base no seu ângulo para a fonte de luz, mas os limites entre essas cores discretas tornou-se visível! Triângulos usados para processar objetos 3D mais corretamente devem ser coloridos com um gradiente entre os três vértices, mas tal um gradiente não é suportado pelas interfaces que derivam de ID2D1Brush.

Isso significa que devo cavar ainda mais fundo Direct2D e obter acesso às mesmas ferramentas de sombreamento que usam de programadores do Direct3D.

Charles Petzold é um colaborador de longa data de MSDN Magazine e autor de "Programação Windows, 6ª edição" (Microsoft Press, 2013), um livro sobre como escrever aplicativos para Windows 8. Seu site é charlespetzold.com.

Agradecemos ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Doug Erickson
Doug Erickson é um escritor de programação de chumbo para equipe de documentação de desenvolvedor do Microsoft OSG. Quando não escrito e desenvolvimento de conteúdo e código de gráficos de DirectX, ele está lendo artigos como Charles', porque isso é como ele gosta de passar seu tempo livre. Bem, isso e andar de moto.