Animar imágenes y vídeos con cortinas.js

Índice
  1. WebGL, posicionamiento CSS y capacidad de respuesta
  2. Espera, ¿sombreros?
  3. Configuración de un avión básico.
  4. Añadir 3D e interactuar
  5. Refactorizando nuestro JavaScript
  6. Refactorizando los sombreadores
  7. Vídeos y sombreadores de desplazamiento.
  8. Profundizando

Mientras navega por los últimos sitios web galardonados, es posible que observe muchas animaciones de distorsión de imágenes sofisticadas o efectos 3D nítidos. La mayoría de ellos se crean con WebGL, una API que permite animaciones y efectos de procesamiento de imágenes acelerados por GPU . También tiende a utilizar bibliotecas creadas sobre WebGL, como three.js o pixi.js. Ambas son herramientas muy poderosas para crear escenas 3D y 2D respectivamente.

Pero debes tener en cuenta que esas bibliotecas no fueron diseñadas originalmente para crear presentaciones de diapositivas o animar elementos DOM . Sin embargo, existe una biblioteca diseñada solo para eso y cubriremos cómo usarla aquí en esta publicación.

WebGL, posicionamiento CSS y capacidad de respuesta

Digamos que estás trabajando con una biblioteca (como three.js o pixi.js) y quieres usarla para crear interacciones, como eventos de desplazamiento y de pasar el mouse sobre los elementos. ¡Puedes tener problemas! ¿Cómo posiciona los elementos WebGL en relación con el documento y otros elementos DOM? ¿Cómo manejarías la capacidad de respuesta?

Esto es exactamente lo que tenía en mente al crear Curtains.js.

Curatins.js te permite crear planos que contienen imágenes y videos (en WebGL los llamaremos texturas) que actúan como elementos HTML simples, con posición y tamaño definidos por reglas CSS. Pero estos planes se pueden mejorar con las infinitas posibilidades de WebGL y los sombreadores.

Espera, ¿sombreros?

Los shaders son pequeños programas escritos en GLSL que le indicarán a tu GPU cómo renderizar tus planos. Saber cómo funcionan los shaders es obligatorio aquí porque así es como manejaremos las animaciones. Si nunca has oído hablar de ellos, es posible que quieras aprender los conceptos básicos primero. Hay muchos buenos sitios web para comenzar a aprenderlos, como The Book of Shaders .

Ahora que tienes la idea, ¡creemos nuestro primer avión!

Configuración de un avión básico.

Para mostrar nuestro primer plano, necesitaremos un poco de HTML, CSS y algo de JavaScript para crear el plano. Luego nuestros sombreadores lo animarán.

HTML

El HTML será realmente simple aquí. Crearemos uno divque contendrá nuestro lienzo y otro divque contendrá nuestra imagen.

body  !-- div that will hold our WebGL canvas --  div/div  !-- div used to create our plane --  div    !-- image that will be used as a texture by our plane --    img src="path/to/my-image.jpg" /        /div/body

CSS

Usaremos CSS para asegurarnos de divque lo que envuelve el lienzo cubre la ventana y aplica cualquier tamaño al div del plano. (Nuestro plano WebGL tendrá exactamente el mismo tamaño y posiciones que este div).

También proporcionaremos algunas reglas CSS básicas para aplicar en caso de cualquier error durante la inicialización.

body {  /* make the body fit our viewport */  position: relative;  width: 100%;  height: 100vh;  margin: 0;    /* hide scrollbars */  overflow: hidden;}#canvas {  /* make the canvas wrapper fit the window */  position: absolute;  top: 0;  left: 0;  width: 100%;  height: 100vh;}.plane {  /* define the size of your plane */  width: 80%;  max-width: 1400px;  height: 80vh;  position: relative;  top: 10vh;  margin: 0 auto;}.plane img {  /* hide the img element */  display: none;}/*** in case of error show the image ***/.no-curtains .plane {  overflow: hidden;  display: flex;  align-items: center;  justify-content: center;}.no-curtains .plane img {  display: block;  max-width: 100%;  object-fit: cover;}

Javascript

Hay un poco más de trabajo en JavaScript. Necesitamos crear una instancia de nuestro contexto WebGL, crear un plano con parámetros uniformes y usarlo. Para este primer ejemplo también veremos cómo detectar errores.

window.onload = function() {  // pass the id of the div that will wrap the canvas to set up our WebGL context and append the canvas to our wrapper  var webGLCurtain = new Curtains("canvas");  // if there's any error during init, we're going to catch it here  webGLCurtain.onError(function() {    // we will add a class to the document body to display original images    document.body.classList.add("no-curtains");  });  // get our plane element  var planeElement = document.getElementsByClassName("plane")[0];  // set our initial parameters (basic uniforms)  var params = {    vertexShaderID: "plane-vs", // our vertex shader ID    fragmentShaderID: "plane-fs", // our fragment shader ID    uniforms: {      time: {        name: "uTime", // uniform name that will be passed to our shaders        type: "1f", // this means our uniform is a float        value: 0,      },    }  }  // create our plane mesh  var plane = webGLCurtain.addPlane(planeElement, params);  // if our plane has been successfully created  // we use the onRender method of our plane fired at each requestAnimationFrame call  plane  plane.onRender(function() {    plane.uniforms.time.value++; // update our time uniform value  });}

Sombrereros

Necesitamos escribir el sombreador de vértices. Básicamente, necesitaremos posicionar nuestro plano en función de la vista del modelo y la matriz de proyección y pasar las variables al sombreador de fragmentos.

Una de esas variables se llama vTextureCoordy se utiliza en el sombreador de fragmentos para mapear nuestra textura en el plano. Podríamos pasar directamente nuestro aTextureCoordatributo, pero obtendríamos una textura estirada porque nuestro plano y nuestra imagen no tendrán necesariamente la misma relación de aspecto. Afortunadamente, la biblioteca proporciona una matriz de textura uniforme que podemos usar para calcular nuevas coordenadas que recortarán la textura para que siempre se ajuste al plano (considérelo como un tamaño de fondo: equivalente a la cubierta).

!-- vertex shader --script type="x-shader/x-vertex"  #ifdef GL_ES  precision mediump float;  #endif  // those are the mandatory attributes that the lib sets  attribute vec3 aVertexPosition;  attribute vec2 aTextureCoord;  // those are mandatory uniforms that the lib sets and that contain our model view and projection matrix  uniform mat4 uMVMatrix;  uniform mat4 uPMatrix;  // our texture matrix uniform (this is the lib default name, but it could be changed)  uniform mat4 uTextureMatrix0;  // if you want to pass your vertex and texture coords to the fragment shader  varying vec3 vVertexPosition;  varying vec2 vTextureCoord;  void main() {    // get the vertex position from its attribute    vec3 vertexPosition = aVertexPosition;    // set its position based on projection and model view matrix    gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);    // set the varying variables    // thanks to the texture matrix we will be able to calculate accurate texture coords    // so that our texture will always fit our plane without being distorted    vTextureCoord = (uTextureMatrix0 * vec4(aTextureCoord, 0.0, 1.0)).xy;    vVertexPosition = vertexPosition;  }/script

Ahora, pasemos a nuestro sombreador de fragmentos. Aquí es donde agregaremos un pequeño efecto de desplazamiento basado en nuestro uniforme de tiempo y las coordenadas de textura.

!-- fragment shader --script type="x-shader/x-fragment"  #ifdef GL_ES  precision mediump float;  #endif  // get our varying variables  varying vec3 vVertexPosition;  varying vec2 vTextureCoord;  // the uniform we declared inside our javascript  uniform float uTime;  // our texture sampler (this is the lib default name, but it could be changed)  uniform sampler2D uSampler0;  void main() {    // get our texture coords    vec2 textureCoord = vTextureCoord;    // displace our pixels along both axis based on our time uniform and texture UVs    // this will create a kind of water surface effect    // try to comment a line or change the constants to see how it changes the effect    // reminder : textures coords are ranging from 0.0 to 1.0 on both axis    const float PI = 3.141592;    textureCoord.x += (      sin(textureCoord.x * 10.0 + ((uTime * (PI / 3.0)) * 0.031))      + sin(textureCoord.y * 10.0 + ((uTime * (PI / 2.489)) * 0.017))      ) * 0.0075;    textureCoord.y += (      sin(textureCoord.y * 20.0 + ((uTime * (PI / 2.023)) * 0.023))      + sin(textureCoord.x * 20.0 + ((uTime * (PI / 3.1254)) * 0.037))      ) * 0.0125;              gl_FragColor = texture2D(uSampler0, textureCoord);  }/script

¡Y voilá! Ya terminaste y, si todo salió bien, deberías ver algo como esto.

Añadir 3D e interactuar

Muy bien, eso está muy bien hasta ahora, pero comenzamos esta publicación hablando sobre 3D e interacciones, así que veamos cómo podríamos agregarlos.

Acerca de los vértices

Para agregar un efecto 3D tendríamos que cambiar la posición de los vértices planos dentro del sombreador de vértices. Sin embargo, en nuestro primer ejemplo, no especificamos cuántos vértices debería tener nuestro plano, por lo que se creó con una geometría predeterminada que contiene seis vértices que forman dos triángulos:

Para obtener animaciones 3D decentes, necesitaríamos más triángulos y, por tanto, más vértices:

Refactorizando nuestro JavaScript

Afortunadamente, es fácil especificar nuestra definición de plano, ya que podría establecerse dentro de nuestros parámetros iniciales.

También escucharemos la posición del mouse para agregar un poco de interacción. Para hacerlo correctamente, tendremos que esperar a que el plano esté listo, convertir las coordenadas del documento del mouse a las coordenadas del espacio del clip WebGL y enviarlas a los sombreadores como un uniforme.

// we are using window onload event here but this is not mandatorywindow.onload = function() {  // track the mouse positions to send it to the shaders  var mousePosition = {    x: 0,    y: 0,  };  // pass the id of the div that will wrap the canvas to set up our WebGL context and append the canvas to our wrapper  var webGLCurtain = new Curtains("canvas");  // get our plane element  var planeElement = document.getElementsByClassName("plane")[0];  // set our initial parameters (basic uniforms)  var params = {    vertexShaderID: "plane-vs", // our vertex shader ID    fragmentShaderID: "plane-fs", // our framgent shader ID    widthSegments: 20,    heightSegments: 20, // we now have 20*20*6 = 2400 vertices !    uniforms: {      time: {        name: "uTime", // uniform name that will be passed to our shaders        type: "1f", // this means our uniform is a float        value: 0,      },      mousePosition: { // our mouse position        name: "uMousePosition",        type: "2f", // notice this is a length 2 array of floats        value: [mousePosition.x, mousePosition.y],      },      mouseStrength: { // the strength of the effect (we will attenuate it if the mouse stops moving)        name: "uMouseStrength", // uniform name that will be passed to our shaders        type: "1f", // this means our uniform is a float        value: 0,      },    }  }  // create our plane mesh  var plane = webGLCurtain.addPlane(planeElement, params);  // if our plane has been successfully created we could start listening to mouse/touch events and update its uniforms  plane  plane.onReady(function() {    // set a field of view of 35 to exaggerate perspective    // we could have done it directly in the initial params    plane.setPerspective(35);    // listen our mouse/touch events on the whole document    // we will pass the plane as second argument of our function    // we could be handling multiple planes that way    document.body.addEventListener("mousemove", function(e) {      handleMovement(e, plane);    });    document.body.addEventListener("touchmove", function(e) {      handleMovement(e, plane);    });  }).onRender(function() {    // update our time uniform value    plane.uniforms.time.value++;    // continually decrease mouse strength    plane.uniforms.mouseStrength.value = Math.max(0, plane.uniforms.mouseStrength.value - 0.0075);  });  // handle the mouse move event  function handleMovement(e, plane) {    // touch event    if(e.targetTouches) {      mousePosition.x = e.targetTouches[0].clientX;      mousePosition.y = e.targetTouches[0].clientY;    }    // mouse event    else {      mousePosition.x = e.clientX;      mousePosition.y = e.clientY;    }    // convert our mouse/touch position to coordinates relative to the vertices of the plane    var mouseCoords = plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y);    // update our mouse position uniform    plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];        // reassign mouse strength    plane.uniforms.mouseStrength.value = 1;  }}

Ahora que nuestro JavaScript está listo, tenemos que reescribir nuestros sombreadores para que utilicen la posición del mouse de manera uniforme.

Refactorizando los sombreadores

Veamos primero nuestro sombreador de vértices. Disponemos de tres uniformes que podríamos utilizar para nuestro efecto:

  1. El tiempo que aumenta constantemente
  2. La posición del ratón
  3. La fuerza de nuestro ratón, que disminuye constantemente hasta el siguiente movimiento del ratón.

Usaremos los tres para crear una especie de efecto dominó 3D.

script type="x-shader/x-vertex"  #ifdef GL_ES  precision mediump float;  #endif  // those are the mandatory attributes that the lib sets  attribute vec3 aVertexPosition;  attribute vec2 aTextureCoord;  // those are mandatory uniforms that the lib sets and that contain our model view and projection matrix  uniform mat4 uMVMatrix;  uniform mat4 uPMatrix;  // our texture matrix uniform (this is the lib default name, but it could be changed)  uniform mat4 uTextureMatrix0;  // our time uniform  uniform float uTime;  // our mouse position uniform  uniform vec2 uMousePosition;  // our mouse strength  uniform float uMouseStrength;  // if you want to pass your vertex and texture coords to the fragment shader  varying vec3 vVertexPosition;  varying vec2 vTextureCoord;  void main() {    vec3 vertexPosition = aVertexPosition;    // get the distance between our vertex and the mouse position    float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));    // this will define how close the ripples will be from each other. The bigger the number, the more ripples you'll get    float rippleFactor = 6.0;    // calculate our ripple effect    float rippleEffect = cos(rippleFactor * (distanceFromMouse - (uTime / 120.0)));    // calculate our distortion effect    float distortionEffect = rippleEffect * uMouseStrength;    // apply it to our vertex position    vertexPosition +=  distortionEffect / 15.0;    gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);    // varying variables    // thanks to the texture matrix we will be able to calculate accurate texture coords    // so that our texture will always fit our plane without being distorted    vTextureCoord = (uTextureMatrix0 * vec4(aTextureCoord, 0.0, 1.0)).xy;    vVertexPosition = vertexPosition;  }/script

En cuanto al sombreador de fragmentos, lo haremos de forma sencilla. Vamos a simular luces y sombras en función de la posición de cada vértice:

script type="x-shader/x-fragment"  #ifdef GL_ES  precision mediump float;  #endif  // get our varying variables  varying vec3 vVertexPosition;  varying vec2 vTextureCoord;  // our texture sampler (this is the lib default name, but it could be changed)  uniform sampler2D uSampler0;  void main() {    // get our texture coords    vec2 textureCoords = vTextureCoord;    // apply our texture    vec4 finalColor = texture2D(uSampler0, textureCoords);    // fake shadows based on vertex position along Z axis    finalColor.rgb -= clamp(-vVertexPosition.z, 0.0, 1.0);    // fake lights based on vertex position along Z axis    finalColor.rgb += clamp(vVertexPosition.z, 0.0, 1.0);    // handling premultiplied alpha (useful if we were using a png with transparency)    finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);    gl_FragColor = finalColor;  }/script

¡Ahí lo tienes!

Con estos dos sencillos ejemplos hemos visto cómo crear un avión e interactuar con él.

Vídeos y sombreadores de desplazamiento.

Nuestro último ejemplo creará una presentación de diapositivas de video básica en pantalla completa utilizando un sombreador de desplazamiento para mejorar las transiciones.

Concepto de sombreador de desplazamiento

El sombreador de desplazamiento creará un bonito efecto de distorsión. Se escribirá dentro de nuestro sombreador de fragmentos utilizando una imagen en escala de grises y desplazará las coordenadas de píxeles de los videos en función de los valores RGB de la textura. Esta es la imagen que utilizaremos:

El efecto se calculará en función del valor RGB de cada píxel, siendo un píxel negro [0, 0, 0]y un píxel blanco [1, 1, 1](equivalente a GLSL [255, 255, 255]). Para simplificar, usaremos sólo el valor del canal rojo, ya que en una imagen en escala de grises el rojo, el verde y el azul son siempre iguales.

Puedes intentar crear tu propia imagen en escala de grises (funciona muy bien con formas geométricas) para obtener tu efecto de transición único.

Múltiples texturas y vídeos.

Un plano puede tener más de una textura simplemente agregando varias etiquetas de imagen. Esta vez, en lugar de imágenes, queremos usar videos. Solo tenemos que reemplazar las img /etiquetas por video /una. Sin embargo, hay dos cosas que debemos saber cuando se trata de videos:

  • En los dispositivos móviles, no podemos reproducir vídeos automáticamente sin un gesto del usuario, como un clic. Por lo tanto, es más seguro agregar un botón de “ingresar al sitio” para mostrar y ejecutar nuestros videos.
  • Los vídeos pueden tener un gran impacto en la memoria, el rendimiento y el ancho de banda. Debes intentar que sean lo más livianos posibles.

HTML

El código HTML sigue siendo bastante sencillo. Crearemos nuestro contenedor div de lienzo, nuestro div de plano que contiene las texturas y un botón para activar la reproducción automática del video. Solo observe el uso del data-sampleratributo en las etiquetas de imagen y video: será útil dentro de nuestros sombreadores.

body  div/div  !-- our plane --  div    !-- notice here we are using the data-sampler attribute to name our sampler uniforms --    img src="path/to/displacement.jpg" data-sampler="displacement" /    video src="path/to/video.mp4" data-sampler="firstTexture"/video    video src="path/to/video-2.mp4" data-sampler="secondTexture"/video  /div      div    span      Click to enter site    /span  /div/body

CSS

La hoja de estilo manejará algunas cosas: mostrará el botón y ocultará el lienzo antes de que el usuario haya ingresado al sitio y hará que nuestro planediv se ajuste a la ventana.

@media screen {      body {    margin: 0;    font-size: 18px;    font-family: 'PT Sans', Verdana, sans-serif;    background: #212121;    line-height: 1.4;    height: 100vh;    width: 100vw;    overflow: hidden;  }      /*** canvas ***/      #canvas {    position: absolute;    top: 0;    left: 0;    width: 100%;    height: 100vh;    z-index: 10;        /* hide the canvas until the user clicks the button */    opacity: 0;    transition: opacity 0.5s ease-in;  }    /* display the canvas */  .video-started #canvas {    opacity: 1;  }      .plane {    position: absolute;    top: 0;    right: 0;    bottom: 0;    left: 0;    z-index: 15;        /* tell the user he can click the plane */    cursor: pointer;  }      /* hide the original image and videos */  .plane img, .plane video {    display: none;  }      /* center the button */  #enter-site-wrapper {    display: flex;    justify-content: center;    align-items: center;    align-content: center;    position: absolute;    top: 0;    right: 0;    bottom: 0;    left: 0;    z-index: 30;      /* hide the button until everything is ready */    opacity: 0;    transition: opacity 0.5s ease-in;  }    /* show the button */  .curtains-ready #enter-site-wrapper {    opacity: 1;  }      /* hide the button after the click event */  .curtains-ready.video-started #enter-site-wrapper {    opacity: 0;    pointer-events: none;  }      #enter-site {    padding: 20px;    color: white;    background: #ee6557;    max-width: 200px;    text-align: center;    cursor: pointer;  }}

Javascript

En cuanto a JavaScript, haremos lo siguiente:

  • Establezca un par de variables para almacenar el estado de nuestra presentación de diapositivas.
  • Crea el objeto Cortinas y agrega el plano.
  • Cuando el avión esté listo, escuche un evento de clic para iniciar la reproducción de nuestros videos (observe el uso del playVideos()método). Agregue otro evento de clic para cambiar entre los dos videos.
  • Actualice nuestro uniforme del temporizador de transición dentro del onRender()método.
window.onload = function() {  // here we will handle which texture is visible and the timer to transition between images  var activeTexture = 1;  var transitionTimer = 0;    // set up our WebGL context and append the canvas to our wrapper  var webGLCurtain = new Curtains("canvas");    // get our plane element  var planeElements = document.getElementsByClassName("plane");    // some basic parameters  var params = {    vertexShaderID: "plane-vs",    fragmentShaderID: "plane-fs",    imageCover: false, // our displacement texture has to fit the plane    uniforms: {      transitionTimer: {        name: "uTransitionTimer",        type: "1f",        value: 0,      },    },  }      var plane = webGLCurtain.addPlane(planeElements[0], params);      // if our plane has been successfully created  plane  plane.onReady(function() {    // display the button    document.body.classList.add("curtains-ready");      // when our plane is ready we add a click event listener that will switch the active texture value    planeElements[0].addEventListener("click", function() {      if(activeTexture == 1) {        activeTexture = 2;      }      else {        activeTexture = 1;      }    });        // click to play the videos    document.getElementById("enter-site").addEventListener("click", function() {      // display canvas and hide the button      document.body.classList.add("video-started");            // play our videos      plane.playVideos();    }, false);      }).onRender(function() {    // increase or decrease our timer based on the active texture value    // at 60fps this should last one second    if(activeTexture == 2) {      transitionTimer = Math.min(60, transitionTimer + 1);    }    else {      transitionTimer = Math.max(0, transitionTimer - 1);    }    // update our transition timer uniform    plane.uniforms.transitionTimer.value = transitionTimer;  });}

Sombrereros

Aquí es donde ocurrirá toda la magia. Como en nuestro primer ejemplo, el sombreador de vértices no hará mucho y tendrás que concentrarte en el sombreador de fragmentos que creará un efecto de “inmersión”:

script type="x-shader/x-vertex"  #ifdef GL_ES  precision mediump float;  #endif  // default mandatory variables  attribute vec3 aVertexPosition;  attribute vec2 aTextureCoord;  uniform mat4 uMVMatrix;  uniform mat4 uPMatrix;  // our texture matrices  // notice how it matches our data-sampler attributes + "Matrix"  uniform mat4 firstTextureMatrix;  uniform mat4 secondTextureMatrix;  // varying variables  varying vec3 vVertexPosition;  // our displacement texture will use original texture coords attributes  varying vec2 vTextureCoord;  // our videos will use texture coords based on their texture matrices  varying vec2 vFirstTextureCoord;  varying vec2 vSecondTextureCoord;  // custom uniforms  uniform float uTransitionTimer;  void main() {    vec3 vertexPosition = aVertexPosition;    gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);    // varying variables    // texture coords attributes because we want our displacement texture to be contained    vTextureCoord = aTextureCoord;    // our videos texture coords based on their texture matrices    vFirstTextureCoord = (firstTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;    vSecondTextureCoord = (secondTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;    // vertex position as usual    vVertexPosition = vertexPosition;  }/scriptscript type="x-shader/x-fragment"  #ifdef GL_ES  precision mediump float;  #endif  // all our varying variables  varying vec3 vVertexPosition;  varying vec2 vTextureCoord;  varying vec2 vFirstTextureCoord;  varying vec2 vSecondTextureCoord;  // custom uniforms  uniform float uTransitionTimer;  // our textures samplers  // notice how it matches our data-sampler attributes  uniform sampler2D firstTexture;  uniform sampler2D secondTexture;  uniform sampler2D displacement;  void main( void ) {    // our texture coords    vec2 textureCoords = vTextureCoord;    // our displacement texture    vec4 displacementTexture = texture2D(displacement, textureCoords);    // our displacement factor is a float varying from 1 to 0 based on the timer    float displacementFactor = 1.0 - (cos(uTransitionTimer / (60.0 / 3.141592)) + 1.0) / 2.0;    // the effect factor will tell which way we want to displace our pixels    // the farther from the center of the videos, the stronger it will be    vec2 effectFactor = vec2((textureCoords.x - 0.5) * 0.75, (textureCoords.y - 0.5) * 0.75);    // calculate our displaced coordinates of the first video    vec2 firstDisplacementCoords = vec2(vFirstTextureCoord.x - displacementFactor * (displacementTexture.r * effectFactor.x), vFirstTextureCoord.y- displacementFactor * (displacementTexture.r * effectFactor.y));    // opposite displacement effect on the second video    vec2 secondDisplacementCoords = vec2(vSecondTextureCoord.x - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.x), vSecondTextureCoord.y - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.y));    // apply the textures    vec4 firstDistortedColor = texture2D(firstTexture, firstDisplacementCoords);    vec4 secondDistortedColor = texture2D(secondTexture, secondDisplacementCoords);    // blend both textures based on our displacement factor    vec4 finalColor = mix(firstDistortedColor, secondDistortedColor, displacementFactor);    // handling premultiplied alpha    finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);    // apply our shader    gl_FragColor = finalColor;  }/script

Aquí está nuestra pequeña presentación de diapositivas en video con un efecto de transición genial:

Este ejemplo es una excelente manera de mostrarte cómo crear una presentación de diapositivas con cortinas.js: es posible que quieras usar imágenes en lugar de videos, cambiar la textura de desplazamiento, modificar el sombreador de fragmentos…

También podríamos agregar más diapositivas, pero luego tendríamos que encargarnos del intercambio de texturas. No cubrimos esto aquí, pero debes saber que hay un ejemplo en el sitio web de la biblioteca que trata sobre este tema.

Profundizando

Acabamos de raspar la superficie de lo que es posible con cortinas.js. Podrías intentar crear varios planos con un genial efecto de pasar el ratón sobre los pulgares de tu artículo, por ejemplo. Las posibilidades son casi infinitas.

Si desea echar un vistazo a la documentación completa de la API o ver más ejemplos que cubren todos esos usos básicos, puede consultar el sitio web de la biblioteca o el repositorio de GitHub.

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion