0

My objective is to utilize curtainjs to apply a distortion and scale effect along the Y axis to a plane and its texture, with the added specification of incorporating rounded edges on the plane. However, I am not sure of how to execute this modification without disrupting the rest of the script, and thus, preventing the successful execution of the intended distortion and scaling effects, so it can distort and scale properly.

  • Presented below is the original script of curtainjs, which implements a scale effect along the Y axis on both the plane and its texture :
import {Curtains, Plane, ShaderPass, Vec2, Vec3} from 'curtainsjs';

window.addEventListener("load", () => {

    let planeDrawn = 0;
    const debugElement = document.getElementById("debug-value");

    const curtains = new Curtains({
        container: "canvas",
        antialias: false, 
        pixelRatio: Math.min(1.5, window.devicePixelRatio) 
    });

    curtains.onRender(() => {

        scrollEffect = curtains.lerp(scrollEffect, 0, 0.05);


        debugElement.innerText = planeDrawn;
    }).onScroll(() => {

        const delta = curtains.getScrollDeltas();


        delta.y = -delta.y;

        if(delta.y > 60) {
            delta.y = 60;
        }
        else if(delta.y < -60) {
            delta.y = -60;
        }

        if(Math.abs(delta.y) > Math.abs(scrollEffect)) {
            scrollEffect = curtains.lerp(scrollEffect, delta.y, 0.5);
        }


        for(let i = 0; i < planes.length; i++) {

            applyPlanesParallax(i);
        }

    }).onError(() => {

        document.body.classList.add("no-curtains", "planes-loaded");
    }).onContextLost(() => {

        curtains.restoreContext();
    });

    const planes = [];
    let scrollEffect = 0;

    const planeElements = document.getElementsByClassName("plane");

    const vs = `
        precision mediump float;
    
        // default mandatory variables
        attribute vec3 aVertexPosition;
        attribute vec2 aTextureCoord;
        attribute vec3 aVertexNormal; // HERE CHANGES
    
        uniform mat4 uMVMatrix;
        uniform mat4 uPMatrix;
    
        uniform mat4 planeTextureMatrix;
    
        // custom variables
        varying vec3 vVertexPosition;
        varying vec2 vTextureCoord;
        varying vec3 vVertexNormal; // HERE CHANGES
    
        void main() { 
            gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
    
            // varyings
            vVertexPosition = aVertexPosition;
            vVertexNormal = aVertexNormal; // HERE CHANGES
            vTextureCoord = (planeTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
        }
    `;

    const fs = `
        precision mediump float;
    
        varying vec3 vVertexPosition;
        varying vec2 vTextureCoord;
        varying vec3 vVertexNormal; // HERE CHANGES
    
        uniform sampler2D planeTexture;
    
        void main() {
            // just display our texture
            float edge = smoothstep(0.4, 0.5, length(vVertexNormal)); // HERE CHANGES
            gl_FragColor = texture2D(planeTexture, vTextureCoord);
        }
    `;

    for(let i = 0; i < planeElements.length; i++) {
        const plane = new Plane(curtains, planeElements[i], {
            vertexShader: vs,
            fragmentShader: fs,
        }); 

        planes.push(plane);

        handlePlanes(i);
    }



    function handlePlanes(index) {
        const plane = planes[index];

        plane.onReady(() => {
        
            planeDrawn++;


            applyPlanesParallax(index);


            if(index === planes.length - 1) {
                document.body.classList.add("planes-loaded");
            }
        }).onAfterResize(() => {
      
            applyPlanesParallax(index);
        }).onRender(() => {

         
            plane.scale.y = 1 + Math.abs(scrollEffect) / 300;
            plane.textures[0].scale.y = 1 + Math.abs(scrollEffect) / 150;
        }).onReEnterView(() => {
  
            planeDrawn++;
        }).onLeaveView(() => {

            planeDrawn--;
        });
    }

    function applyPlanesParallax(index) {


        const windowHeight = curtains.getBoundingRect().height / 1.2;

        const planeBoundingRect = planes[index].getBoundingRect();
        const planeOffsetTop = planeBoundingRect.top + planeBoundingRect.height / 2;

        const parallaxEffect = (planeOffsetTop - windowHeight / 2) / windowHeight;

 
        planes[index].relativeTranslation.y = parallaxEffect * windowHeight / 4;
    }


    // post processing
    const firstFs = `
        precision mediump float;
    
        varying vec3 vVertexPosition;
        varying vec2 vTextureCoord;
    
        uniform sampler2D uRenderTexture;
    
        uniform float uScrollEffect;
    
        void main() {
            // invert colors
            vec4 scene = texture2D(uRenderTexture, vTextureCoord);
            vec4 invertedColors = texture2D(uRenderTexture, vTextureCoord);
    
            if(
                vTextureCoord.x > 0.625 && vTextureCoord.x < 0.875 && vTextureCoord.y > 0.625 && vTextureCoord.y < 0.875
                || vTextureCoord.x > 0.125 && vTextureCoord.x < 0.375 && vTextureCoord.y > 0.125 && vTextureCoord.y < 0.375
            ) {
                invertedColors.rgb = vec3(1.0 - invertedColors.rgb);
            }
    
            vec4 finalColor = mix(scene, invertedColors, abs(uScrollEffect) / 60.0);
    
            gl_FragColor = finalColor;
        }
    `;

    const firstShaderPassParams = {
        fragmentShader: firstFs, 
        uniforms: {
            scrollEffect: {
                name: "uScrollEffect",
                type: "1f",
                value: 0,
            },
        },
    };

    const firstShaderPass = new ShaderPass(curtains, firstShaderPassParams);
    firstShaderPass.onRender(() => {

        firstShaderPass.uniforms.scrollEffect.value = scrollEffect;
    });


    const secondFs = `
        #ifdef GL_ES
        precision mediump float;
        #endif
    
        varying vec3 vVertexPosition;
        varying vec2 vTextureCoord;
    
        uniform sampler2D uRenderTexture;
    
        uniform float uScrollEffect;
    
        void main() {
            vec2 textureCoords = vTextureCoord;
            vec2 texCenter = vec2(0.5, 0.5);
    
            // distort around scene center
            textureCoords += vec2(texCenter - textureCoords).xy * sin(distance(texCenter, textureCoords)) * uScrollEffect / 175.0;
    
            gl_FragColor = texture2D(uRenderTexture, textureCoords);
        }
    `;

    const secondShaderPassParams = {
        fragmentShader: secondFs, 
        uniforms: {
            scrollEffect: {
                name: "uScrollEffect",
                type: "1f",
                value: 0,
            },
        },
    };

    const secondShaderPass = new ShaderPass(curtains, secondShaderPassParams);
    secondShaderPass.onRender(() => {
        secondShaderPass.uniforms.scrollEffect.value = scrollEffect;
    });
});

Simple explanation of what the script is doing :

The script starts by importing the necessary modules from the Curtains.js library, including Curtains, Plane, ShaderPass, Vec2, and Vec3. Then it adds an event listener to the window object that triggers a function on the load event.

Inside the load event function, the script initializes the Curtains object by passing a configuration object to it, which includes a reference to the container element where the WebGL canvas should be created, as well as some optional parameters like antialias and pixelRatio.

After that, the script defines several event handlers that are used to handle rendering, scrolling, errors, and context loss events.

Next, the script creates several Plane objects using the Plane constructor of the Curtains library, passing a configuration object to it that includes various options for the plane's appearance and behavior, including vertex and fragment shaders, geometry, width and height segments, and uniforms.

The script also defines two custom shaders, one for the vertex shader and one for the fragment shader. These shaders are written in GLSL, a language used to write graphics shaders, and define how the vertices and fragments of the 3D objects should be processed.

Finally, the script defines several functions that are used to handle various events and apply parallax and distortion scrolling effects to the planes.

CLICK here to see the result

  • To make the plane has rounded edges tried to replace the plane with an other geometry using a simple function like this one :
const w = 16;   // width
    const h = 9;    // height
    const r = 2;    // radius corner
    const s = 18;   // smoothness

    // helper const's
    const wi = w / 2 - r;
    const hi = h / 2 - r;
    const w2 = w / 2;
    const h2 = h / 2;
    const ul = r / w;
    const ur = ( w - r ) / w;
    const vl = r / h;
        const vh = ( h - r ) / h;

    let positions = [

        -wi, -h2, 0,  wi, -h2, 0,  wi, h2, 0,
        -wi, -h2, 0,  wi,  h2, 0, -wi, h2, 0,   
        -w2, -hi, 0, -wi, -hi, 0, -wi, hi, 0,
        -w2, -hi, 0, -wi,  hi, 0, -w2, hi, 0,   
         wi, -hi, 0,  w2, -hi, 0,  w2, hi, 0,
         wi, -hi, 0,  w2,  hi, 0,  wi, hi, 0
     
    ];

    let uvs = [
    
        ul,  0, ur,  0, ur,  1,
        ul,  0, ur,  1, ul,  1,
         0, vl, ul, vl, ul, vh,
         0, vl, ul, vh,  0, vh,
        ur, vl,  1, vl,  1, vh,
        ur, vl,  1, vh, ur, vh 
    
    ];

    let phia = 0; 
    let phib, xc, yc, uc, vc;

    for ( let i = 0; i < s * 4; i ++ ) {

        phib = Math.PI * 2 * ( i + 1 ) / ( 4 * s );
    
    
        xc = i < s || i >= 3 * s ? wi : - wi;
        yc = i < 2 * s ? hi : -hi;

        positions.push( xc, yc, 0, xc + r * Math.cos( phia ), yc + r * Math.sin( phia ), 0,  xc + r * Math.cos( phib ), yc + r * Math.sin( phib ), 0 );
    
        uc = xc = i < s || i >= 3 * s ? ur : ul;
        vc = i < 2 * s ? vh : vl;
    
        uvs.push( uc, vc, uc + ul * Math.cos( phia ), vc + vl * Math.sin( phia ), uc + ul * Math.cos( phib ), vc + vl * Math.sin( phib ) );
    
        phia = phib;
        
    }
    const geometry = new THREE.BufferGeometry( );
    geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) );
    geometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );

However, upon replacing the original plane with the rounded edges geometry, the script failed to execute and the intended effect was not achieved.

So, does anyone know how can i make the edges of the plane rounded while still keeping the distortion and scale effects of curtainjs working ?

  • Maybe I'm misunderstanding what you want, but do you simply want the corners of the images to be rounded, such that the plane will behave exactly as it does in the demo, just with round corners? If so, can't you make the textures have rounded corners with alpha values? – TheJim01 Mar 30 '23 at 02:44
  • Thank you for your message. Your understanding is correct; my objective is to create rounded corners on the images while maintaining the existing functionality of the curtainjs plane. The technique you suggested, which involves applying alpha values to the texture, is one potential solution to achieving this effect. – Michel dev Mar 30 '23 at 18:54
  • So here is what i tried to do : `fs: // apply round corners with alpha values vec4 texColor = texture2D(planeTexture, vTextureCoord); float radius = 0.05; float alpha = smoothstep(0.0, radius, vTextureCoord.x) * smoothstep(0.0, radius, 1.0 - vTextureCoord.x) * smoothstep(0.0, radius, vTextureCoord.y) * smoothstep(0.0, radius, 1.0 - vTextureCoord.y); texColor.a *= alpha; gl_FragColor = texColor;` Does this code fully achieve the desired result, or is there anything else that needs to be added or modified to make it work as intended? – Michel dev Mar 30 '23 at 18:56
  • Upon testing, I discovered that the script failed to execute as intended. Rather than rounding the edges of the plane, it applied the rounding effect to the entire texture. I am currently seeking guidance on how to modify the alpha values of the texture to produce rounded edges and thereby achieve the intended visual effect of a rounded edges plane ? – Michel dev Mar 30 '23 at 22:32
  • You may want to try modifying the image BEFORE applying it as a texture. You could use a 2D canvas to apply an alpha overlay on the image, and then use a [`CanvasTexture`](https://threejs.org/docs/#api/en/textures/CanvasTexture) as your texture. – TheJim01 Apr 05 '23 at 14:53
  • Thank you for the suggestion, TheJim01. I will give it a try and modify the image before using it as a texture. I will use a 2D canvas to apply an alpha overlay on the image and then use a CanvasTexture as the texture. I appreciate your help and expertise on this matter. – Michel dev Apr 08 '23 at 06:46

0 Answers0