Generate a visual distortion effect using WebGL and three.js

I'm currently learning three.js and experimenting with image transformations.

After seeing a cool effect demonstrated here, I'm interested in replicating it.

Can anyone provide guidance on the steps to achieve similar image transformations?

So far, I've set up a loader:

// instantiate a loader
var loader = new THREE.TextureLoader();

// load a resource
loader.load(
    // resource URL
    'clouds.jpg',
    // Function when resource is loaded
    function (texture) {
        init(new THREE.MeshBasicMaterial({
            map: texture
        }));
    },
    // Function called when download progresses
    function (xhr) {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
    },
    // Function called when download errors
    function (xhr) {
        console.log('An error happened');
    }
);

var init = function(material) {
    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    var rectLength = window.innerHeight;
    var rectWidth = window.innerWidth;
    var rectShape = new THREE.Shape();
    rectShape.moveTo(0,0);
    rectShape.lineTo(0, rectWidth);
    rectShape.lineTo(rectLength, rectWidth);
    rectShape.lineTo(rectLength, 0);
    rectShape.lineTo(0, 0);
    var geometry = new THREE.ShapeGeometry(rectShape);
    var cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    camera.position.z = 1;

    var render = function () {
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    };

    render();
}

Currently, the image renders as a flat 2D shape but I'm unsure how to implement effects within the render function.

Would it be better to handle this in plain WebGL?

Any advice, tips, or tutorials would be greatly appreciated!

Answer №1

Exploring the potential in WebGL, you can achieve similar results as with 2D canvas but with added possibilities for more intricate warping effects on a per-pixel basis rather than just per-line.

var img = new Image();
img.onload = start;
img.src = "https://i.imgur.com/v38pV.jpg";

function start() {

  var canvas = document.querySelector("canvas");
  var ctx = canvas.getContext("2d");

  function mix(a, b, l) {
    return a + (b - a) * l;
  }
  
  function upDown(v) {
    return Math.sin(v) * 0.5 + 0.5;
  }
  
  function render(time) {
    time *= 0.001;

    resize(canvas);

    var t1 = time;
    var t2 = time * 0.37;

    // for each line in the canvas
    for (var dstY = 0; dstY < canvas.height; ++dstY) {
      
      // v is value that goes 0 to 1 down the canvas
      var v = dstY / canvas.height;
      
      // compute some amount to offset the src
      var off1 = Math.sin((v + 0.5) * mix(3, 12, upDown(t1))) * 300;
      var off2 = Math.sin((v + 0.5) * mix(3, 12, upDown(t2))) * 300;
      var off = off1 + off2;
      
      // compute what line of the source image we want
      // NOTE: if off = 0 then it would just be stretching
      // the image down the canvas.
      var srcY = dstY * img.height / canvas.height + off;
      
      // clamp srcY to be inside the image
      srcY = Math.max(0, Math.min(img.height - 1, srcY));

      // draw a single line from the src to the canvas
      ctx.drawImage(
        img, 
        0, srcY, img.width, 1, 
        0, dstY, canvas.width, 1);
    }    
    
    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);

  function resize(canvas) {
    var width = canvas.clientWidth;
    var height = canvas.clientHeight;
    if (width != canvas.width || height != canvas.height) {
      canvas.width = width;
      canvas.height = height;
    }
  }
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>

The technique employed here seems to take inspiration from slit scan, although the exact formula remains unspecified at this point.

While Three.js could also handle such effects well, using WebGL directly allows for greater control and customization without the overhead of a large library for a relatively simple effect.

Below is an alternative version utilizing twgl:

var vs = `
attribute vec4 position;

varying vec2 v_texcoord;

void main() {
  gl_Position = position;
  v_texcoord = position.xy * .5 + .5;
}
`;

// Fragment shader code for one effect
var fs1 = `
precision mediump float;

uniform float time;
uniform sampler2D tex;

varying vec2 v_texcoord;

float upDown(float v) {
  return sin(v) * 0.5 + 0.5;
}

void main() {
  float t1 = time;
  float t2 = time * 0.37;

... (Continued) ...
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
.top { position: absolute; left: 5px; top: 5px; color: white; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<canvas></canvas>
<div class="top">click to switch effects</div>

Answer №2

Opting for WebGL Shaders is undoubtedly the top choice for achieving a similar effect as mentioned. It would be beneficial to explore Texture Shader demonstrations or simply take a look at this example in Three.js by following this link.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The error message indicates that the Worklight adapter is an object, not a function

Upon deploying the Worklight adapter to the production server, I encountered an error when the adapter called a Java code from JavaScript: Procedure invocation error. Ecma Error: TypeError: Cannot call property updateProposal in object [JavaPackage com.id ...

XMLHTTP is not accessible in the Department of Health

My D.O.H framework is powered by nodejs, specifically version 1.10. While I understand that nodejs typically uses xmlhttprequest or other modules for XHR requests, in my scenario, I am opting to utilize Dojo's xhr instead. Unfortunately, it seems tha ...

Adjust picture dimensions when the window changes (using jQuery)

Hey there, I'm in need of some help resizing images on my webpage. I want the images to adjust their size when the page loads or when the browser is resized. The images can be either portrait or landscape, and they will be contained within a div that ...

How to disable specific multiple dates in Material-UI datepickers?

Is there a way to disable specific dates on the calendar, not just past or future dates? I want to be able to choose which dates are disabled, such as June 29th, June 27th, and July 4th. I know you can use 'shouldDisableDates' to achieve this, b ...

Instructions on how to submit a form using a button while also clicking on an anchor link simultaneously

I have a form with a button to submit it. Now, I want to pass the same variables to another page in order to insert them into a database. The approach I'm considering is passing the variables using an anchor link with GET parameters. However, I' ...

What are the possible arguments for the event type onInput?

While diving into the world of html / javascript / vue, I stumbled upon the following code snippet. <input type="text" onInput="doAction(event);"> <script> var mesdata = { message: 'type your m ...

Avoid form submission when the 'enter' key is pressed in Edge, but not in Chrome or Firefox

I'm dealing with an issue in HTML where a 'details' tag is set to open and close when the user presses enter. However, on Edge browser, pressing enter on the 'details' tag actually submits the form. I've been tasked with preve ...

Divide the string into two halves and display them in separate div elements

I am in need of a code that can split a string in half. For instance, if the inputted name is "Trishy", which consists of 6 letters, the first 3 letters should be placed into the second li within the <ul class="hardcover_front">, while the remaining ...

Tips for changing the TextField variant when it receives input focus and keeping the focus using Material-UI

When a user focuses on the input, I'd like to change the variant of the TextField. The code snippet below accomplishes this, but the input loses focus. This means the user has to click again on the input to focus and start typing. import React, { useS ...

Issue: A problem has arisen with the element type, as it was supposed to be a string for built-in components but is currently undefined. This error is being encountered

Help needed: Error in my code! I am working with three files: HeaderOption.js, HeaderOptions.js, Search.js This is the content of HeaderOptions.js import HeaderOption from "./HeaderOption"; import DotsVerticalIcon from "@heroicons/react/o ...

Having trouble with the addEventListener function not working on a vanilla JS form?

I'm struggling to get a form working on a simple rails project. The idea is to collect a name and date from the form and display them on the same page using JS. Later, I'll process this information to determine if it's your birthday and cal ...

When entering a sub-URL into the browser address bar, the routes do not display as expected

I am encountering an issue with navigating to different routes within my React application. While the home route is visible and functions properly when I start the app locally (npm run dev), I am unable to access other routes. No errors are displayed in t ...

What is the most efficient method for managing axios errors in Laravel and Vue.js?

Currently, I am developing spa web applications using Laravel as the backend and Vue.js as the frontend framework. For authentication with API, I am utilizing Laravel Passport, and for managing my application state, I am using Vuex. Initially, I created A ...

Limit the v-text-field input to only accept a single digit

I am currently working on creating an SMS verification code component using a series of v-text-field components. My goal is to limit the input to just a single digit. <v-text-field v-for="(num, key) of code" :key=" ...

Error in mandatory data required by the Screenleap API in JavaScript

I have a JSON encoded data stored in a variable called $json. Here is how it appears: I have referred to the documentation at "https://www.screenleap.com/api/presenter" ...

Is it possible to enclose a form inside a Bootstrap popover?

<div class="container"> <div class="row" style="padding-top: 240px;"> <a href="#" class="btn btn-large btn-primary" rel="popover" data-content="<form ...

Issue: attempting to write after the end of a node

Currently, I am using the stream() function from https://www.npmjs.com/package/cassandra-driver to read data from Cassandra. I am listening for events and piping the stream to the response object; however, I encountered an error: Error: write after end T ...

Unusual shadow cast by the box's silhouette

I am currently facing an issue with a box and its shadow. When I close the box, a different shadow lingers behind. I have tried troubleshooting this problem but cannot pinpoint the source. I have included the relevant code files in the specified folders. I ...

The issue of Ejs partial header and footer file only functioning in one file and not the other (both at the same directory level)

I'm in the process of setting up a view page for a post on the main page of a website. Both pages share the same header and footer files, located at the same directory level. However, while one page is functioning correctly, the other is not. The erro ...

Errors slipping through the cracks of Express middleware

When it comes to using express and typescript, I am attempting to create a middleware for error handling similar to Sentry. Below is the code snippet: const catchError = ( error: Error, req: Request, _res: Response, next: any ) => { console. ...