Efectos Especiales con CSS

Este artículo describe que son los shaders CSS y como funcionan incluyendo ejemplos en código. Incluso cubre como usar los shaders para crear efectos personalizados y como escribir tus propios shaders.

Unos cuantos avisos: Este trabajo esta en progreso. Mientras discutimos con lo ancho de la comunidad, haremos algunos cambios. La sintaxis usada en este artículo, refleja la sintaxis propuesta para los shaders CSS, sin embargo, puede evolucionar mientras continua la discusión entre la W3C FX Task Force y el ancho de la comunidad. Acorde a una práctica común, todas las nuevas propiedades propuestas tendrán el prefijo -webkit- en nuestro prototipo WebKit. A favor de la simplicidad, he omitido este prefijo en el resto del artículo.

Efectos más sofisticados como relieves mostrado en la Figura 1, serán posibles.

Figura 1. Filter Effects aplicados a contenido SVG, tomado del sitio svg-wow.org

Los avances en HTML5 y CSS (por ejemplo: transiciones, animaciones, transformaciones, sombras en textos y cajas, degradados, SVG )han mejorado la interactividad y riqueza del HTML. Los filtros SVG, ahora son Filter Effects 1.0 disponibles en CSS y HTML, adicionalmente a SVG, los cuales traeran efectos como escala de grises, tono sepia, o hue-rotate a todo contenido web.

La propuesta de Adobe CSS shaders fue traída por la W3C FX task force. Los shaders CSS definen el mecanismo de extensión de un filtro y proveen fácilmente efectos visuales animados a cualquier contenido HTML5. Trabajan particularmente bien con animaciones CSS y transiciones CSS.

Los siguientes videos muestran ejemplos de shaders CSS en acción sobre contenido HTML.

Demo Flipbook CSS shaders

Desdoblamiento de Mapa CSS shaders

Demo Twitter Feed CSS shaders

Filter Effects 1.0

Para entender los shaders CSS, es necesario tener un entendimiento básico de efectos de filtro predeterminados. El siguiente demo muestra algunos ejemplos simples de efectos de filtros: El usuario obtiene una una disolvencia de color a una simple escala de grises en el estado hover.

Demo Escala de Grises

Este es el código para el efecto escala de grises.

<html>
  <head> 
  ... 
  <style>
  #shaded {
  filter: grayscale(1);
  transition: filter ease-in 0.3s;
  }
#shaded:hover {
  filter: grayscale(0);
  }
  </style>
  </head>
<body>
  <div id="shaded">
  <div id="multi-col">
  <h2>The Creative Web</h2>
 <p>Lorem ipsum dolor ... </p>
  <img id="png-img" src="planes.jpg"/>
 <p>Mauris at ... </p>
  <img id="svg-img" src="picture.svg" />
 
  <p>Morbi congue ....</p>
  <img id="css3-img" src="html5_css3_styling.svg" />
 
  </div>
  </div>
  </body>
  </html>

El uso de efectos de filtro es muy directo: La propiedad filter, define el filtro (o cadena de filtros) que serán aplicados. Como podrás ver, también es muy fácil de integrar con transiciones CSS. La propiedad filter puede ser animada.

En el ejemplo la función grasycale () es animada gradualmente para disolverse por el parametro amount, en la función del filtro, es animado de 1 (escala de grises al 100%) a 0 (0% escala de grises) cuando el usuario se encuentra sobre el objeto.

El borrador de la especificación W3C Filter Effects 1.0 define dos cosas:

Una sintaxis general para definir un filtro al ensamblar filtros primitivos en un gráfico.

Una propiedad CSS filter, que puede hacer referencia a una definición de filtro o usar más funciones de filtro.

La propiedad filter puede usar un número predeterminado de funciones: blur, drop-shadow, gamma, grayscale, hue-rotate, invert, opacity, saturate, sepia y sharpen. La Figura 2 ejemplifica cada una:

  1. blur (5,5)
  2. drop-shadow(10,5,5)
  3. hue-rotate(328deg)
  4. saturate(5)
  5. invert(1)
  6. grayscale(1)
  7. opacity(0.5)
  8. gamma(1.1,3.6,0)
  9. sepia(0.5)

Figura 2. Funciones de Efectos de Filtro

Lo mejor de los Efectos de Filtro, es la simplicidad de la sintaxis y la integración con animaciones CSS y transiciones CSS.

Sin embargo, algunas cosas son más difíciles. Por ejemplo, ¿qué pasa si deseas convertir sólo una porción del objeto a escala de grises? ¿qué pasa si quieres realizar la transición de otra manera, por ejemplo, como un efecto wipe sobre el elemento? ó ¿qué pasa si quieres un Efecto de Filtro que no esta entre el set predeterminado de funciones de filtro el el set de filtros primitivos predeterminados? Aquí es donde los shaders CSS entran.

La propuesta de shaders CSS, ofrece agregar un custom() a la función de filtro de Efectos de Filtro que integran filtros predeterminados, animaciones CSS y transiciones CSS. Los shaders CSS, proveen la flexibilidad y expresividad necesarias para crear efectos de manera arbitraria, desde algo simple hasta lo más complejo.

Efectos de Filtro personalizados con shaders CSS

Comenzare con un ejemplo. El efecto mostrado arriba (disolvencia de escala de grises a color)es bueno, pero se puede mejorar. El siguiente video muestra un efecto más sofisticado: mientras ocurre la transición de escala de grises a color, el contenido se deforma por un momento y simultáneamente, ocurre un color-swipe sobre el contenido de abajo hacia arriba.

Demo de shaders CSS.

Este demo utiliza un vertex shader para el efecto de distorción y un pixel shader para el efecto swipe de color. Ambos están referenciados por la función filter custom() que también lleva los parámetros para los shaders, así como algunas opciones de configuración. Este es el código:

 <html>
  <head> 
  ... 
  <style>
  #shaded {
  filter: custom(url('wobble.vs')        /* wobble effect */
  url('color-swipe.fs'), /* swipe effect  */
  40 40,                 /* mesh lines/cols */
  amplitude 60,          /* wobble strength */
  amount 0.0);           /* effect amount */
  transition: filter ease-in-out 2s;
  ...;
  }
#shaded:hover {
  filter: custom(url('wobble.vs') 
  url('color-swipe.fs'), 
  40 40, 
  amplitude 60, 
  amount 1.0);
  }
  </style>
  </head>
<body>
  <div id="shaded">
  <div id="multi-col">
  <h2>The Creative Web</h2>
  <!-- Same as previous example -->
  </div>
  </div>
  </body>
  </html>

Pueden ver la similitud del código con el ejemplo anterior en escala de grises. Para otros Efectos de Filtro, usamos la propiedad filter para definir el efecto. La diferencia radica en lugar de usar una de las funciones predeterminadas de filter, usamos la función custom() y referenciamos los dos shaders. Las especificidades de los shaders serán discutidas más adelante. Por el momento, es importante entender que cada shader, provee un efecto específico y expone parámetros que pueden ser animados vía CSS.

En este ejemplo, el filtro personalizado utiliza un wobble.vs vertex shader (para la distorción) y un fragmento del shader color-swipe.fs para el barrido de escala de grises a color.

¿Qué son los shaders?

Los shaders son comunes en gráficos 3D. Son regularmente, pequeños programas que procesan los vértices de geometría 3D (vertex shaders) y el color de los pixeles (fragment shaders).

Por ejemplo, un vertex shader puede crear un efecto de una bandera ondeandose sobre una superficie, ó un efecto wobble (distorción) como el ejemplo anterior. Un fragment shader (también llamado pixel shader) puede computar de manera arbitraria el color de un pixel. Los shaders CSS utilizan el poder de los programas acelerados por hardware.

¿Cómo funcionan los shaders CSS?

Con los shaders CSS, puedes convertir tus objetos SVG o HTML en una malla con vértices. Esto se ilustra en la Figura 3, paso por paso:  1. La malla se procesa por un vertex shader(paso 2) lo que hace posible cualquier tipo de distorsión en un espacio 3D. En el paso 3, la malla se renderea o rasteriza en pixeles con los colores del fragment shader.

Figura 3. Modelo del proceso de los shaders CSS

Como autores, pueden controlar la granularidad de la malla y pueden especificar parámetros que controlen los shaders.

En el ejemplo, el elemento con el id shaded tiene sus propiedades filter con los siguientes valores:

custom(url('wobble.vs')        /* wobble effect */
url('color-swipe.fs'), /* swipe effect  */
40 40,                 /* mesh lines/cols */
amplitude 60,          /* wobble strength */
amount 0.0);           /* effect amount */

Los shaders wobble.vs y color-swipe.fs fueron programados para que, cuando el parámetro amount sea 0, no hay wobble y el pixel shader aplica de manera uniforme la escala de grises al elemento.

Cuando el usuario esta sobre el elemento con el shader, la propiedad filter es:

custom(url('wobble.vs') 
url('color-swipe.fs'), 
40 40, 
amplitude 60,
amount 1)

Recuerden que el significado del parámetro es un shader específico. En este ejemplo, el autor del shader ha diseñado que 0.5 sea el valor máximo del efecto wobble y regrese a un valor fijo en 1.0. El shader color-swipe utiliza valores entre 0.0 y 1.0 esto genera un barrido de color de abajo hacia arriba con un efecto shine. El efecto shine esta hecho agregando blanco al valor de los pixeles ubicados en la división horizontal del swipe. Ver el video anterior es la mejor manera de entender el efecto.

Así como el Efecto de Filtro de escala de grises anterior, la integración con transiciones CSS facilita el ajuste del efecto en movimiento y se hace de la misma forma, usando la propiedad transition.

Escribiendo Shaders

En la práctica, parece algo común que la gente usará efectos personalizados a través de shaders que expondrán facilmente parámetros configurados, como en el ejemplo anterior.

Escribir shaders, mientras se obtiene alguna práctica no es tan difícil. Incluso, y esto es un secreto a voces, es muy divertido generarlos

Los shaders se escriben en el lenguaje Shading OpenGL ES, el mismo lenguaje usado por shaders WebGL. Aquí esta el ejemplo del vertex shader que genera el efecto wobble expuesto anteriormente

Para aquellos que no estén familiarizados con shaders, las siguientes definiciones les ayudarán a leer los ejemplos

  • Vertex: Es una coordenada en la geometría procesada por el shader.
  • Texture: Imagen de tipo raster. Los shaders CSS, convierten el render de un elemento en textura y es procesada por el vértice y los fragment shaders.
  • Attributes: Parámetros por vértice, asignados al vertex shader.
  • Projection Matrix: Matriz que convierte las coordenadas del rango normalizado de los vértices ([-.05,+.05] sobre cada eje) al valor actual del sistema de coordenadas de la ventana actual. Se usa generalmente en vertex shader.
precision mediump float; /* required */
// ================= Per-vertex attributes =================== //
  attribute vec3 a_position; /* The vertex's coordinates */
  attribute vec2 a_texCoord; /* The vertext's texture coordinate */
// Uniform parameters are available to shaders and have the 
  // same value for all vertex or pixel.
  uniform mat4 u_projectionMatrix; /* The projection matrix */
// ================ Shader parameters ======================== //
  uniform float amplitude;
  uniform float amount;
  // ============== End shader parameters ====================== //
 
  const float rotate = 20.0; /* could be made a uniform   */
  /* to allow control form CSS */
  const float PI = 3.14...;
  varying vec2 v_texCoord;
 
  mat4 rotateX(float f) {...}
  mat4 rotateY(float f) {...}
  mat4 rotateZ(float f) {...}
  void main()
  { 
  v_texCoord = a_texCoord.xy;
  vec4 pos = vec4(a_position, 1.0);
 
  float r = 1.0 - abs((amount - 0.5) / 0.5);
  float a = r * rotate * PI / 180.0;
  mat4 rotX = rotateX(a);
  mat4 rotY = rotateY(a / 4.0);
  mat4 rotZ = rotateZ(a / 8.0);
 
  float dx = 0.01 * cos(3.0 * PI * (pos.x + amount)) * r;
  float dy = 0.01 * cos(3.0 * PI * (pos.y + amount)) * r;
  float dz = 0.1 * cos(3.0 * PI * (pos.x + pos.y + amount)) * r;
  pos.x += dx;
  pos.y += dy;
  pos.z += dz;
 
  gl_Position = u_projectionMatrix * rotZ * rotY * rotX * pos;
  }

Y este el shader color-swipe del ejemplo:

precision mediump float; /* required */
// The 'original' content rendering in a texture.
  uniform sampler2D s_texture;
// ================ Shader parameters ======================== //
  uniform float amplitude; /* unused in this fragment shader */
  uniform float amount;
  // ============== End shader parameters ====================== //
varying vec2 v_texCoord;
// The desired 'color swipe' color.
  const vec4 swipeColor = vec4(1.0, 1.0, 1.0, 1.0); 
vec4 grayscale(vec4 color) {
  ...; 
  return gray;
  }
void main() {
  vec4 color = texture2D(s_texture, v_texCoord);
  vec4 gray = grayscale(color);
  vec2 pos = v_texCoord;
 float p = 1.0 - pos.y; /* progress from bottom to top */
 
  vec4 sc = swipeColor * color.a;
  float threshold = amount * 1.2;
  if (p &lt; threshold) {
  float a = min(abs(threshold - p) / 0.2, 1.0); 
  gl_FragColor = mix(sc, color, a);
  } else {
  float a = min(abs(threshold - p) / 0.005, 1.0);
  gl_FragColor = mix(sc, gray, a);
  }
  }

Existen muy buenos recursos en la Web, para aprender como escribir shaders o algunas bibliotecas que contienen excelentes sets de shaders (hay algunas referencias al final).

Lenguajes para generar shaders como el lenguaje Shading OpenGL ES, están diseñados para programar efectos visuales de manera fácil. Los shaders CSS ofrecen una forma de amarrar expresividad en la sintaxis CSS y facilitar el uso y animación de estos efectos.

Comparación con WebGL

WebGL, provee una implementación a los elementos canvas en HTML5. WebGL ofrece un contexto 3D para los canvas y, dentro de este contexto, los pixel shaders están disponibles (también los vertex shaders y todas las características 3D que ofrece WebGL). WebGL opera dentro de los límites  del elemento canvas para el cual provee un contexto.

En contraste, los shaders CSS, proveen una manera arbitraria de aplicar shaders a contenido Web.

A dónde ir a partir de aqui

Adobe esta llevando los shaders CSS a la W3C como parte del FX Task Force y nuestro objetivo es empezar a contribuir para el código de WebKit, en cuanto el trabajo sea aceptado por la W3C los desarrolladores web podrán empezar a disfrutar de este nuevo y poderoso set de Efectos de Filtro.

Mientras tanto, pueden ver los siguientes recursos para aprender más de animaciones CSS y efectos:

La versión original de este artículo pueden encontrarla Adobe Devnet traducido aquí bajo licencia Creative Commons por Juan de Dios León, cualquier comentario pueden hacerlo en este mismo post o en @juandediosleon

Leave a Reply