How can I mirror just one side of a texture in Three.js / WebGL?

I am attempting to create a kaleidoscopic effect using only one side, but I have a large number of Points and would like the effect to be achieved within the shader. If there is a Threejs trick that can mirror half of the texture or the Points object, that would be ideal. Despite trying transformation matrices, I have been unsuccessful in making it work.

I came across an old KaleidoShader that requires the use of EffectComposer, but I am interested in manually implementing it myself (without EffectComposer) and am facing challenges in doing so. I am utilizing an FBO and attempted to incorporate the code from the shader into both my simulation and render shaders, but to no avail. Do I need to add another FBO texture or is it possible to perform these calculations within one of the existing shaders?

For a visual aid, refer to this image.

I have dedicated considerable time to this without reaching a resolution, so any guidance in the right direction would be greatly appreciated.

Thank you.

Answer №1

I recently came across this informative article

Copying and pasting the code provided in that repository appears to be effective

body {
  margin: 0;
}
#c {
  width: 100vw;
  height: 100vh;
  display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
import {EffectComposer} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/postprocessing/EffectComposer.js';
import {RenderPass} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/postprocessing/RenderPass.js';
import {ShaderPass} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/postprocessing/ShaderPass.js';
import {GUI} from 'https://threejsfundamentals.org/threejs/../3rdparty/dat.gui.module.js';

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 2;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    scene.add(light);
  }

  const boxWidth = 1;
  const boxHeight = 1;
  const boxDepth = 1;
  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

  function makeInstance(geometry, color, x) {
    const material = new THREE.MeshPhongMaterial({color});

    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    cube.position.x = x;

    return cube;
  }

  const cubes = [
    makeInstance(geometry, 0x44aa88,  0),
    makeInstance(geometry, 0x8844aa, -2),
    makeInstance(geometry, 0xaa8844,  2),
  ];

  const composer = new EffectComposer(renderer);
  composer.addPass(new RenderPass(scene, camera));

  // from:
  // https://github.com/mistic100/three.js-examples/blob/master/LICENSE
  const  kaleidoscopeShader = {
    uniforms: {

      "tDiffuse": { value: null },
      "sides":    { value: 6.0 },
      "angle":    { value: 0.0 }

    },

    vertexShader: `

      varying vec2 vUv;

      void main() {

        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

      }

    `,

    fragmentShader: `

      uniform sampler2D tDiffuse;
      uniform float sides;
      uniform float angle;
      
      varying vec2 vUv;

      void main() {

        vec2 p = vUv - 0.5;
        float r = length(p);
        float a = atan(p.y, p.x) + angle;
        float tau = 2. * 3.1416 ;
        a = mod(a, tau/sides);
        a = abs(a - tau/sides/2.) ;
        p = r * vec2(cos(a), sin(a));
        vec4 color = texture2D(tDiffuse, p + 0.5);
        gl_FragColor = color;

      }
    ` 
  };

  const kaleidoscopePass = new ShaderPass(kaleidoscopeShader);
  kaleidoscopePass.renderToScreen = true;
  composer.addPass(kaleidoscopePass);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  const gui = new GUI();
  gui.add(kaleidoscopePass.uniforms.sides, 'value', 0, 20).name('sides');
  gui.add(kaleidoscopePass.uniforms.angle, 'value', 0, 6.28, 0.01).name('angle');
  
  let then = 0;
  function render(now) {
    now *= 0.001;  // convert to seconds
    const deltaTime = now - then;
    then = now;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
      composer.setSize(canvas.width, canvas.height);
    }

    cubes.forEach((cube, ndx) => {
      const speed = 1 + ndx * .1;
      const rot = now * speed;
      cube.rotation.x = rot;
      cube.rotation.y = rot;
    });

    composer.render(deltaTime);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
</script>

Answer №2

If you need a texture wrap mode that performs mirroring, you can set

texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping
. This should help achieve the desired effect.

For a demonstration of how mirrored repeat wrapping works on both axes, check out this example:

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

Using CSS nth-of-type on the same level of the DOM allows for specific

I was having trouble using the nth-of-type(2n+1) selector to target odd and even rows in the scenario below. What I wanted was for the nth-of-type selector to apply different styles to the odd rows with the classes "row-data row-header" and "row-data row-c ...

Ways to obtain the ID of the following element using jQuery

Here is some HTML code, and I am trying to retrieve the ID of the next element from a clicked element with the class name "coba" or to get the ID of an element with the class name "coba2." <div class="dropdown"> <label>Category</label> ...

Exploring SQL Components with JavaScript

Here is the code I am currently using: //This function handles all games and their attributes function handleGames(){ sql.query('SELECT id FROM games', function (err, rows){ if(err){ console.log(String(err).error.bgWhite) ...

Developing a sliding menu with AngularJS

Currently, I am developing an AngularJS application. One of the features I am working on involves having a menu at the top of my page that, when an item is selected, will slide down to reveal content specific to that selection in the same area as the menu. ...

How to implement a "callWhenReady" function in JavaScript

I am new to javascript and currently working on restructuring my prototype code. The current setup involves multiple levels of nested callbacks, making it difficult to read and manage. I am aiming for a cleaner approach similar to the following: GoogleMap ...

The jQuery $.change function is not functioning properly when used with a cloned select element

In my table, there is a button that allows you to add a new row by cloning the last one. Each row contains a <select> with a different name (0-9), all having the class .variable_priority. When clicking the button, the row clones successfully and the ...

Encountering an issue with the node.js express server when fetching data

I'm running into an issue with the fetch function and node.js. When a button is clicked on my frontend, I want to send a post request to receive an array from my backend as a response. My backend is built using node.js with express, and I'm using ...

Tips for sending a state into the gtm.js function?

As an intern at a startup, I am the sole front-end developer responsible for coding a website in Next.js. My boss has requested that I incorporate Google Tag Manager into the project. Following the example provided by Next on their GitHub page, I have succ ...

There seems to be an issue with Node.js/Express: the page at /

Recently, I've been working on some code (specifically in app.js on the server). console.log("Server started. If you're reading this then your computer is still alive."); //Just a test command to ensure everything is functioning correctly. var ...

Having trouble executing "npm install" following the clone from GitHub in React

After cloning a repository from GitHub, I attempted to run "npm install" but encountered the following error: https://i.sstatic.net/QM4RZ.png Since the project is still in development, should I install or add anything else to successfully run it? ...

Creating dynamic elements in JavaScript and assigning them unique IDs

Hi there, I'm currently working on a project that requires generating dynamic divs with a textbox and delete button inside each one. The challenge I'm facing is figuring out how to assign a unique ID to each textbox element so that I can properly ...

Is it possible to save ng-bind values into a PHP string?

As a newcomer to AngularJS with some PHP knowledge, I am curious about the possibility of storing ng-bind values in a PHP string. For instance: <li ng-if="jSEO.resources[3][0][0]"> <span> Internal Links:</span> <font color="#000000" ...

What could be causing the drop-down values to fail to be saved?

I'm dealing with an address object that has nested objects for both permanent and postal addresses. Despite successfully saving the values of input boxes in a large form, I'm facing an issue with not being able to save the dropdown (select) value ...

Tips for concealing one modal and revealing a different one upon page refresh

I am facing an issue with my modal form. What I want is for the modal to close upon submitting the form, then for the page to reload, and finally for the success modal to be displayed. However, when I clicked submit, the success modal ended up appearing o ...

The default skin of video.js is disrupted by a 16:8 ratio

Incorporating videojs into my react application has presented a challenge. I have enclosed the video player in a div set to a 16:8 ratio, but unfortunately the default skin does not display properly. On the other hand, when I implement code that includes t ...

Tips for adapting my custom input component for compatibility with vee-validate?

I recently developed an input component for my Vue project and integrated it within my forms. My aim is to implement vee-validate for validating the inputs. Initially, I attempted to validate my component like any regular input field. However, upon encoun ...

I am experiencing an issue where the button I place inside a material-ui table is unresponsive to clicks

Here is the structure of my table: <TableContainer component={Paper} style={{height: "40vh", width: "90vh"}}> <Table size="small" sx={{ minWidth: 200 }}> <TableHea ...

Troubleshooting issues with ASP.NET bundling and minification when using Angular.js

After reviewing many questions on the same topic, none of them were able to solve my specific case. Our current challenge involves bundling and minifying AngularJs files within .Net code. The following code snippet shows how we are bundling our files insi ...

Navigating File Paths in Node.js

My directory structure is as follows: Main > models > file1.ejs | |> routes > file2.ejs In my code, I'm trying to require file1 from file2 like this: const variable = require("../models/file1.ejs). But what if I don't want to ...

Tracking user sessions using cookies, not relying on JavaScript like Google does

Currently, I am working in an environment that utilizes PHP on the server side and Javascript on the client side. In order to monitor user sessions, I regularly send a JS PUT request to the server every 5 seconds. This allows me to gather data such as the ...