1

I'm trying to integrate a WebGL animation on list items: images who give place to a video on mouseover.

The transitions are same as http://taotajima.jp/ and I'm inspired of the frag and vertex from https://github.com/watab0shi/taotajimajp-transition

For this, I work with Angular and CurtainsJS with GSAP.

My problem is the first textures hides the video at the end of animation (more details at end of this post)

I created a TS class after my component (home.page.ts):

export class WebglHover {
  webGLCurtain: any;
  canvas: any;
  planeElement: any;
  mouse: any;
  params: any;
  plane: any;

  constructor(set) {
    this.canvas = set.canvas;
    this.webGLCurtain = new Curtains({
      container: this.canvas,
      watchScroll: false,
      pixelRatio: Math.min(1.5, window.devicePixelRatio),
    });
    this.planeElement = set.planeElement;
    this.mouse = {
      x: 0,
      y: 0,
    };
    this.params = {
      vertexShader: document.getElementById("plane-vs").textContent,
      fragmentShader: document.getElementById("plane-fs").textContent,
      widthSegments: 40,
      heightSegments: 40, // 40*40*6 = 9600 vertices
      uniforms: {
        time: {
          name: "uTime",
          type: "1f",
          value: 0,
        },
        mousepos: {
          name: "uMouse",
          type: "2f",
          value: [0, 0],
        },
        resolution: {
          name: "uReso",
          type: "2f",
          value: [innerWidth, innerHeight],
        },
        progress: {
          name: "uProgress",
          type: "1f",
          value: 0,
        },
        acceleration: {
          name: "uAccel",
          value: [0.5, 2.0],
          type: "2f"
        }
      },
    };
    this.initPlane();
  }

  initPlane() {
    this.plane = new Plane(this.webGLCurtain, this.planeElement, this.params);

    this.plane.setScale(1, 1);

    if (this.plane) {
      this.plane.onReady(() => {
        this.update();
        this.initEvent();
      });
    }
  }

  update() {
    this.plane.onRender(() => {
      this.plane.uniforms.time.value += 0.01;

      this.plane.uniforms.resolution.value = [innerWidth, innerHeight];
    });
  }

  resize() {
      // this.plane.resize();
      // this.plane.updatePosition();

      // this.plane.resetPlane(this);
      // this.plane.setPerspective(50, 0.1, 150)
  }

  initEvent() {

    this.planeElement.addEventListener("mouseenter", () => {
      TweenLite.to(this.plane.uniforms.progress, 0.8, {
        value: 1,
      });
    });

    this.planeElement.addEventListener("mouseout", () => {
      TweenLite.to(this.plane.uniforms.progress, 0.8, {
        value: 0,
      });
    });

    document.body.addEventListener("resize", () => {
      this.resize();
    });
  }

And I use it like in my component before WebGlHover (home.page.ts):

  ngAfterViewInit(): void {
    window.onload = () => {
      setTimeout(() => {
        document.querySelectorAll(".list_items").forEach((slide) => {
          const canvas = slide.querySelector(".canvas");
          const planeElement = slide.querySelector(".plane");
          new WebglHover({
            canvas: canvas,
            planeElement: planeElement,
          });
        });
      });
    };
  }

The template (home.page.html)

  <main class="list_items">
    <section class="item">
      <div class="canvas"></div>
      <div class="plane">
        <img
          data-sampler="texture0"
          id="texture0"
          src="/assets/img/the-9d-project.jpg"
          crossorigin="anonymous"
        />
        <video
          id="video texture1"
          data-sampler="texture1"
          loop
          autoplay
          muted
          [controls]="false"
          preload="auto"
          data-setup='{ "controls": false, "autoplay": true, "preload": "auto", "loop":true }'
        >
          <source
            src="/assets/Main Sequence-1.webm"
            type="video/webm"
          />
        </video>
      </div>
      <div class="slide__content">
        <p>Lorem ipsum dolor sit.</p>
      </div>
    </section>
  </main>

Vertex and shader (index.html)

<!-- vertex shader -->
 <script id="plane-vs" type="x-shader/x-vertex">
  precision mediump float;

  // 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;

  uniform mat4 texture0Matrix;
  uniform mat4 texture1Matrix;
  uniform mat4 mapMatrix;
  uniform float uFixAspect;

  // if you want to pass your vertex and texture coords to the fragment shader
  varying vec3 vVertexPosition;
  varying vec2 vTextureCoord0;
  varying vec2 vTextureCoord1;
  varying vec2 vTextureCoordMap;

  void main() {
    vec3 vertexPosition = aVertexPosition;

    gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);

    // set the varyings
    vTextureCoord0 = (texture0Matrix * vec4(aTextureCoord, 0., 1.)).xy;
    vTextureCoord1 = (texture1Matrix * vec4(aTextureCoord, 0., 1.)).xy;
    vVertexPosition = vertexPosition;
  }
</script>
<script id="plane-fs" type="x-shader/x-fragment">
  precision mediump float;

  uniform float uTime;
  uniform float uProgress;
  uniform vec2 uReso;
  uniform vec2 uMouse;
  uniform vec2 uAccel;
  
  // get our varyings
  varying vec3 vVertexPosition;
  varying vec2 vTextureCoord0;
  varying vec2 vTextureCoord1;
  varying vec2 vTextureCoordMap;

  // the uniform we declared inside our javascript

  // our texture sampler (default name, to use a different name please refer to the documentation)
  uniform sampler2D texture0;
  uniform sampler2D texture1;
  uniform sampler2D map;

  vec2 translateDirection = vec2( -.5, 1. );

  vec2 mirrored( vec2 v ) {
    vec2 m = mod( v, 2. );
    return mix( m, 2. - m, step( 1., m ) );
  }
  
  float tri( float p ) {
    return mix( p, 1. - p, step( .5, p ) ) * 2.;
  }

  void main(){
    vec2 uv = vTextureCoord0; 

    float progress0 = uProgress;
    float progress1 = 1. - uProgress;

    float pct = fract( uProgress );

    float delayValue = ( ( pct * 7. ) - ( uv.y * 2. ) + uv.x ) - 2.;
    delayValue = clamp( delayValue, 0., 1. );
  
    vec2 translate = pct + delayValue * uAccel;
    vec2 translate0 = translateDirection * translate;
    vec2 translate1 = translateDirection * ( translate - 1. - uAccel );
  
    vec2 w = sin( sin( uTime ) * vec2( 0., 0.3 ) + uv.yx * vec2( 0., 4. ) ) * vec2( 0., .5 );
    vec2 xy = w * ( tri( pct ) * .5 + tri( delayValue ) * .5 );

    vec2 uv0 = vTextureCoord1 + translate0 + xy;
    vec2 uv1 = vTextureCoord1 + translate1 + xy;
  
    vec3 color0 = texture2D( texture0, mirrored( uv0 ) ).rgb;
    vec3 color1 = texture2D( texture1, mirrored( uv1 ) ).rgb;
  
    vec3 color = mix( color0, color1, delayValue );
  
    gl_FragColor = vec4( color, 1. );        
  }
</script>

So, when I hover an item, the animation works, but at end, the video (texture1) is replaced by the first image (texture0) and I don't understand why.

Result: https://gyazo.com/115974a0920894ee113cb8a743587dea

CodeSandbox link: https://codesandbox.io/s/divine-leftpad-wdf41?file=/src/app/app.component.ts

Someone can explain me what I'm doing wrong?

Peter O.
  • 32,158
  • 14
  • 82
  • 96
F3LENYR
  • 125
  • 1
  • 7
  • Please make a [minimal, complete, and verifiable example](https://stackoverflow.com/help/minimal-reproducible-example). – Zach Saucier Jan 24 '21 at 19:49
  • Example : https://codesandbox.io/s/divine-leftpad-wdf41?file=/src/app/app.component.ts – F3LENYR Jan 24 '21 at 22:03
  • Martin answered this a couple of days ago but deleted his answer for some reason... Try `vec3 color = mix( color0, color1, uProgress );`. Also we at GreenSock *highly* recommend the [GSAP 3 formatting](https://greensock.com/3-migration/). – Zach Saucier Jan 25 '21 at 15:35
  • Hey Zach, thanks for your response. I've tried before what you said, but : the video works really well at the start, but just 2 sec after the video is rotating at her opposite (mirroring or rotating.. i don't know) for no reason You can observe this if you edit the sandbox with what you said. – F3LENYR Jan 25 '21 at 16:08

1 Answers1

2

There are a couple errors in your fragment shader.

  • You're using the fractional value of your uProgress uniform, but when uProgress equals to 1, its fractional value equals to 0 and your mix operation then falls back to color0 again.
  • Your translate1 value needs to be multiplied by progress1 so when uProgress equals 1, the second texture is not translated anymore.

This fragment shader should fix your issue:

precision mediump float;

uniform float uTime;
uniform float uProgress;
uniform vec2 uReso;
uniform vec2 uMouse;
uniform vec2 uAccel;

// get our varyings
varying vec3 vVertexPosition;
varying vec2 vTextureCoord0;
varying vec2 vTextureCoord1;
varying vec2 vTextureCoordMap;

// the uniform we declared inside our javascript

// our texture sampler (default name, to use a different name please refer to the documentation)
uniform sampler2D texture0;
uniform sampler2D texture1;
uniform sampler2D map;

vec2 translateDirection = vec2( -.5, 1. );

vec2 mirrored( vec2 v ) {
    vec2 m = mod( v, 2. );
    return mix( m, 2. - m, step( 1., m ) );
}

float tri( float p ) {
    return mix( p, 1. - p, step( .5, p ) ) * 2.;
}

void main(){
    vec2 uv = vTextureCoord0;

    float progress0 = uProgress;
    float progress1 = 1. - uProgress;

    float pct = fract( uProgress );

    float delayValue = ( ( uProgress * 7. ) - ( uv.y * 2. ) + uv.x ) - 2.;
    delayValue = clamp( delayValue, 0., 1. );

    vec2 translate = pct + delayValue * uAccel;
    vec2 translate0 = translateDirection * translate;
    vec2 translate1 = translateDirection * ( translate - 1. - uAccel ) * progress1;

    vec2 w = sin( sin( uTime ) * vec2( 0., 0.3 ) + uv.yx * vec2( 0., 4. ) ) * vec2( 0., .5 );
    vec2 xy = w * ( tri( delayValue ) * .5 + tri( delayValue ) * .5 );

    vec2 uv0 = vTextureCoord1 + translate0 + xy;
    vec2 uv1 = vTextureCoord1 + translate1 + xy;

    vec3 color0 = texture2D( texture0, mirrored( uv0 ) ).rgb;
    vec3 color1 = texture2D( texture1,  uv1 ).rgb;

    vec3 color = mix( color0, color1, delayValue );

    gl_FragColor = vec4( color, 1. );
}

Here's an updated codesandbox: https://codesandbox.io/s/cranky-fog-sfd0n?file=/src/index.html

Hope that suits your needs,

Martin Laxenaire
  • 135
  • 1
  • 11