Compartir a través de


Factor DirectX

Lienzo y cámara

Charles Petzold

Descargar el código de ejemplo

Charles PetzoldHace 15 años, artista inglés David Hockney comenzó a desarrollar una teoría sobre el arte renacentista que resultó para ser bastante controvertido. Hockney maravillaba cómo viejos maestros como van Eyck, Velázquez y Leonardo fueron capaces de representar el mundo visual en la lona con una precisión asombrosa en perspectiva y el sombreado. Él se convenció de que esta precisión era posible solamente con la ayuda de instrumentos ópticos de lentes y espejos, como la cámara oscura y la cámara lúcida. Estos dispositivos aplanan la escena 3D y acercarla a la artista para la reproducción. Hockney publicó su tesis en un hermoso y persuasivo libro titulado, "el conocimiento secreto: Redescubriendo las perdidas técnicas de los viejos maestros"(Viking, 2001).

Más recientemente, inventor Tim Jenison (fundador de NewTek, la compañía que desarrolló Video Toaster y LightWave 3D), se obsesionó con el uso de herramientas ópticas para recrear la pintura de 350 años de Vermeer "La lección de Música . Él construyó un salón similar al original, había decorado con reproducciones de muebles y utilería, reclutó modelos vivos (incluyendo a su hija) y utiliza una herramienta óptica sencilla de su propia invención para pintar la escena. La mandíbula -­caer resultados son crónica en el fascinante documental, "Vermeer deTim.

¿Por qué es necesario utilizar dispositivos ópticos — o, en estos días, una simple cámara — para capturar con precisión las escenas 3D en superficies 2D? Gran parte de lo que pensamos que vemos en el mundo real se construye en el cerebro de la relativamente escasa información visual. Creemos que vemos una escena entera reales en un gran panorama, pero en cualquier momento, realmente nos centramos sólo en un pequeño detalle de él. Es prácticamente imposible de juntar estos fragmentos visuales separados en un cuadro compuesto que se asemeja al mundo real. Es mucho más fácil cuando el lienzo se complementa con un mecanismo parecido a una cámara — que imita la óptica del ojo humano, pero sin las deficiencias del ojo.

Un proceso similar se produce en gráficos de computadora: Al representar gráficos 2D, un lienzo metafórico funciona muy bien. El lienzo es una superficie de dibujo, y en su forma más obvia, corresponde directamente a las filas y columnas de píxeles que componen la pantalla de video.

Pero al pasar de 2D a 3D, el lienzo metafórico debe complementarse con una cámara metafórica. Como una cámara del mundo real, esta cámara metafórica captura objetos en espacio 3D y les aplana sobre una superficie que luego puede ser transferida a la lona.

Características de la cámara

Una cámara del mundo real puede ser descrita con varias características que se convierten fácilmente en las descripciones matemáticas requeridas por gráficos en 3D. Una cámara tiene una determinada posición en el espacio, que puede ser representado como un punto 3D.

La cámara también está dirigida en una dirección determinada: un vector 3D. Se puede calcular este vector por obtener la posición de un objeto que apunte directamente a la cámara y restando la posición de la cámara.

Si tienes una cámara fija en una posición determinada y apuntando en una dirección determinada, todavía tienen la libertad para inclinar la cámara a un lado y hecho girar 360 grados. Esto significa que se requiere para indicar "arriba" en relación con la cámara otro vector. Este vector es perpendicular a la dirección de la cámara.

Después de establecer cómo se coloca la cámara en el espacio, se llega a tocar el violín con las perillas de la cámara.

Las cámaras de hoy son a menudo capaces de zoom: Usted puede ajustar la lente de la cámara desde un ángulo amplio que abarca una gran escena a una vista teleobjetivo que se estrecha para un cierre para arriba. La diferencia se basa en la distancia entre el plano que captura la imagen y el punto focal, que es un solo punto a través del cual pasa la luz, como se muestra en la figura 1. La distancia entre el plano focal y el punto focal se llama la distancia focal.

The Focal Plane, Focal Point and Focal Length
Figura 1 el plano Focal, Punto Focal y la longitud Focal

Los tamaños del plano focal y la distancia focal implican un campo de visión angular emana el punto focal. Telefoto vistas (más correctamente llamados "foco largo") son generalmente asociados con un campo de visión inferior a 35 grados, mientras que un gran angular es mayor de 65 grados, con la vista de los campos más normal en el medio.

Computación gráfica agrega una característica de la cámara no es posible en la vida real: En 3D gráficos programación a menudo tienen una opción entre las cámaras que lograr la perspectiva o proyección ortográfica. Perspectiva es como la vida real: Los objetos más lejos de la cámara parecen ser más pequeños porque el campo de visión abarca una gama mayor más alejado del punto focal.

Con proyección ortográfica, no es el caso. Todo se procesa en un tamaño relativo a tamaño real del objeto, sin importar su distancia desde la cámara. Matemáticamente, esto es el más simple de las dos proyecciones y es más apropiado para dibujos técnicos y arquitectónicos.

La cámara se transforma

En la programación de gráficos en 3D, las cámaras son constructos matemáticos. La cámara consisten en dos transformaciones de matriz como aquellos que manipulan objetos en el espacio 3D. Las dos cámara transformaciones se denominan vista y proyección.

La matriz de vista efectivamente posiciones y orienta la cámara en un espacio tridimensional; la matriz de proyección describe lo que la cámara "ve" y cómo lo ve. Estas cámaras las transformaciones se aplican después de todo la otra matriz transforma está acostumbrado a menudo posición de objetos en espacio 3D, llamado "espacio del mundo". Después de todas las transformaciones, primero se aplica la transformación de la vista, y finalmente transformar la proyección.

En la programación de DirectX — si Direct3D o la exploración de conceptos 3D en Direct2D — es más fácil de construir estas transformaciones de matriz usando la biblioteca de matemáticas de DirectX, la colección de funciones en el espacio de nombres DirectX que comienzan con las letras XM y utilizar los tipos de datos XMVECTOR y XMMATRIX. Estos tipos de dos datos son proxies para registros de la CPU, así que estas funciones son a menudo muy rápidas.

Cuatro funciones están disponibles para calcular la matriz de opinión:

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

Los argumentos de las funciones incluyen la palabra "Ojo", pero la documentación utiliza la palabra "cámara".

Las abreviaturas de LH y RH soporte para izquierda y derecha. A suponiendo que un sistema de coordenadas izquierdo para estos ejemplos. Si señalas con el dedo índice de su mano izquierda en la dirección del eje de X y el dedo medio en la dirección positiva Y el positivo, el pulgar apuntará a Z positivo. Si el positivo eje X va bien positiva Y sube (una orientación común en programación 3D) y el eje Z – sale de la pantalla.

Todas las cuatro funciones requieren tres objetos de tipo XMVECTOR y devuelven un objeto del tipo XMMATRIX. En todas las funciones de cuatro, dos de estos argumentos indican la posición de la cámara (marcada como EyePosition en la plantilla de función) y el UpDirection. Las funciones de Mira incluyen un argumento FocusPosition — una posición está apuntando la cámara — mientras las Funciones procuré tienen un EyeDirection, que es un vector. Es sólo un cálculo simple para convertir de una forma a otra.

Por ejemplo, supongamos que desea colocar la cámara en el punto (0, 0, -100), apuntando hacia el origen (y, por lo tanto, en la dirección del eje Z positivo), con la parte superior de la cámara apuntando hacia arriba. Puede llamar a cualquiera

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

o

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

En cualquier caso, la función crea esta matriz de vista:

Esta matriz de opinión particular simplemente cambia las unidades de la escena 3D 100 en la dirección del eje Z positivo. Muchas de las matrices de opinión también implicará rotaciones de diversos tipos, pero sin ajuste de escala. Después de la transformación de vista se aplica a la escena 3D, la cámara puede ser asumida para ser colocado en el origen (con la parte superior de la cámara apuntada en la dirección positiva y) y apuntando en la dirección positiva de Z (para un sistema izquierdo) o en la dirección – Z (derecha). Esta orientación permite la transformación de proyección a ser mucho más sencillo de lo que sería lo contrario.

Los convenios de proyección

En incursiones anteriores de esta columna en programación 3D dentro de Direct2D, he convertido objetos del espacio 3D a la pantalla de video 2D simplemente haciendo caso omiso de la coordenada Z.

Es hora de convertir de 3D a 2D de una manera más profesional, mediante convenios que están encapsulados en las matrices de proyección de cámara estándar. La conversión estándar de 3D a 2D en realidad se produce en dos etapas: primero de 3D coordina a coordenadas 3D normalizadas y luego a coordenadas 2D. La transformación de proyección especificada por el programa controla la primera conversión. En la programación de Direct3D, la segunda conversión generalmente se realiza automáticamente por el sistema de representación. Un programa que utiliza Direct2D para mostrar gráficos en 3D debe llevar a cabo esta segunda conversión de sí mismo.

El propósito de la transformación de la proyección es en parte normalizar todas las coordenadas 3D en la escena. Esta normalización define qué objetos son visibles en el renderizado final y que están excluidos. A raíz de esta normalización, la escena final renderizada abarca a X coordenadas que van desde – 1 a la izquierda y 1 a la derecha, Y coordina desde – 1 en la parte inferior a 1 en la parte superior y las coordenadas Z entre 0 (la más cercana a la cámara) y 1 (más alejado de la cámara). Las coordenadas Z se utilizan también para determinar qué objetos oscurecen otros objetos en relación con el espectador.

Todo no en este espacio se descarta y entonces el normalizado las coordenadas X e Y se asignan a la anchura y la altura de la superficie de la pantalla, mientras se ignoran las coordenadas Z.

Para normalizar las coordenadas Z, las funciones que calcular una matriz de proyección siempre requieren argumentos de tipo flotan llamado NearZ y FarZ que indican una distancia desde la cámara a lo largo del eje Z. Estas dos distancias se convierten en coordenadas normalizadas Z de 0 y 1, respectivamente.

Esto es algo contraintuitivo porque implica una zona del espacio 3D que está muy cerca la cámara para ser visible y otra área que está demasiado lejos. Pero por razones prácticas, es necesario limitar la profundidad de esta manera. Todo detrás de la cámara debe ser eliminado, por ejemplo y los objetos demasiado a que la cámara oscurecería todo lo demás. Si Z coordina fuera a infinito fueron permitidos, la resolución de números de punto flotante tributarían al determinar qué objetos superponen otros.

Porque la matriz de vista cámara representa posible traslación y rotación de la cámara, la matriz de proyección está siempre basada en una cámara situada en el origen y señalando a lo largo del eje Z. Va a utilizar las coordenadas izquierdas para estos ejemplos, lo que significa que la cámara está apuntando en la dirección del eje Z positivo. Coordenadas izquierdas son un poco más simples al tratar con proyección transforma porque son iguales a las coordenadas a lo largo del eje Z positivo más que el eje Z – NearZ y FarZ.

La biblioteca de matemáticas DirectX define 10 funciones para el cálculo de la matriz de proyección: cuatro para las proyecciones de orthographic y seis proyecciones de perspectiva, la mitad para las coordenadas izquierdas y la mitad para derecha.

En las funciones XMMatrixOrthographicRH y LH, especifica ViewWidth y ViewHeight junto con NearZ y FarZ. Figura 2 es un punto de vista mirando hacia abajo sobre el sistema de coordenadas 3D desde una ubicación en el eje Y positivo. Esta figura muestra cómo estos argumentos definen un ortoedro en un sistema de coordenadas izquierdo visible para un ojo en el origen.

A Top View of an Orthographic Transform
Figura 2 vista superior de una ortográfica transformar

A menudo, la proporción de ViewWidth a ViewHeight es igual a la proporción de aspecto de la pantalla utilizada para la representación. La transformación de proyección escalas desde – ViewWidth / 2 a ViewWidth / 2 a la gama de – 1 a 1 y más tarde esas coordenadas normalizadas se escalan por la mitad del ancho de píxel de la superficie de la pantalla para la representación. El cálculo es similar a ViewHeight.

Esto es que una llamada a XMMatrixOrthographicLH con ViewWidth y ViewHeight han decidido FarZ establece en 50 y 100, que coincide con el diagrama asumiendo marcas cada 10 unidades 40 y 20 y NearZ:

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

Esto da como resultado la siguiente matriz:

Las fórmulas de transformación son:

Puedes ver que x se transforman los valores de – 20 y 20 -1 y 1, respectivamente y eso y valores de – 10 y 10 son transformados a – 1 y 1, respectivamente. Un valor de Z de 50 se transforma a 0, y se transforma un valor Z de 100 a 1.

Dos funciones adicionales ortográficas contienen las palabras de forma descentrada y permiten especificar izquierda, derecha, arriba y abajo las coordenadas en lugar de anchuras y alturas.

Las funciones XMMatrixPerspectiveRH y LH tienen los mismos argumentos como XMMatrixOrthograhicRH y LH, pero definir un tronco de cuatro lados (como una pirámide con su corte superior) como se muestra en la figura 3.

A Top View of a Perspective Transform
Figura 3 vista superior de una perspectiva de transformación

Los argumentos ViewWidth y ViewHeight en las funciones de transformación controlan la anchura y la altura del tronco en el NearZ, pero la anchura y la altura en FarZ es proporcionalmente más grande basado en la relación de FarZ a NearZ. Este diagrama también muestra cómo un mayor rango de x y coordenadas y más lejos de la cámara se asignan en el mismo espacio (y por lo tanto, se hacen más pequeños) como x e y las coordenadas más cerca de la cámara.

Este es un llamado a XMMatrixPerspectiveLH con los mismos argumentos que he usado para XMMatrixOrthographicLH:

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

La matriz creada a partir de esta convocatoria es:

Observe que la cuarta columna indica una transformación no afín. En una transformación afín, el m14, m24 y m34 valores son 0 y m44 es 1. Aquí, m34 es 1 y m44 es 0.

Esto es cómo se logra la perspectiva en entornos de programación 3D, echemos un vistazo a la multiplicación de transformar en detalle:

La multiplicación de la matriz da como resultado las siguientes fórmulas de transformación:

Tenga en cuenta el valor de w´. Como comenté en la entrega de abril de esta columna, el uso de coordenadas w en 3D transforma ostensiblemente acomoda traducción, pero también trae en las matemáticas de coordenadas homogéneas (o proyectivas). Las transformaciones afines siempre llevará a cabo en el subconjunto de 4D espacio donde w es igual a 1, pero esta transformación afín-no ha salido de coordenadas ese espacio 3D 3D. Las coordenadas deberán ser desplazadas atrás en ese espacio 3D mediante dividiéndolas por w´. Las fórmulas de transformación que incorporan este ajuste necesario son:

Cuando z es igual a NearZ o 50, las fórmulas de transformación son los mismos en cuanto a la proyección ortográfica:

Los valores de x de -20 a 20 se transforman en los valores de x´ de ­-1 a 1, por ejemplo.

Para otros valores de z, las fórmulas de transformación son diferentes, y cuando z equivale a FarZ o 100, se ven así:

A esta distancia desde la cámara, los valores de x de ­-40 a 40 se transforman en x´ valores de -1 a 1. Una gama más grande de x y y valores en FarF ocupan el mismo campo visual como un rango menor en NearZ.

Como con las funciones ortogonales, dos funciones adicionales tienen las palabras de forma descentrada en los nombres de función y dejarte conjunto izquierda, derecha, arriba, y abajo coordina en lugar de anchuras y alturas.

Las funciones XMMatrixPerspectiveFovRH y LH permiten especificar un campo angular de visión (FOV) en lugar de una anchura y altura. Este campo de visión es probable diferentes a lo largo de los ejes X e Y. Tienes que especificarlo en el eje Y y también proporcionar una relación de anchura a altura.

Para crear una matriz de perspectiva consistente con el ejemplo anterior, el campo de visión se puede calcular con la función atan2, con un argumento y igual a la mitad de la altura en NearZ y el argumento x igual a NearZ y luego doblando el resultado:

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

El segundo argumento es la relación de anchura a altura, o 2 en este ejemplo:

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

Esta llamada se traduce en una matriz de perspectiva idéntica a la que acabamos de crear con XMMatrixPerspectiveLH.

Un círculo de texto

En entrega del mes de febrero de esta columna, he demostrado cómo crear un círculo de texto 3D y animar su rotación. Sin embargo, utilicé Direct2D geometrías para ese ejercicio y encontró muy mal desempeño. En la columna de marzo he demostrado cómo teselar texto en una colección de triángulos, que parecían tener mucho mejor rendimiento que geometrías. En la columna de mayo, he demostrado cómo utilizar triángulos para crear y representar objetos 3D.

Es hora de juntar estas técnicas diferentes. El código descargable para esta columna incluye un proyecto de Windows 8 Direct2D llamado TessellatedText3D. Para este programa definí un triángulo 3D utilizando objetos XMFLOAT3:

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

El constructor de la clase TessellatedText3DRenderer se carga un archivo de fuente, crea una cara fuente y genera un ID2D1Path­objeto de geometría desde el método GetGlyphRunOutline con un tamaño de fuente arbitraria de 100. Esta geometría se convierte entonces en una colección de triángulos utilizando el método Tessellate con un fregadero mosaico personalizado. Con la fuente particular, fuente tamaño glifo índices y especifiqué, Tessellate genera 1.741 triángulos.

Los triángulos 2D se convierten luego en triángulos 3D envolviendo el texto en un círculo. Basado en el tamaño de fuente arbitrario de 100, este círculo pasa a tener un radio de unos 200 (almacenado en m_source­radio), y el círculo está centrado en el origen 3D.

En el método de actualización de la clase TessellatedText3DRenderer, las funciones XMMatrixRotationY y XMMatrixRotationX proporcionan dos transformaciones para animar una rotación del texto alrededor de los ejes X e Y. Éstos se almacenan en XMMATRIX objetos llamados rotateMatrix y tiltMatrix, respectivamente.

El método Update que continúa con el código que se muestra en la figura 4. Este código calcula las matrices de opinión y proyección. La matriz de opinión establece la posición de la cámara sobre el eje de Z – basado en el radio del círculo, así que la cámara es una longitud de radio fuera el texto circular pero apuntando hacia el centro.

Figura 4 la vista y Matrices de proyección en 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);
  ...
}

Con argumentos que también se basa en el radio del círculo, el código continúa mediante el cálculo de una matriz de proyección y luego multiplica todas las matrices juntos. El paralelo de usos XMVector3TransformCoordStream de procesamiento para aplicar esta transformación a una matriz de objetos XMFLOAT3 (en realidad una matriz de objetos Triangle3D), realizando automáticamente la división por w*'*.

El método Update continúa más allá de lo que se muestra en la figura 4 mediante la conversión de los transformaron coordenadas 3D a 2D usando un factor de escala basado en la mitad del ancho de la pantalla de video, y haciendo caso omiso de la z coordina. El método Update utiliza también los vértices de cada triángulo 3D para calcular un producto Cruz, que es la superficie normal — un vector perpendicular a la superficie. Los triángulos 2D entonces se dividen en dos grupos basados en la coordenada z de la superficie normal. Si la coordenada z es negativa, el triángulo es dirigido hacia el espectador, y si es positivo, el triángulo se enfrenta a distancia. El método Update concluye con la creación de dos objetos ID2D1Mesh basados en los dos grupos de triángulos 2D.

El método Render a continuación muestra la malla de triángulos traseros con un pincel gris y la malla de frente con un cepillo azul. El resultado se muestra en la figura 5.

The TessellatedText3D Display
Figura 5 la pantalla TessellatedText3D

Como puedes ver, los triángulos de frente más cercano al espectador son mucho mayores que los triángulos hacia atrás más lejos. Este programa no tiene ninguno de los problemas de rendimiento con geometrías de renderizado.

¿Sombreado con luz?

En la entrega de mayo de esta columna, tuve luz en cuenta cuando se muestran objetos 3D. El programa asume la luz proviene de una determinada dirección y calcula diferentes matices para los lados de las figuras sólidos basados en el ángulo que la luz incide en cada superficie.

¿Es posible algo similar con este programa?

En teoría, sí, Pero mis experimentos revelaron algunos problemas de funcionamiento graves. En lugar de crear dos objetos ID2D1Mesh en el método Update, necesita un programa de implementación de sombreado del texto representar cada triángulo con un color diferente, y eso requiere 1.741 diferentes ID2D1Mesh objetos recreados en cada llamada de actualización y 1.741 correspondiente ID2D1SolidColorBrush. Esto desaceleró la animación hasta aproximadamente un fotograma por segundo.

Lo que es peor, las imágenes no eran satisfactorias. Cada triángulo tiene un color sólido discreto diferente basado en su ángulo de la fuente de luz, pero los límites entre estos colores discretos llegó a ser visibles. Triángulos que se utiliza para representar objetos 3D más bien deben ser coloreados con un gradiente entre los tres vértices, pero tal un gradiente no es compatible con las interfaces que se derivan de ID2D1Brush.

Esto significa que debo cavar más profundo en Direct2D y tener acceso a las mismas herramientas de sombreado que utilizan los programadores Direct3D.

Charles Petzold es desde hace mucho tiempo colaborador de MSDN Magazine y el autor de "Programación Windows, 6ª edición" (Microsoft Press, 2013), un libro acerca de cómo escribir aplicaciones para Windows 8. Su sitio Web es charlespetzold.com.

Gracias al siguiente experto técnico de Microsoft por su ayuda en la revisión de este artículo: Doug Erickson
Doug Erickson es un escritor de programación de plomo para el equipo de documentación de desarrollador de Microsoft OSG. Cuando no escribir y desarrollar código gráficos DirectX y contenido, está leyendo artículos como Charles', porque así es como le gusta pasar su tiempo libre. Bueno, eso y montando motocicletas.