El diseñador de maquetación no necesita matemáticas 2: matrices, transformaciones básicas, construcción 3D y filtros para imágenes





La última vez hablamos de los gráficos y trayectorias para la animación stop motion, y hoy será sobre la matriz. Descubriremos cómo construir transformaciones básicas en CSS, SVG y WebGL, construiremos una visualización del mundo 3D en la pantalla con nuestras propias manos, mientras dibujaremos un paralelo con una herramienta como Three.js, y también experimentaremos con filtros para fotos y averiguaremos qué porque esa magia reside en su núcleo.



Permítanme recordarles que en esta serie de artículos nos familiarizamos con varias cosas del campo de las matemáticas que asustan a los diseñadores de maquetación, pero que pueden ser útiles para resolver problemas de trabajo. Tratamos de evitar teorizaciones innecesarias, prefiriendo imágenes y explicaciones en los dedos, con énfasis en aplicaciones prácticas en el frontend. En este sentido, las formulaciones en algunos lugares pueden no ser del todo precisas desde el punto de vista de las matemáticas, o no del todo completas. El propósito de este artículo es dar una idea general de lo que sucede y por dónde empezar si sucede algo.



Los scripts para generar imágenes al estilo de esta serie de artículos están en GitHub , por lo que si desea calcular lo mismo por sí mismo, ya sabe qué hacer.



Pocas definiciones



Una matriz en matemáticas es una abstracción, podemos decir que es un tipo de datos en cierto sentido, y escribirlo en forma de tabla rectangular. El número de columnas y filas puede ser cualquier cosa, pero en la web casi siempre tratamos con matrices cuadradas 2x2, 3x3, 4x4 y 5x5.



También necesitamos una definición como un vector. Creo que de la geometría de la escuela se puede recordar la definición asociada con las palabras "longitud" y "dirección", pero en general, en matemáticas, muchas cosas se pueden llamar vector. En particular, hablaremos de un vector como un conjunto ordenado de valores. Por ejemplo, coordenadas de la forma (x, y) o (x, y, z), o un color en el formato (r, g, b) o (h, s, l, a), etc. Dependiendo de cuántos elementos se incluyan en dicho conjunto, hablaremos de un vector de una dimensión u otra: si dos elementos son bidimensionales, tres son tridimensionales, etc. Además, en el marco de los temas considerados, en ocasiones puede ser conveniente pensar en un vector como una matriz de tamaños 1x2, 1x3, 1x4, etc. Técnicamente, podríamos limitarnos solo al término "matriz", pero aún usaremos la palabra "vector" para separar estos dos conceptos entre sí,al menos en un sentido lógico.



Tanto para matrices como para vectores, se definen diversas operaciones que se pueden realizar con ellas. En particular, multiplicación. Los estamos multiplicando constantemente entre nosotros. El algoritmo de multiplicación en sí no es muy complicado, aunque puede parecer un poco confuso:



function multiplyMatrices(a, b) {
    const m = new Array(a.length);

    for (let row = 0; row < a.length; row++) {
        m[row] = new Array(b[0].length);

        for (let column = 0; column < b[0].length; column++) {
            m[row][column] = 0;

            for (let i = 0; i < a[0].length; i++) {
                m[row][column] += a[row][i] * b[i][column];
            }
        }
    }

    return m;
}


Pero para nosotros, de hecho, no es tan importante recordar constantemente el principio de su funcionamiento al resolver problemas cotidianos. Aquí lo mencionamos más bien para completar, para proporcionar contexto para más ejemplos.



Cuando se trabaja con entidades complejas en matemáticas, es muy útil abstraer. Como aquí, a menudo hablaremos de multiplicación, pero no prestaremos atención a qué tipo de operaciones aritméticas y en qué orden ocurren allí. Sabemos que la multiplicación está definida, y eso es suficiente para el trabajo.


Solo usaremos matrices cuadradas en un conjunto de problemas muy específico, por lo que un conjunto de reglas simples será suficiente:



  • Solo puede multiplicar matrices de la misma dimensión.
  • Multiplicamos la matriz por la matriz, obtenemos la matriz.
  • Puedes multiplicar una matriz por un vector; obtenemos un vector.
  • El orden de multiplicación es importante.






Utilizaremos principalmente la multiplicación de izquierda a derecha, ya que es más familiar y adecuada para las explicaciones, pero en algunos libros o bibliotecas puede encontrar una notación de derecha a izquierda, y todas las matrices se reflejarán en diagonal. Esto no afecta de ninguna manera la esencia de las manipulaciones que tienen lugar, por lo que no nos detendremos en esto, pero si copia y pega algo, preste atención.


Además, para seguir trabajando, necesitaremos un concepto como la matriz de identidad. Esta es una matriz con unos en la diagonal principal y ceros en todas las demás celdas. El algoritmo de multiplicación está construido de tal manera que al multiplicar la matriz identidad por otra matriz, obtenemos la misma matriz. O un vector, si hablamos de un vector. En otras palabras, la matriz identidad juega el papel de uno en la multiplicación habitual de números. Esta es una cosa neutral que "no afecta nada" cuando se multiplica.







Y lo último que necesitamos es el ejemplo que se muestra en la imagen de arriba. Esto es como un caso especial de multiplicar una matriz por un vector, cuando la última fila de la matriz es una "parte de la matriz identidad", y el último elemento del vector también es igual a 1.



En este ejemplo, usamos las letras (x, y) y, como habrás adivinado, la siguiente sección trata sobre las coordenadas en 2D. Pero, ¿por qué agregar una tercera coordenada y dejarla como una? - usted pregunta. Se trata de conveniencia, o mejor aún, de versatilidad. Muy a menudo agregamos +1 coordenada para simplificar los cálculos, y trabajamos con 2D con matrices de 3x3, trabajamos con 3D, con matrices de 4x4, y trabajamos con 4D, por ejemplo, con colores en el formato (r, g, b, a) con matrices 5x5. A primera vista esto parece una idea loca, pero luego veremos cómo unifica todas las operaciones. Si desea comprender este tema con más detalle, puede buscar en Google la expresión "coordenadas uniformes".



Pero basta de teoría, pasemos a la práctica.



I. Transformaciones básicas en gráficos por computadora



Tomemos las expresiones del ejemplo anterior y veámoslas como son, fuera del contexto de las matrices:



newX = a*x + b*y + c
newY = d*x + e*y + f


Puede pensar en esto como las ecuaciones paramétricas que trazamos la última vez. ¿Qué sucede si establece estos o aquellos coeficientes en ellos? Comencemos con la siguiente opción:



newX = 1*x + 0*y + 0 = x
newY = 0*x + 1*y + 0 = y


Aquí no cambia nada: las nuevas coordenadas (x, y) son idénticas a las antiguas. Si sustituimos estos coeficientes en la matriz y miramos de cerca, veremos que obtenemos la matriz identidad.



¿Qué pasa si tomamos otros coeficientes? Por ejemplo, estos son:



newX = 1*x + 0*y + A = x + A
newY = 0*x + 1*y + 0 = y


Obtendremos el desplazamiento a lo largo del eje X. Pero, ¿qué más podría haber pasado aquí? Si esto no le resulta obvio, entonces es mejor volver a la primera parte, donde hablamos de gráficos y coeficientes.



Cambiando estos 6 coeficientes - a, b, c, d, e, f - y observando los cambios en xey, tarde o temprano llegaremos a cuatro de sus combinaciones, que parecen útiles y convenientes para un uso práctico. Escribámoslas de inmediato en forma de matrices, volviendo al ejemplo original:







los nombres de estas matrices hablan por sí mismos. Al multiplicar estas matrices por vectores con las coordenadas de algunos puntos, objetos de la escena, etc. obtenemos nuevas coordenadas para ellos. Además, operamos con transformaciones intuitivas: movimiento, escala, rotación e inclinación, y los coeficientes determinan la severidad de una transformación particular a lo largo de los ejes correspondientes.



A menudo es conveniente pensar en las matrices como transformaciones de algo, como las coordenadas. Esta es otra palabra sobre abstracciones.


Las transformaciones se pueden apilar. En términos de matrices, usaremos la operación de multiplicación, que puede ser un poco confusa, pero esta es una sobrecarga de lenguaje común. Si necesitamos mover algún objeto hacia un lado y aumentar, entonces podemos tomar una matriz para el desplazamiento, una matriz para escalar y multiplicarlas. El resultado será una matriz que proporciona tanto el desplazamiento como la escala al mismo tiempo. Solo queda transformar cada punto de nuestro objeto con su ayuda.







Transformaciones básicas en CSS



Pero estas son todas palabras. Veamos cómo se ve en una interfaz real. En CSS, (de repente) tenemos una función matricial. Se parece a esto en el contexto del código:



.example {
    transform: matrix(1, 0, 0, 1, 0, 0);
}


Muchos novatos que lo ven por primera vez están cubiertos por la pregunta: ¿por qué hay seis parámetros? Esto es extraño. Habrían sido 4 o 16, todavía no iba a dónde, ¿pero 6? ¿Qué están haciendo?



Pero en realidad todo es sencillo. Estos seis parámetros son los mismos coeficientes a partir de los cuales acabamos de ensamblar las matrices para las transformaciones básicas. Pero por alguna razón se organizaron en un orden diferente:







también en CSS hay una función matrix3d ​​para establecer una transformación 3D usando una matriz. Ya hay 16 parámetros, exactamente para hacer una matriz 4x4 (no olvides que agregamos +1 dimensión).



Las matrices para transformaciones 3D básicas se construyen de la misma manera que para 2D, solo se necesitan más coeficientes para organizarlas no en dos coordenadas, sino en tres. Pero los principios son los mismos.


Naturalmente, cada vez sería extraño cercar la matriz y monitorear la colocación correcta de los coeficientes cuando se trabaja con transformaciones simples en CSS. Los programadores solemos intentar hacernos la vida más fácil. Así que ahora tenemos funciones cortas en CSS para crear transformaciones individuales: translateX, translateY, scaleX, etc. Normalmente los usamos, pero es importante entender que en su interior crean las mismas matrices de las que hablamos, simplemente ocultándonos este proceso detrás de otra capa de abstracción.



Las mismas transformaciones de traslación, rotación, escala y sesgo, así como la función de matriz universal para definir transformaciones, están presentes en SVG. La sintaxis es ligeramente diferente, pero la esencia es la misma. Al trabajar con gráficos 3D, por ejemplo con WebGL, también recurriremos a las mismas transformaciones. Pero más sobre eso más adelante, ahora es importante entender que están en todas partes y funcionan en todas partes de acuerdo con el mismo principio.



Subtotales



Resumamos lo anterior:



  • Las matrices se pueden utilizar como transformaciones para vectores, en particular para las coordenadas de algunos objetos en la página.
  • Casi siempre operamos con matrices cuadradas y agregamos +1 dimensión para simplificar y unificar los cálculos.
  • Hay 4 transformaciones básicas: trasladar, rotar, escalar y sesgar. Se utilizan en todas partes, desde CSS hasta WebGL, y funcionan de manera similar en todas partes.


II. Construcción de escena 3D DIY



Un desarrollo lógico del tema de la transformación de coordenadas será la construcción de una escena 3D y su visualización en pantalla. De una forma u otra, esta tarea generalmente se encuentra en todos los cursos de gráficos por computadora, pero en los cursos front-end generalmente no lo es. Veremos, tal vez un poco simplificado, pero no obstante una versión completa de cómo se puede hacer una cámara con diferentes ángulos de visión, qué operaciones son necesarias para calcular las coordenadas de todos los objetos en la pantalla y construir una imagen, y también dibujar paralelos con Three.js - el más popular herramienta para trabajar con WebGL.



Aquí debería surgir una pregunta razonable: ¿por qué? ¿Por qué aprender a hacer todo con las manos si tienes una herramienta preparada? La respuesta está en problemas de rendimiento. Probablemente hayas visitado sitios con concursos como Awwwards, CSS Design Awards, FWA y similares. ¿Recuerda el rendimiento de los sitios que participan en estos concursos? Sí, casi todo el mundo se ralentiza, se retrasa al cargar y hace que la computadora portátil zumbe como un avión. Sí, por supuesto, la razón principal suele ser sombreadores complejos o demasiada manipulación DOM, pero la segunda es la increíble cantidad de scripts. Esto tiene un efecto desastroso en la carga de dichos sitios. Por lo general, todo sucede así: necesitas hacer algo en WebGL; se necesita algún tipo de motor 3D (+ 500KB) y algunos complementos (+ 500KB);necesitas hacer que un objeto se caiga o que algo salga volando; necesitan un motor de física (+ 1 MB, o incluso más); necesita actualizar algunos datos en la página; bueno, agregue un marco de SPA con una docena de complementos (+ 500KB), etc. Y de esta manera, se escriben varios megabytes de scripts, que no solo necesitan ser descargados por el cliente (y esto es además de las imágenes grandes), sino que también el navegador hará algo con ellos después de la descarga, no solo vuelan hacia él. Además, en el 99% de los casos, hasta que los guiones funcionen, el usuario no verá toda la belleza que necesitaría mostrar desde el principio.lo que el cliente necesita descargar (y esto es además de las imágenes grandes), por lo que el navegador hará algo con ellos después de la carga; no solo acuden a él por una razón. Además, en el 99% de los casos, hasta que los guiones funcionen, el usuario no verá toda la belleza que necesitaría mostrar desde el principio.lo que el cliente necesita descargar (y esto es además de las imágenes grandes), por lo que el navegador hará algo con ellos después de la carga; no solo acuden a él por una razón. Además, en el 99% de los casos, hasta que los guiones funcionen, el usuario no verá toda la belleza que necesitaría mostrar desde el principio.



Es una creencia popular que cada 666 KB de scripts en producción aumenta el tiempo de carga de la página el tiempo suficiente para que un usuario envíe a un desarrollador de sitios al siguiente círculo del infierno. Three.js en la configuración mínima pesa 628KB ...


Además, a menudo las tareas simplemente no requieren la conexión de herramientas complejas. Por ejemplo, para mostrar un par de planos con texturas en WebGL y agregar un par de sombreadores para que las imágenes diverjan en oleadas, no necesita todos los Three.js. Y para hacer caer un objeto, no necesita un motor de física completo. Sí, probablemente acelerará su trabajo, especialmente si está familiarizado con él, pero lo pagará con el tiempo de los usuarios. Aquí cada uno decide por sí mismo qué es más rentable para él.



Coordinar la cadena de transformación



De hecho, la esencia de las transformaciones de coordenadas para construir una escena 3D en nuestra pantalla es bastante simple, pero aún la analizaremos por pasos, ya que lo más probable es que este proceso sea algo nuevo para muchos diseñadores de maquetación.



Eso es todo. Digamos que un diseñador ha dibujado un modelo 3D. Sea un cubo (en los ejemplos usaremos las construcciones más simples para no complicar la ilustración de la nada):







¿Cómo es este modelo? De hecho, es un conjunto de puntos en algún sistema de coordenadas y un conjunto de relaciones entre ellos para que pueda determinar entre qué puntos deben ubicarse los planos. La cuestión de los planos en el contexto de WebGL dependerá del propio navegador, y las coordenadas son importantes para nosotros. Necesita descubrir exactamente cómo transformarlos.



El modelo, como dijimos, tiene un sistema de coordenadas. Pero normalmente queremos tener muchos modelos, queremos hacer una escena con ellos. La escena, nuestro mundo 3D, tendrá su propio sistema de coordenadas global. Si solo interpretamos las coordenadas del modelo como coordenadas globales, entonces nuestro modelo se ubicará como si estuviera "en el centro del mundo". En otras palabras, nada cambiará. Pero queremos agregar muchos modelos a diferentes lugares de nuestro mundo, algo como esto:







¿Qué hacer? Necesita convertir las coordenadas de cada modelo individual en coordenadas globales. La posición del modelo en el espacio se establece mediante compensaciones, rotaciones y escalas; ya hemos visto estas transformaciones básicas. Ahora necesitamos construir una matriz de transformación para cada modelo, que almacenará exactamente esta información sobre dónde está el modelo en relación con el mundo y cómo se rota.



Por ejemplo, para los cubos, habrá aproximadamente las siguientes matrices:



//     .
//   «  »      .
const modelMatrix1 = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]
];

// ,     X.
const modelMatrix2 = [
    [1, 0, 0, 1.5],
    [0, 1, 0, 0  ],
    [0, 0, 1, 0  ],
    [0, 0, 0, 1  ]
];

// ,     X   .
const modelMatrix3 = [
    [1, 0, 0, -1.5],
    [0, 1, 0, 0   ],
    [0, 0, 1, 0   ],
    [0, 0, 0, 1   ]
];


Además, actuaremos aproximadamente de la siguiente manera:



    {
       = [    ] *  
}


En consecuencia, cada modelo necesita su propia matriz.



Del mismo modo, puede hacer una cadena de algunos objetos. Si un pájaro tiene que batir sus alas, entonces será apropiado traducir las coordenadas de los puntos de las alas en las coordenadas del pájaro, y luego en las coordenadas mundiales globales. Será mucho más fácil adivinar la trayectoria del ala inmediatamente en coordenadas globales. Pero esto es así, por cierto.




A continuación, debe decidir de qué lado miraremos el mundo. Necesito una camara.







Una cámara es una abstracción, como una imitación de una cámara física. Tiene coordenadas y algunos ángulos de inclinación que definen su ubicación en coordenadas globales. Nuestra tarea es transformar un conjunto de coordenadas ahora globales en un sistema de coordenadas de cámara. El principio es el mismo que en el ejemplo anterior:



     {
        = [   ] *  
}


, , . !


Veamos la escena desde el lugar donde está nuestra cámara condicional:







Ahora, habiendo convertido todos los puntos al sistema de coordenadas de la cámara, podemos simplemente descartar el eje Z e interpretar los ejes X e Y como "horizontal" y "vertical". Si dibuja todos los puntos de los modelos en la pantalla, obtiene una imagen, como en el ejemplo, sin perspectiva y es difícil entender qué parte de la escena cae realmente en el marco. La cámara parece tener un tamaño infinito en todas las direcciones. De alguna manera podemos ajustar todo para que lo que necesitamos se ajuste a la pantalla, pero sería bueno tener una forma universal de determinar qué parte de la escena caerá en el campo de visión de la cámara y cuál no.



Con las cámaras físicas, podemos hablar de un ángulo de visión. ¿Por qué no agregarlo aquí también?



Para ello, necesitamos otra matriz: la matriz de proyección. En general, se puede construir de diferentes formas. Dependiendo de lo que se tome como parámetros iniciales, obtendrá una vista ligeramente diferente de esta misma matriz, pero la esencia será la misma. Tomaremos la siguiente versión ligeramente simplificada:



//     90 
const s = 1 / (Math.tan(90 * Math.PI / 360));
const n = 0.001;
const f = 10;

const projectionMatrix  = [
    [s, 0, 0,          0],
    [0, s, 0,          0],
    [0, 0, -(f)/(f-n), -f*n/(f-n)],
    [0, 0, -1,         0]
];


La matriz de proyección, de una forma u otra, contiene tres parámetros: este es el ángulo de visión, así como la distancia mínima y máxima a los puntos con los que avanza el trabajo. Puede expresarse de diferentes maneras, puede usarse de diferentes maneras, pero estos parámetros estarán en esta matriz de todos modos.



Entiendo que nunca es obvio por qué la matriz se ve exactamente así, pero para derivarla con explicaciones, necesita fórmulas para 2-3 páginas. Esto una vez más nos devuelve a la idea de que es útil abstraer, podemos operar con un resultado más general, sin entrar en pequeños detalles donde no es necesario resolver un problema específico.



Ahora, haciendo las ya conocidas transformaciones:



       {
      = [   ] *   
}


Obtenemos en nuestro campo de visión exactamente lo que esperamos. Al aumentar el ángulo, vemos sobre todo en los lados, disminuyendo el ángulo, solo vemos lo que está más cerca de la dirección hacia la que se dirige la cámara. ¡LUCRO!







Pero en realidad no. Nos olvidamos de la perspectiva. Se necesita una imagen desesperada en algunos lugares, por lo que debe agregarla de alguna manera. Y aquí, de repente, no necesitamos matrices. La tarea parece muy difícil, pero se resuelve mediante la división banal de las coordenadas X e Y por W para cada punto:







* Aquí hemos desplazado la cámara hacia un lado y hemos agregado líneas paralelas "en el piso" para dejar más claro dónde aparece esta perspectiva.



Al elegir los coeficientes a su gusto, obtendremos diferentes opciones de perspectiva. En cierto sentido, los coeficientes aquí determinan el tipo de lente, cuánto "aplana" el espacio circundante.


Ahora tenemos una imagen completa. Puede tomar las coordenadas X e Y de cada punto y dibujarlo en la pantalla de la forma que desee.



En general, esto es suficiente para construir una escena, pero en proyectos reales también puede encontrar una transformación adicional asociada con el escalado al final. La idea es que luego de la proyección obtenemos las coordenadas (x, y) dentro de 1, las coordenadas normalizadas, y luego las multiplicamos por el tamaño de la pantalla o lienzo, obteniendo las coordenadas para mostrar en pantalla. Este paso adicional elimina el tamaño del lienzo de todos los cálculos, dejándolo solo al final. A veces esto es conveniente.



Aquí probablemente te duela la cabeza por la cantidad de información, así que vamos a reducir la velocidad y repetir todas las transformaciones en un solo lugar:







Si combinas estas transformaciones en una sola, obtienes un pequeño motor.



¿Cómo se ve en Three.js?



Ahora que entendemos de dónde vino este pequeño motor, echemos un vistazo a un ejemplo del sombreador de vértices predeterminado en Three.js que "no hace nada":



void main() {
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}


o más completamente:



void main() {
    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}


¿Te recuerda a algo? Sí, este es este motor en particular. Y por "no hace nada" queremos decir que simplemente hace todo el trabajo de recalcular las coordenadas, basándose en matrices pasadas cuidadosamente desde Three.js. Pero nadie se molesta en hacer estas matrices con sus propias manos, ¿verdad?



Tipos de cámara en gráficos por computadora y Three.js



El tema de los tipos de cámara no concierne directamente a las matrices, pero de todos modos le dedicaremos un par de minutos, ya que todavía estamos hablando de Three.js, y a veces la gente tiene un lío en la cabeza al respecto.



La cámara es una abstracción. Nos ayuda a pensar en el mundo 3D de la misma manera que pensamos en el mundo real. Como dijimos, una cámara tiene una posición en el espacio, una dirección en la que mira y un ángulo de visión. Todo esto se especifica mediante dos matrices y, posiblemente, una división adicional de coordenadas para crear perspectiva.



En gráficos por computadora, tenemos dos tipos de cámaras: "con perspectiva" y "sin perspectiva". Estos son dos tipos de cámaras fundamentalmente diferentes en términos técnicos, que requieren diferentes pasos para obtener una imagen. Y eso es todo. No hay nada más. Todo lo demás son sus combinaciones, algunas abstracciones más complejas. Por ejemplo, Three.js tiene una cámara estéreo; esta no es una especie de cámara de "tipo especial" en términos técnicos, sino solo una abstracción, dos cámaras ligeramente espaciadas en el espacio y ubicadas en un ángulo:







para cada mitad de la pantalla, tomamos nuestra propia cámara y resulta imagen estéreo. Y CubeCamera son 6 cámaras ordinarias ubicadas en diferentes lados desde un punto, nada más.



¿Que sigue?



El siguiente paso, después de obtener las coordenadas de los objetos, es determinar qué objetos serán visibles y cuáles estarán ocultos detrás de otros. En el contexto de WebGL, el navegador lo hará por sí mismo. Bueno, todavía habrá tareas relacionadas como aplicarles texturas, calcular la iluminación por normales, sombras, postprocesar imágenes, etc. Pero ya hemos hecho la parte más importante y difícil de entender. Es genial. De hecho, muchas cosas generativas no necesitan estas mismas texturas e iluminación, por lo que es muy posible que el conocimiento adquirido ahora sea suficiente para trabajar con ellas.



Por cierto, proyectar una sombra de un objeto a un plano no es más que una proyección de este objeto en este mismo plano en un cierto ángulo, seguida de una mezcla de colores. El proceso es inherentemente muy similar a la cámara, pero también agrega un ángulo entre el plano de proyección y la "dirección de visión".


Acerca de las texturas y efectos para imágenes en WebGL, incluso sin bibliotecas, hablamos en artículos anteriores más de una vez. Puede consultarlos si está interesado en este tema. Por lo tanto, al combinar todo este conocimiento, podemos construir cosas 3D llenas de color con nuestras propias manos.



3D- . – - . , Three.js . , , , - , - . , .




Ahora es el momento de resumir lo anterior para que haya espacio en su cabeza para el próximo caso de uso de matrices.



Entonces:



  • Puede construir un mundo en 3D y calcular las coordenadas de los objetos en la pantalla con sus propias manos usando un tren de matrices.
  • En el mundo 3D, operamos con una abstracción como una "cámara". Tiene una ubicación, dirección y ángulo de visión. Todo esto se establece utilizando las mismas matrices. Y hay dos vistas de cámara básicas: perspectiva y no perspectiva.
  • En el contexto de WebGL, la representación manual de una imagen en la pantalla o los cálculos físicos a menudo pueden eliminar las grandes dependencias y acelerar la carga de la página. Pero es importante lograr un equilibrio entre sus scripts, herramientas listas para usar y opciones alternativas para resolver problemas, prestando atención no solo a su conveniencia, sino también a los problemas de velocidad de descarga y rendimiento máximo, incluso en teléfonos.


III. Filtros para imágenes



Finalmente, veremos un área de aplicación de matrices como filtros para imágenes. Si consideramos un color en formato RGBA como un vector, entonces podemos asumir que aquí podemos aplicar una transformación similar a la que usamos con coordenadas:







Y aplicar esto a la imagen según el principio obvio:



    {
       = [  ] *   
}


Si la matriz identidad actúa como una matriz, nada cambiará, eso ya lo sabemos. ¿Qué sucede si aplica filtros similares a las transformaciones de traducción y escala?







UNED. El resultado son filtros de brillo y contraste. Interesante.



Cuando experimente con dichos filtros, siempre debe recordar ajustar los valores para que la imagen no esté sobreexpuesta. Si multiplica algo por un número grande, lo más probable es que necesite restar o dividir algo en alguna parte. Como se muestra en el ejemplo anterior.


Pero, ¿cómo hacer una imagen en blanco y negro a partir de una en color? Lo primero que me viene a la mente es sumar los valores del canal RGB, dividir por 3 y usar el valor resultante para los tres canales. En formato de matriz, se verá algo así:







Y aunque obtuvimos una imagen en blanco y negro, aún se puede mejorar. Nuestro ojo percibe la claridad de diferentes colores de manera diferente. Y para transmitir esto de alguna manera durante la desaturación, hacemos diferentes coeficientes para cada canal RGB en esta matriz.



El siguiente ejemplo presentará los valores generalmente aceptados para estos coeficientes, pero nadie se molesta en jugar con ellos. En total, estos coeficientes deberían dar 1, pero dependiendo de sus proporciones, obtendremos imágenes en blanco y negro ligeramente diferentes. Esto puede, hasta cierto punto, simular una reproducción de color diferente cuando se trabaja con cámaras de película.



Y si también multiplicamos un poco la diagonal principal, obtenemos un filtro de saturación universal:







funciona en ambas direcciones, tanto en desaturación (puede llegar a una imagen completamente en blanco y negro), como en saturación. Todo depende del coeficiente correspondiente.



En general, puede jugar con filtros durante mucho tiempo, obteniendo una variedad de resultados:







* Las matrices utilizadas en este ejemplo se pueden ver en GitHubsi de repente los necesita. Para insertarse en el artículo, su volumen será excesivo.



Pero sigamos prestando un poco de atención a dónde se aplica esto realmente. Está claro que la sola idea de reemplazar el color de cada píxel sugiere sombreadores para procesar una foto, o para posprocesar alguna escena 3D, pero ¿tal vez todavía esté en algún lugar de la interfaz?



Filtros en CSS



En CSS, tenemos una propiedad de filtro. Y allí, en particular, existen tales opciones para filtros relacionados con los colores:



  • brillo (lo logramos)
  • contraste (hecho)
  • invertir (igual que el contraste, solo coeficientes diagonales principales con un signo diferente)
  • saturar (hecho)
  • escala de grises (como ya se señaló, este es un caso especial de saturación)
  • sepia (un concepto muy vago, se obtienen diferentes versiones de sepia jugando con coeficientes, donde de alguna manera reducimos la presencia de azul)


Y estos filtros aceptan coeficientes como entrada, que luego se sustituyen de una forma u otra en las matrices que hicimos anteriormente. Ahora sabemos cómo funciona esta magia desde adentro. Y ahora está claro cómo se combinan estos filtros en las entrañas del intérprete CSS, porque todo aquí se construye según el mismo principio que con las coordenadas: multiplicar matrices - añadir efectos. Es cierto que no hay una matriz de función personalizada en esta propiedad en CSS. ¡Pero está en SVG!



Filtrar matrices en SVG



Dentro de SVG, tenemos feColorMatrix, que se usa para crear filtros para imágenes. Y aquí ya tenemos total libertad: podemos hacer una matriz a nuestro gusto. La sintaxis es algo como esto:



<filter id=’my-color-filter’>
    <feColorMatrix in=’SourceGraphics’
        type=’matrix’,
        values=’1 0 0 0 0
                0 1 0 0 0
                0 0 1 0 0
                0 0 0 1 0
                0 0 0 0 1‘
    />
</filter>


También puede aplicar filtros SVG a elementos DOM regulares dentro de CSS, hay una función de url especial para esto ... ¡Pero no les dije eso!



De hecho, los filtros SVG dentro de CSS aún no son compatibles con todos los navegadores (sin señalar con el dedo a IE), pero hay rumores de que Edge finalmente se está moviendo a un motor de cromo y las versiones anteriores perderán soporte en un futuro previsible, por lo que es hora de utilizar esta tecnología. maestro, puedes hacer muchas cosas interesantes con él.


¿Qué más pasa?



Además de los efectos para imágenes, construidos sobre el principio de transformaciones, hay varias cosas construidas sobre desplazamientos de píxeles, mezclando sus colores y otras manipulaciones, donde la matriz puede ser un buen formato para almacenar datos mediante los cuales esta misma manipulación debería tener lugar.



Matriz de kernel



En particular, en la interfaz, nos encontramos con algo como la matriz del núcleo y los efectos asociados con ella. El punto es simple: hay una matriz cuadrada, generalmente 3x3 o 5x5, aunque puede haber más, y los coeficientes se almacenan en ella. En el centro de la matriz, para el píxel "actual", alrededor del centro, para los píxeles vecinos. Si la matriz es de 5x5, entonces aparece otra capa alrededor del centro, para los píxeles ubicados uno del actual. Si es 7x7, entonces otra capa, etc. En otras palabras, consideramos la matriz como un campo bidimensional, en el que puede organizar los coeficientes a su discreción, ya sin referencia a ninguna ecuación. Y se interpretarán de la siguiente manera:



    {
       =
           ,      
}


Un lienzo en blanco no es muy adecuado para tales tareas, pero los sombreadores son muy uniformes. Pero es fácil adivinar que cuanto mayor sea la matriz, más píxeles vecinos utilizaremos. Si la matriz es 3x3, agregaremos 9 colores, si 5x5 - 25, si 7x7 - 49, etc. Más operaciones: más carga en el procesador o la tarjeta de video. Esto afectará inevitablemente el rendimiento de la página en su conjunto.



Siempre que sea posible, use matrices pequeñas para tales efectos si necesita superponerlos en algún lugar en tiempo real.


Dentro de SVG, tenemos una etiqueta feConvolveMatrix especial, que está hecha solo para crear tales efectos:



<filter id=’my-image-filter’>
    <feConvolveMatrix
        kernelMatrix=’0 0 0
                      0 1 0
                      0 0 0’
    />
</filter>


Aquí hemos creado el filtro más simple para la imagen, que no hace nada: el nuevo color de cada píxel será igual al actual multiplicado por 1, y los valores de los colores de los píxeles vecinos se multiplicarán por 0.



Tenga en cuenta que los diferentes navegadores procesan los SVG de manera diferente, y la reproducción del color también puede flotar muy ampliamente. A veces, la diferencia es simplemente catastrófica. Por lo tanto, siempre pruebe sus filtros SVG o use lienzo, que es más predecible en nuestro contexto.


Si comenzamos a organizar los números en capas, de mayor a menor, nos volvemos borrosos: cuanto







más grande es la matriz, más píxeles vecinos tocamos, más se lava la imagen. Lo principal aquí es no olvidar normalizar los valores, de lo contrario, la imagen simplemente se iluminará.



Ahora, sabiendo cómo funciona el desenfoque, podemos entender por qué su uso activo en una página dentro de CSS o SVG conduce a frenos: para cada píxel, el navegador hace un montón de cálculos.


Si comienza a experimentar cambiando los signos de los coeficientes y los organiza en diferentes patrones, obtendrá efectos de nitidez, detección de bordes y algunos otros. Intenta jugar con ellos tú mismo. Esto puede resultar útil.







Por lo tanto, puede crear diferentes efectos para fotos, o incluso videos, en tiempo real y hacer que dependan de alguna acción del usuario. Todo depende de tu imaginación.



Subtotales



Resumamos lo que se dijo en esta parte:



  • Las matrices se pueden utilizar no solo para transformaciones relacionadas con coordenadas, sino también para crear filtros de color. Todo se hace según el mismo principio.
  • Las matrices se pueden utilizar como un almacenamiento 2D conveniente para algunos datos, incluidos diferentes coeficientes para efectos visuales.


Conclusión



Si nos abstraemos un poco de los intrincados algoritmos, las matrices se convertirán en una herramienta asequible para resolver problemas prácticos. Con su ayuda, puede calcular transformaciones geométricas con sus propias manos, incluso dentro del marco de CSS y SVG, construir escenas 3D, y también hacer todo tipo de filtros para fotos o para post-procesamiento de imágenes dentro del marco de WebGL. Todos estos temas suelen ir más allá del frontend clásico y están más relacionados con la infografía en general, pero incluso si no resuelves estos problemas directamente, conocer los principios de su solución te permitirá comprender mejor cómo funcionan algunas de tus herramientas. Nunca será superfluo.



Espero que este artículo le haya ayudado a comprender el tema del uso práctico de matrices en la interfaz, o al menos le haya proporcionado una base a partir de la cual pueda basarse en su desarrollo posterior. Si cree que otros temas relacionados con las matemáticas o la física merecen la misma revisión en el contexto del diseño, escriba sus pensamientos en los comentarios, tal vez uno de los próximos artículos los debata.



All Articles