Este artículo proviene de un motor de traducción automática.
El factor DirectX
Transformaciones en 3D sobre mapas de bits en 2D
Descargar el ejemplo de código
Programación de gráficos en tres dimensiones es sobre todo una cuestión de crear ilusiones ópticas. Se procesan imágenes en una pantalla plana que consta de una matriz bidimensional de píxeles, pero estos objetos que parecen tener una tercera dimensión con la profundidad.
Probablemente el mayor contribuyente a la ilusión de 3D es sombreado, el arte y la ciencia de colorear píxeles para que las superficies se asemejan a texturas reales con iluminación y sombras.
Debajo de todo eso, sin embargo, es una infraestructura virtuales objetos descritos por coordenadas 3D. En última instancia, estas coordenadas 3D se acoplan en un espacio 2D, pero hasta ese último paso, coordenadas 3D se han modificado a menudo sistemáticamente de varias maneras mediante el uso de transformaciones. Matemáticamente, las transformaciones son operaciones de álgebra matricial. Lograr una fluidez en estas transformaciones 3D es crucial para cualquier persona que quiere convertirse en un programador de gráficos en 3D.
Recientemente, he estado explorando las formas que 3D es compatible con el componente Direct2D de DirectX. Exploración 3D dentro de la relativa familiaridad y comodidad del Direct2D permite familiarizarse con los conceptos del 3D antes de la penetración profunda aterrador en Direct3D.
¿Mapas de bits en 3D?
Una de las varias formas que 3D es compatible con Direct2D está escondida como último argumento a los métodos de DibujarBitmap definidos por ID2D1DeviceContext. Este argumento permite aplicar una transformación 3D a un mapa de bits 2D. (Esta función es especial para ID2D1DeviceContext. No es apoyado por los métodos DibujarBitmap definidos por el ID2D1RenderTarget o las otras interfaces que se derivan de eso.)
Los métodos DibujarBitmap definidos por ID2D1DeviceContext tienen un argumento final de tipo D2D1_MATRIX_4X4_F, que es una matriz de transformación de 4 × 4 que realiza una transformación 3D en el mapa de bits como se representa en la pantalla:
void DrawBitmap(ID2D1Bitmap *bitmap,
D2D1_RECT_F *destinationRectangle,
FLOAT opacity,
D2D1_INTERPOLATION_MODE interpolationMode,
const D2D1_RECT_F *sourceRectangle,
const D2D1_MATRIX_4X4_F *perspectiveTransform)
Esto parece ser el único propósito de D2D1_MATRIX_4X4_F. No se usa en otros lugares en DirectX. En la programación de Direct3D, la biblioteca de matemáticas DirectX se utiliza en su lugar para representar las transformaciones 3D.
DD2D1_MATRIX_4X4_F es un typedef para D2D_MATRIX_4X4_F, que se define en figura 1. Es básicamente una colección de 16 valores flotantes dispuestos en cuatro filas de cuatro columnas. Puede hacer referencia al valor de la tercera fila y segunda columna utilizando los datos miembro _32, o usted puede conseguir en el mismo valor que el elemento de matriz de base cero m [1] [2].
Figura 1 el 3D transformar la matriz aplicado a los mapas de bits
typedef struct D2D_MATRIX_4X4_F
{
union
{
struct
{
FLOAT _11, _12, _13, _14;
FLOAT _21, _22, _23, _24;
FLOAT _31, _32, _33, _34;
FLOAT _41, _42, _43, _44;
} ;
FLOAT m[4][4];
};
} D2D_MATRIX_4X4_F;
Sin embargo, al mostrar las matemáticas de la transformación, yo en cambio referiré al elemento en la tercera fila y segunda columna de la matriz como m32. La matriz entera puede ser representada en la notación tradicional matriz, así:
La transformación 3D de mapa de bits en Direct2D subyace una instalación similar en el tiempo de ejecución de Windows, donde aparece como la estructura Matrix3D y la propiedad de proyección definida por UIElement. Los dos mecanismos son tan similares que puedes déjamos tu conocimiento y experiencia entre los dos entornos.
La transformación lineal
Mucha gente encontrando 3D transforma por primera vez se pregunta: ¿Por qué es una matriz de 4 × 4? ¿Una matriz de 3 × 3 no debería ser suficiente para 3D?
Para responder esta pregunta mientras explora la transformación 3D en DibujarBitmap, he creado un programa llamado BitmapTransformExperiment que está incluido en el código descargable para este artículo. Este programa contiene controles spinner caseras que permiten seleccionar los valores de los 16 elementos de la matriz de transformación y ver cómo la matriz afecta a la visualización de un mapa de bits. La figura 2 muestra una visualización habitual.
Figura 2 el programa BitmapTransformExperiment
Para sus experimentaciones iniciales, restringir su atención a las tres filas superiores y las tres columnas de la izquierda de la matriz. Estos componen una matriz de 3 × 3 que realiza la siguiente transformación:
La matriz de 3 × 1 a la izquierda representa una coordenada 3D. Para el mapa de bits, el valor de x oscila entre 0 y la anchura de mapa de bits; y se extiende desde 0 a la altura de mapa de bits; y z es 0.
Cuando la matriz 1 × 3 se multiplica por la matriz de transformación de 3 × 3, la multiplicación de la matriz estándar resulta en coordenadas transformadas:
La matriz identidad — en el cual los elementos diagonales de la m11, m22 y m33 son todos 1 y todo lo demás es 0 — resulta en no transformar.
Porque yo estoy empezando con un mapa de bits plano, la coordenada z es 0, por lo tanto m31, m32 y m33 no tienen efecto sobre el resultado. Cuando se procesa el transformado de mapa de bits a la pantalla, el (x*', y', z') resultado se contrae en un sistema de coordenadas plana en 2D haciendo caso omiso de la z'* coordinar, lo que significa que m13, m23 y m33 no tienen efecto. Por esta razón la tercera fila y tercera columna son atenuadas hacia fuera en el programa BitmapTransformExperiment. Puede establecer los valores de estos elementos de la matriz, pero no afectan cómo se representa el mapa de bits.
Descubrirá que es ese m11 es un factor de escalamiento horizontal con un valor predeterminado de 1. Hacerla más grande o más pequeño para aumentar o disminuir la anchura de mapa de bits, o que sea negativo para el mapa de bits en el eje vertical del tirón. Asimismo, m22 es un factor de escala vertical.
El valor de m21 es un factor de sesgar vertical: Valores distintos de 0 gire el rectangular de mapa de bits en un paralelogramo como el borde derecho se desplaza hacia arriba o hacia abajo. Asimismo, m12 es un factor sesgar horizontal.
Una combinación de sesgar horizontal y vertical sesgar puede resultar en rotación. Para rotar el mapa de bits hacia la derecha en un ángulo particular, ponga m11 y m22 el coseno de ese ángulo, establece m21 en el seno del ángulo y establece m12 para el seno negativo. Figura 2 muestra la rotación de 30 grados.
En el contexto del 3D, la rotación se muestra en la figura 2 es en realidad la rotación alrededor del eje Z, que conceptualmente se extiende fuera de la pantalla. Para un ángulo de α, la matriz de transformación se ve así:
También es posible girar el mapa de bits alrededor del eje o eje de las X. Rotación alrededor del eje Y no afecta la coordenada, entonces la matriz de transformación es esta:
Si esta prueba con el programa BitmapTransformExperiment, usted descubrirá que sólo el valor m11 tiene un efecto. Colocarla con el coseno del ángulo de rotación de un sólo disminuye la anchura del mapa de bits prestado. Esa disminución de ancho es consistente con la rotación alrededor del eje Y.
Asimismo, esta rotación alrededor del eje X:
En el programa BitmapTransformExperiment, esto resulta en la reducción de la altura del mapa de bits.
Los signos de los factores de dos seno en las matrices de transformación gobiernan la dirección de rotación. Conceptualmente, el eje Z positivo se supone que se extienden desde la pantalla, y las rotaciones siguen la regla del lado izquierda: Alinee el pulgar de su mano izquierda con el eje de rotación y apúntelo hacia valores positivos; la curva de los otros dedos indica la dirección de la rotación para los ángulos positivos.
El tipo de transformación 3D representada por esta matriz de transformación de 3 × 3 se conoce como una transformación lineal. La transformación implica sólo constantes multiplicadas por la x, y y z coordenadas. No importa qué números entras en los primeros tres filas y columnas de la matriz de transformación, el mapa de bits no se transforma en algo más exótico que un paralelogramo; en tres dimensiones, un cubo siempre se transforma en un paralelepípedo.
Has visto cómo el mapa de bits se puede escalar, sesgada y rotada, pero a lo largo de este ejercicio, la esquina superior izquierda del mapa de bits ha mantenido fija en una sola ubicación. (Esa ubicación es gobernada por una transformación 2D en el método Render antes de la llamada DibujarBitmap). La incapacidad para mover la esquina superior izquierda de los resultados de mapa de bits de las matemáticas de la transformación lineal. No hay nada en la fórmula de transformación que puede cambiar un (0, 0, 0) apunte a otro lugar, que es un tipo de transformación conocido como traducción.
Lograr la traducción
Para entender cómo obtener traducción en 3D, brevemente pensemos en 2D se transforma.
En dos dimensiones, una transformación lineal es una matriz de 2 × 2, y es capaz de escalar, sesgar y rotar. Para obtener la traducción así como, los gráficos 2D se supone que existen en el espacio 3D, pero en un plano 2D donde la coordenada z siempre equivale a 1. Para dar cabida a la dimensión adicional, la matriz de transformación lineal 2D se expande en una matriz de 3 × 3, pero generalmente la última fila es fijo:
Los resultados de la multiplicación de matriz en estos transforman fórmulas:
Los factores m31 y m32 son los factores de traducción. El secreto detrás de este proceso es que la traducción en dos dimensiones es equivalente a sesgar en tres dimensiones.
Un proceso análogo se utiliza para los gráficos 3D: Las coordenadas 3D en realidad se supone que existen en el espacio 4D donde la coordenada de la cuarta dimensión es 1. Pero para representar un punto de coordenadas 4D, tienes un tonto problema poco práctico: Es un punto 3D (x, y, z) y ninguna carta viene después z, así que ¿qué letra usas para la cuarta dimensión? La letra disponible más cercana es w, así es la coordenada 4D (x, y, z, w).
La matriz de transformación lineal 3D es extendida a 4 × 4 para dar cabida a la dimensión extra:
Ahora son las fórmulas de transformación:
Ahora tienes la traslación a lo largo de los ejes X, Y y Z con m41, m42 y m43. El cálculo parece ocurrir en el espacio de 4D, pero en realidad se limita a una corte transversal en 3D del espacio 4D donde la coordenada w siempre es 1.
Coordenadas homogéneas
Si usted juega con los valores m41 y m42 en BitmapTransformExperiment, usted verá que efectivamente generen traducción horizontal y vertical.
¿Pero lo que la última fila de la matriz? ¿Qué pasa si no restringe la última fila de ceros y unos. Aquí está la transformación completa 4 × 4 aplicada a un punto 3D:
Las fórmulas para x*', y'* y z*'* siguen siendo los mismos, pero w*'* ahora se calcula así:
Y eso es un problema real. Previamente, se empleó un pequeño truco usar una corte transversal en 3D del espacio 4D donde la coordenada w siempre equivale a 1. Pero ahora ya no es la coordenada W 1, y ha sido propulsado fuera de esa corte transversal en 3D. Estás perdido en el espacio de 4D, y tienes que volver a esa corte transversal en 3D donde w es igual a 1.
No hay ninguna necesidad de construir una máquina del espacio-tiempo transdimensional, sin embargo. Afortunadamente, puedes hacer el salto matemáticamente dividiendo todas las coordenadas transformadas por w*'*:
Ahora w*'* es igual a 1 y estás de vuelta casa!
¿Pero a qué precio? Ahora tienes una división en las fórmulas de transformación, y es fácil ver cómo sería ese denominador 0 en algunas circunstancias. Eso se traduciría en coordenadas infinitas.
Bueno, tal vez es una buena cosa.
Cuando el matemático alemán Agosto Ferdinand Möbius (1790-1868) inventó el sistema que acabo de describir (llamado "coordenadas homogéneas" o "coordenadas proyectivas"), uno de sus objetivos era representar coordenadas infinitas usando números finitos.
La matriz con los ceros y unos en la última fila se llama una transformación afín, lo que significa que no resulta en el infinito, entonces, una transformación capaz de infinito se llama una transformación no afín.
En gráficos 3D, transformaciones no afines son extremadamente importantes, para esto es cómo se logra la perspectiva. Todo el mundo sabe que en la vida real, los objetos más lejos de los ojos parecen ser más pequeñas. En gráficos 3D, obtener ese efecto con un denominador que no sea una constante 1.
Pruébelo en BitmapTransformExperiment: Si haces m14 un número positivo pequeño — y sólo pequeños valores son necesarios para obtener resultados interesantes — entonces los valores de x y y se disminuyen proporcionalmente como x consigue más grande. M14 un pequeño número negativo y los mayores valores de x y y se aumentan. Figura 3 muestra que el efecto combinado con un valor distinto de cero m12. El mapa de bits prestado ya no es un paralelogramo, y la perspectiva sugiere que el borde derecho ha oscilado más cercano de los ojos.
Figura 3 la perspectiva en BitmapTransformExperiment
Del mismo modo, valores no nulos de m24 pueden hacer la parte superior o inferior del mapa de bits aparentemente oscilación hacia usted o más lejos. En programación 3D real, es el valor m34 que es comúnmente utilizado, que permite a los objetos aumentar o disminuir de tamaño basado en sus coordenadas z — su distancia de los ojos del espectador.
Cuando se aplica una transformación 3D a 2D objetos, el valor de m44 generalmente quedo en 1, pero puede funcionar como un factor de escala global. En 3D real de programación, m44 generalmente se establece en 0 cuando perspectiva está involucrado porque conceptualmente la cámara está en el origen. Un valor 0 de m44 sólo funciona si los objetos 3D no tienen coordenadas z de 0, pero cuando se trabaja con objetos 2D, la coordenada z es siempre 0.
Cualquier cuadrilátero convexo
En la aplicación de esta matriz de transformación de 4 × 4 en un mapa de bits plano, eres único haciendo uso de la mitad de las elementos de matriz. Aún así, figura 3 muestra algo no puedes hacer con el normal bidimensional Direct2D transformar la matriz, que consiste en aplicar una transformación que convierte un rectángulo en algo que no sea un paralelogramo. De hecho, los objetos de transformación utilizados en la mayoría del Direct2D se llaman D2D1_MATRIX_3X2_F y Matrix3x2F, haciendo hincapié en la inaccesibilidad de la tercera fila y la imposibilidad de llevar a cabo transformaciones no afín.
Con D2D1_MATRIX_4X4_F, es posible obtener una transformación que traza un mapa de bits en cualquier cuadrilátero convexo — es decir, cualquier arbitraria figura de cuatro lados donde los lados no Cruz y donde los ángulos en los vértices interiores son menos de 180 grados.
Si no me crees, intenta jugar con el programa NonAffineStretch. Tenga en cuenta que este programa es una adaptación de un programa de ejecución de Windows, también llamado NonAffineStretch, en el capítulo 10 de mi libro, "Programación Windows, 6ª edición" (Microsoft Press, 2013).
Figura 4 NonAffineStretch se muestra en uso. Puede utilizar el ratón o con los dedos para arrastrar los puntos verdes a cualquier ubicación en la pantalla. Mientras se mantiene la figura un cuadrilátero convexo, una transformación de 4 × 4 puede ser derivada basado en las ubicaciones de punto. Esa transformación se utiliza para dibujar el mapa de bits y también aparece en la esquina inferior derecha. Sólo ocho valores están involucrados; los elementos en la tercera fila y tercera columna son siempre los valores por defecto y m44 siempre es 1.
Figura 4 el programa de NonAffineStretch
Las matemáticas detrás de esto están un poco complicadas, pero la derivación del algoritmo se muestra en el capítulo 10 de mi libro.
La clase Matrix4x4F
Para facilitar el trabajo con la estructura de D2D1_MATRIX_4X4_F un poco, la clase Matrix4x4F en el espacio de nombres D2D1 deriva de esa estructura. Esta clase define un constructor y un operador de multiplicación (que utilicé en el algoritmo de NonAffineStretch), y varios útiles métodos estáticos para crear común transforman matrices. Por ejemplo, el método Matrix4x4F::RotationZ acepta un argumento que es un ángulo en grados y devuelve una matriz que representa la rotación por ese ángulo alrededor del eje Z:
Otras funciones Matrix4x4F crean matrices de rotación en torno a los ejes X e Y y la rotación alrededor de un eje arbitrario, que es una matriz mucho más difícil.
Una función llamada Matrix4x4F::PerspectiveProjection tiene un argumento llamado profundidad. Devuelve la matriz es:
Esto significa que la fórmula de transformación para x' es:
Y del mismo modo y*'* y z*'*, que significa cuando la coordenada z es igual a la profundidad, el denominador es 0, y todas las coordenadas se convierten en infinitas.
Conceptualmente, esto significa que estás viendo en la pantalla del ordenador a una distancia de las unidades de profundidad, donde las unidades son las mismas que la pantalla misma, significado píxeles o unidades independientes del dispositivo. Si un objeto gráfico tiene una coordenada z igual a profundidad, son unidades de profundidad frente a la pantalla, que está en tu ojo! Ese objeto debe aparecer muy grande para ti — matemáticamente infinito.
Pero un momento: El único propósito de la D2D1_MATRIX_4X4_Fstructure y la clase Matrix4x4F es para las llamadas a DibujarBitmap, y mapas de bits siempre tienen las coordenadas z de 0. ¿Cómo hace este valor m34 de – 1/profundidad tiene ningún efecto en absoluto?
Si se utiliza la matriz PerspectiveProjection por sí mismo en la llamada DibujarBitmap, de hecho no tendrá efecto. Pero se pretende utilizar en conjunción con otras transformaciones de matriz. Transformaciones de matriz se pueden componer multiplicando los. Aunque el mapa de bits original no tiene coordenadas z, y se omiten las coordenadas z para la representación, las coordenadas z ciertamente pueden desempeñar un papel en la composición de transformaciones.
Veamos un ejemplo. El programa de RotatingText crea un mapa de bits con el texto "ROTATE" con un ancho que es casi la mitad de la anchura de la pantalla. Gran parte de los métodos de actualización y procesamiento se muestran en figura 5.
Figura 5 del código de RotatingTextRenderer.cpp
void RotatingTextRenderer::Update(DX::StepTimer const& timer)
{
...
// Begin with the identity transform
m_matrix = Matrix4x4F();
// Rotate around the Y axis
double seconds = timer.GetTotalSeconds();
float angle = 360 * float(fmod(seconds, 7) / 7);
m_matrix = m_matrix * Matrix4x4F::RotationY(angle);
// Apply perspective based on the bitmap width
D2D1_SIZE_F bitmapSize = m_bitmap->GetSize();
m_matrix = m_matrix *
Matrix4x4F::PerspectiveProjection(bitmapSize.width);
}
void RotatingTextRenderer::Render()
{
...
ID2D1DeviceContext* context =
m_deviceResources->GetD2DDeviceContext();
Windows::Foundation::Size logicalSize =
m_deviceResources->GetLogicalSize();
context->SaveDrawingState(m_stateBlock.Get());
context->BeginDraw();
context->Clear(ColorF(ColorF::DarkMagenta));
// Move origin to top center of screen
Matrix3x2F centerTranslation =
Matrix3x2F::Translation(logicalSize.Width / 2, 0);
context->SetTransform(centerTranslation *
m_deviceResources->GetOrientationTransform2D());
// Draw the bitmap
context->DrawBitmap(m_bitmap.Get(),
nullptr,
1.0f,
D2D1_INTERPOLATION_MODE_LINEAR,
nullptr,
&m_matrix);
...
}
En el método de actualización, el método Matrix4x4F::RotationY crea la siguiente transformación:
Multiplica esto por la matriz mostrada anteriormente devuelto por el método de Matrix4x4F::PerspectiveProjection, y usted obtendrá:
Las fórmulas de transformación son:
Definitivamente estos implican perspectiva, y podrá ver el resultado en figura 6.
Figura 6 la pantalla RotatingText
Cuidado: El argumento de profundidad de Matrix4x4F::Perspectiveproyección se establece en el ancho de mapa de bits, así como el mapa de bits rotación oscila alrededor, podría venir muy cerca de la nariz.
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 a los siguientes expertos técnicos de Microsoft por su ayuda en la revisión de este artículo: Jim Galasyn y Mike riquezas